textbringer 0.1.6 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.editorconfig +9 -0
- data/.travis.yml +3 -0
- data/CHANGES.md +8 -0
- data/README.md +42 -8
- data/appveyor.yml +3 -0
- data/lib/textbringer.rb +2 -0
- data/lib/textbringer/buffer.rb +46 -28
- data/lib/textbringer/commands/buffers.rb +4 -8
- data/lib/textbringer/commands/clipboard.rb +8 -2
- data/lib/textbringer/commands/files.rb +26 -0
- data/lib/textbringer/commands/misc.rb +56 -14
- data/lib/textbringer/config.rb +1 -0
- data/lib/textbringer/controller.rb +34 -0
- data/lib/textbringer/modes/c_mode.rb +286 -0
- data/lib/textbringer/modes/completion_list_mode.rb +34 -0
- data/lib/textbringer/modes/programming_mode.rb +37 -0
- data/lib/textbringer/modes/ruby_mode.rb +9 -36
- data/lib/textbringer/utils.rb +17 -26
- data/lib/textbringer/version.rb +1 -1
- data/lib/textbringer/window.rb +47 -29
- data/textbringer.gemspec +2 -1
- metadata +21 -4
data/lib/textbringer/config.rb
CHANGED
@@ -28,13 +28,16 @@ module Textbringer
|
|
28
28
|
@overriding_map = nil
|
29
29
|
@prefix_arg = nil
|
30
30
|
@current_prefix_arg = nil
|
31
|
+
@echo_immediately = false
|
31
32
|
end
|
32
33
|
|
33
34
|
def command_loop(tag)
|
34
35
|
catch(tag) do
|
35
36
|
loop do
|
36
37
|
begin
|
38
|
+
echo_input
|
37
39
|
c = read_char
|
40
|
+
break if c.nil?
|
38
41
|
Window.echo_area.clear_message
|
39
42
|
@last_key = c
|
40
43
|
@key_sequence << @last_key
|
@@ -60,11 +63,13 @@ module Textbringer
|
|
60
63
|
if cmd.nil?
|
61
64
|
keys = @key_sequence.map { |ch| key_name(ch) }.join(" ")
|
62
65
|
@key_sequence.clear
|
66
|
+
@prefix_arg = nil
|
63
67
|
message("#{keys} is undefined")
|
64
68
|
end
|
65
69
|
end
|
66
70
|
rescue Exception => e
|
67
71
|
show_exception(e)
|
72
|
+
@prefix_arg = nil
|
68
73
|
end
|
69
74
|
Window.redisplay
|
70
75
|
end
|
@@ -109,6 +114,8 @@ module Textbringer
|
|
109
114
|
"<#{key}>"
|
110
115
|
when "\e"
|
111
116
|
"ESC"
|
117
|
+
when "\n"
|
118
|
+
"RET"
|
112
119
|
when /\A[\0-\b\v-\x1f\x7f]\z/
|
113
120
|
"C-" + (key.ord ^ 0x40).chr.downcase
|
114
121
|
else
|
@@ -123,5 +130,32 @@ module Textbringer
|
|
123
130
|
Buffer.current&.keymap&.lookup(key_sequence) ||
|
124
131
|
GLOBAL_MAP.lookup(key_sequence)
|
125
132
|
end
|
133
|
+
|
134
|
+
def echo_input
|
135
|
+
if @prefix_arg || !@key_sequence.empty?
|
136
|
+
if !@echo_immediately
|
137
|
+
return if wait_input(1000)
|
138
|
+
end
|
139
|
+
@echo_immediately = true
|
140
|
+
s = String.new
|
141
|
+
if @prefix_arg
|
142
|
+
s << "C-u"
|
143
|
+
if @prefix_arg != [4]
|
144
|
+
s << "(#{@prefix_arg.inspect})"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
if !@key_sequence.empty?
|
148
|
+
s << " " if !s.empty?
|
149
|
+
s << @key_sequence.map { |ch| key_name(ch) }.join(" ")
|
150
|
+
end
|
151
|
+
s << "-"
|
152
|
+
Window.echo_area.show(s)
|
153
|
+
Window.echo_area.redisplay
|
154
|
+
Window.current.window.noutrefresh
|
155
|
+
Window.update
|
156
|
+
else
|
157
|
+
@echo_immediately = false
|
158
|
+
end
|
159
|
+
end
|
126
160
|
end
|
127
161
|
end
|
@@ -0,0 +1,286 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Textbringer
|
4
|
+
CONFIG[:c_indent_level] = 4
|
5
|
+
CONFIG[:c_indent_tabs_mode] = true
|
6
|
+
CONFIG[:c_continued_statement_offset] = 4
|
7
|
+
CONFIG[:c_case_label_offset] = -4
|
8
|
+
CONFIG[:c_label_offset] = -2
|
9
|
+
|
10
|
+
class CMode < ProgrammingMode
|
11
|
+
self.file_name_pattern = /\A.*\.[ch]\z/i
|
12
|
+
|
13
|
+
def initialize(buffer)
|
14
|
+
super(buffer)
|
15
|
+
@buffer[:indent_level] = CONFIG[:c_indent_level]
|
16
|
+
@buffer[:indent_tabs_mode] = CONFIG[:c_indent_tabs_mode]
|
17
|
+
end
|
18
|
+
|
19
|
+
def compile(cmd = read_from_minibuffer("Compile: ",
|
20
|
+
default: default_compile_command))
|
21
|
+
shell_execute(cmd, "*Compile result*")
|
22
|
+
end
|
23
|
+
|
24
|
+
def symbol_pattern
|
25
|
+
/[A-Za-z0-9\_\\]/
|
26
|
+
end
|
27
|
+
|
28
|
+
def default_compile_command
|
29
|
+
"make"
|
30
|
+
end
|
31
|
+
|
32
|
+
TOKEN_NAMES = [
|
33
|
+
:preprocessing_directive,
|
34
|
+
:comment,
|
35
|
+
:keyword,
|
36
|
+
:identifier,
|
37
|
+
:constant,
|
38
|
+
:string_literal,
|
39
|
+
:punctuator,
|
40
|
+
:space,
|
41
|
+
:partial_comment,
|
42
|
+
:unknown
|
43
|
+
]
|
44
|
+
|
45
|
+
TOKEN_REGEXP = /\G(?:
|
46
|
+
(?<preprocessing_directive>
|
47
|
+
^[ \t\f\v]*(?:\#|%:).*(?:\\\n.*)*[^\\]\n
|
48
|
+
) |
|
49
|
+
(?<comment>
|
50
|
+
(?<multiline_comment> \/\* (?> (?:.|\n)*? \*\/ ) ) |
|
51
|
+
(?<singleline_comment> \/\/ .*(?:\\\n.*)*(?<!\\)\n )
|
52
|
+
) |
|
53
|
+
(?<partial_comment>
|
54
|
+
(?<multiline_comment> \/\* (?:.|\n)* ) |
|
55
|
+
(?<singleline_comment> \/\/ .*? \\\n (?:.|\n)* )
|
56
|
+
) |
|
57
|
+
(?<keyword>
|
58
|
+
(?:
|
59
|
+
auto | break | case | char | const | continue | default | double | do |
|
60
|
+
else | enum | extern | float | for | goto | if | inline | int | long |
|
61
|
+
register | restrict | return | short | signed | sizeof | static | struct |
|
62
|
+
switch | typedef | union | unsigned | void | volatile | while | _Bool |
|
63
|
+
_Complex | _Imaginary
|
64
|
+
) \b
|
65
|
+
) |
|
66
|
+
(?<constant>
|
67
|
+
(?<floating_constant>
|
68
|
+
(?<decimal_floating_constant>
|
69
|
+
(?<fractional_constant>
|
70
|
+
(?<digit_sequence> [0-9]+ )? \. \g<digit_sequence> |
|
71
|
+
\g<digit_sequence> \. )
|
72
|
+
(?<exponent_part> [eE] [+\-]? \g<digit_sequence> )?
|
73
|
+
(?<floating_suffix> [flFL] )?
|
74
|
+
) |
|
75
|
+
(?<hexadecimal_floating_constant>
|
76
|
+
(?<hexadecimal_prefix> 0x | 0X )
|
77
|
+
(?<hexadecimal_fractional_constant>
|
78
|
+
(?<hexadecimal_digit_sequence> [0-9a-fA-F]+ )? \.
|
79
|
+
\g<hexadecimal_digit_sequence> |
|
80
|
+
\g<hexadecimal_digit_sequence> \. )
|
81
|
+
(?<binary_exponent_part> [pP] [+\-]? \g<digit_sequence> )
|
82
|
+
\g<floating_suffix>? |
|
83
|
+
\g<hexadecimal_prefix> \g<hexadecimal_digit_sequence>
|
84
|
+
\g<binary_exponent_part> \g<floating_suffix>?
|
85
|
+
)
|
86
|
+
) |
|
87
|
+
(?<integer_constant>
|
88
|
+
(?<decimal_constant> [1-9][0-9]* )
|
89
|
+
(?<integer_suffix>
|
90
|
+
(?<unsigned_suffix> [uU] ) (?<long_suffix> [lL] )?
|
91
|
+
\g<unsigned_suffix> (?<long_long_suffix> ll | LL )?
|
92
|
+
\g<long_suffix> \g<unsigned_suffix>?
|
93
|
+
\g<long_long_suffix> \g<unsigned_suffix>?
|
94
|
+
)? |
|
95
|
+
(?<hexadecimal_constant>
|
96
|
+
\g<hexadecimal_prefix> \g<hexadecimal_digit_sequence> )
|
97
|
+
\g<integer_suffix>? |
|
98
|
+
(?<octal_constant> 0 (?<octal_digit> [0-7] )* )
|
99
|
+
\g<integer_suffix>?
|
100
|
+
) |
|
101
|
+
(?<character_constant>
|
102
|
+
' (?<c_char_sequence>
|
103
|
+
(?<c_char>
|
104
|
+
[^'\\\r\n] |
|
105
|
+
(?<escape_sequence>
|
106
|
+
(?<simple_escape_sequence> \\ ['"?\\abfnrtv] ) |
|
107
|
+
(?<octal_escape_sequence> \\ \g<octal_digit>{1,3} ) |
|
108
|
+
(?<hexadecimal_escape_sequence>
|
109
|
+
\\x \g<hexadecimal_digit_sequence> ) |
|
110
|
+
(?<universal_character_name>
|
111
|
+
\\u[0-9a-fA-F]{4} |
|
112
|
+
\\U[0-9a-fA-F]{8} )
|
113
|
+
)
|
114
|
+
)+
|
115
|
+
) ' |
|
116
|
+
L' \g<c_char_sequence> '
|
117
|
+
)
|
118
|
+
) |
|
119
|
+
(?<string_literal>
|
120
|
+
" (?<s_char_sequence>
|
121
|
+
(?<s_char> [^"\\\r\n] | \g<escape_sequence> )+ ) " |
|
122
|
+
L" \g<s_char_sequence>? "
|
123
|
+
) |
|
124
|
+
(?<identifier>
|
125
|
+
(?<identifier_nondigit>
|
126
|
+
[_a-zA-Z] |
|
127
|
+
\g<universal_character_name> )
|
128
|
+
(?: \g<identifier_nondigit> | [0-9] )*
|
129
|
+
) |
|
130
|
+
(?<punctuator>
|
131
|
+
\[ | \] | \( | \) | \{ | \} |
|
132
|
+
\.\.\. | \. |
|
133
|
+
\+\+ | \+= | \+ |
|
134
|
+
-> | -- | -= | - |
|
135
|
+
\*= | \* |
|
136
|
+
\/= | \/ |
|
137
|
+
&& | &= | & |
|
138
|
+
\|\| | \|= | \| |
|
139
|
+
!= | ! |
|
140
|
+
~ |
|
141
|
+
== | = |
|
142
|
+
\^= | \^ |
|
143
|
+
<: | <% | <<= | << | <= | < |
|
144
|
+
>>= | >> | >= | > |
|
145
|
+
\? | ; |
|
146
|
+
:> | : |
|
147
|
+
, |
|
148
|
+
\#\# | \# |
|
149
|
+
%> | %:%: | %: | %= | %
|
150
|
+
) |
|
151
|
+
(?<space>
|
152
|
+
\s+
|
153
|
+
) |
|
154
|
+
(?<unknown>.)
|
155
|
+
)/x
|
156
|
+
|
157
|
+
def lex(s)
|
158
|
+
tokens = []
|
159
|
+
pos = 0
|
160
|
+
line = 1
|
161
|
+
column = 0
|
162
|
+
while pos < s.size && s.index(TOKEN_REGEXP, pos)
|
163
|
+
text = $&
|
164
|
+
token_name = TOKEN_NAMES.find { |name| $~[name] }
|
165
|
+
if text.empty?
|
166
|
+
raise EditorError, "Empty token: (#{line},#{column}) #{$~.inspect}"
|
167
|
+
end
|
168
|
+
tokens.push([[line, column], token_name, text])
|
169
|
+
lf_count = text.count("\n")
|
170
|
+
if lf_count > 0
|
171
|
+
line += lf_count
|
172
|
+
column = text.slice(/[^\n]*\z/).size
|
173
|
+
else
|
174
|
+
column += text.size
|
175
|
+
end
|
176
|
+
pos += text.size
|
177
|
+
end
|
178
|
+
tokens
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
|
183
|
+
def calculate_indentation
|
184
|
+
if @buffer.current_line == 1
|
185
|
+
return 0
|
186
|
+
end
|
187
|
+
@buffer.save_excursion do
|
188
|
+
@buffer.beginning_of_line
|
189
|
+
if @buffer.looking_at?(/[ \t]*(?:#|%:)/)
|
190
|
+
return 0
|
191
|
+
end
|
192
|
+
bol_pos = @buffer.point
|
193
|
+
s = @buffer.substring(@buffer.point_min, @buffer.point).b
|
194
|
+
tokens = lex(s)
|
195
|
+
_, event, = tokens.last
|
196
|
+
if event == :partial_comment
|
197
|
+
return nil
|
198
|
+
end
|
199
|
+
line, column, event, text = find_nearest_beginning_token(tokens)
|
200
|
+
if event == :punctuator && text == "("
|
201
|
+
return column + 1
|
202
|
+
end
|
203
|
+
if line
|
204
|
+
@buffer.goto_line(line)
|
205
|
+
else
|
206
|
+
(ln, _), ev = tokens.reverse_each.drop_while { |(l, _), e, t|
|
207
|
+
l >= @buffer.current_line - 1 || e == :space
|
208
|
+
}.first
|
209
|
+
if ev == :comment
|
210
|
+
@buffer.goto_line(ln)
|
211
|
+
else
|
212
|
+
@buffer.backward_line
|
213
|
+
end
|
214
|
+
end
|
215
|
+
@buffer.looking_at?(/[ \t]*/)
|
216
|
+
base_indentation = @buffer.match_string(0).
|
217
|
+
gsub(/\t/, " " * @buffer[:tab_width]).size
|
218
|
+
@buffer.goto_char(bol_pos)
|
219
|
+
if line.nil? ||
|
220
|
+
@buffer.looking_at?(/[ \t]*([}\])]|:>|%>)/)
|
221
|
+
indentation = base_indentation
|
222
|
+
else
|
223
|
+
indentation = base_indentation + @buffer[:indent_level]
|
224
|
+
end
|
225
|
+
if @buffer.looking_at?(/[ \t]*(?:case.*|default):/)
|
226
|
+
indentation += @buffer[:c_case_label_offset]
|
227
|
+
elsif @buffer.looking_at?(/[ \t]*[_a-zA-Z0-9\\]+:/)
|
228
|
+
indentation += @buffer[:c_label_offset]
|
229
|
+
end
|
230
|
+
indent_continued_statement(indentation, tokens, line)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def indent_continued_statement(indentation, tokens ,line)
|
235
|
+
if line && !@buffer.looking_at?(/[ \t]*\{/)
|
236
|
+
_, last_event, last_text =
|
237
|
+
tokens.reverse_each.drop_while { |(l, _), e, t|
|
238
|
+
l == @buffer.current_line || e == :space || e == :comment
|
239
|
+
}.first
|
240
|
+
if last_event != :preprocessing_directive &&
|
241
|
+
/[:;{}]/ !~ last_text
|
242
|
+
indentation + @buffer[:c_continued_statement_offset]
|
243
|
+
else
|
244
|
+
indentation
|
245
|
+
end
|
246
|
+
else
|
247
|
+
indentation
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
CANONICAL_PUNCTUATORS = Hash.new { |h, k| k }
|
252
|
+
CANONICAL_PUNCTUATORS["<:"] = "["
|
253
|
+
CANONICAL_PUNCTUATORS[":>"] = "]"
|
254
|
+
CANONICAL_PUNCTUATORS["<%"] = "{"
|
255
|
+
CANONICAL_PUNCTUATORS["%>"] = "}"
|
256
|
+
BLOCK_END = {
|
257
|
+
"{" => "}",
|
258
|
+
"(" => ")",
|
259
|
+
"[" => "]"
|
260
|
+
}
|
261
|
+
BLOCK_BEG = BLOCK_END.invert
|
262
|
+
|
263
|
+
def find_nearest_beginning_token(tokens)
|
264
|
+
stack = []
|
265
|
+
(tokens.size - 1).downto(0) do |i|
|
266
|
+
(line, column), event, raw_text = tokens[i]
|
267
|
+
text = CANONICAL_PUNCTUATORS[raw_text]
|
268
|
+
case event
|
269
|
+
when :punctuator
|
270
|
+
if BLOCK_BEG.key?(text)
|
271
|
+
stack.push(text)
|
272
|
+
elsif BLOCK_END.key?(text)
|
273
|
+
if stack.empty?
|
274
|
+
return line, column, event, text
|
275
|
+
end
|
276
|
+
if stack.last != BLOCK_END[text]
|
277
|
+
raise EditorError, "#{@buffer.name}:#{line}: Unmatched #{text}"
|
278
|
+
end
|
279
|
+
stack.pop
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
return nil
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Textbringer
|
4
|
+
class CompletionListMode < Mode
|
5
|
+
define_generic_command :choose_completion
|
6
|
+
|
7
|
+
COMPLETION_LIST_MODE_MAP = Keymap.new
|
8
|
+
COMPLETION_LIST_MODE_MAP.define_key("\n", :choose_completion_command)
|
9
|
+
|
10
|
+
def initialize(buffer)
|
11
|
+
super(buffer)
|
12
|
+
buffer.keymap = COMPLETION_LIST_MODE_MAP
|
13
|
+
end
|
14
|
+
|
15
|
+
def choose_completion
|
16
|
+
unless Window.echo_area.active?
|
17
|
+
raise EditorError, "Minibuffer is not active"
|
18
|
+
end
|
19
|
+
s = @buffer.save_excursion {
|
20
|
+
@buffer.beginning_of_line
|
21
|
+
@buffer.looking_at?(/.*/)
|
22
|
+
@buffer.match_string(0)
|
23
|
+
}
|
24
|
+
if s.size > 0
|
25
|
+
Window.current = Window.echo_area
|
26
|
+
complete_minibuffer_with_string(s)
|
27
|
+
if COMPLETION[:original_buffer]
|
28
|
+
COMPLETION[:completions_window].buffer =
|
29
|
+
COMPLETION[:original_buffer]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -25,6 +25,37 @@ module Textbringer
|
|
25
25
|
buffer.keymap = PROGRAMMING_MODE_MAP
|
26
26
|
end
|
27
27
|
|
28
|
+
# Return true if modified.
|
29
|
+
def indent_line
|
30
|
+
result = false
|
31
|
+
level = calculate_indentation
|
32
|
+
return result if level.nil?
|
33
|
+
@buffer.save_excursion do
|
34
|
+
@buffer.beginning_of_line
|
35
|
+
has_space = @buffer.looking_at?(/[ \t]+/)
|
36
|
+
if has_space
|
37
|
+
s = @buffer.match_string(0)
|
38
|
+
break if /\t/ !~ s && s.size == level
|
39
|
+
@buffer.delete_region(@buffer.match_beginning(0),
|
40
|
+
@buffer.match_end(0))
|
41
|
+
else
|
42
|
+
break if level == 0
|
43
|
+
end
|
44
|
+
@buffer.indent_to(level)
|
45
|
+
if has_space
|
46
|
+
@buffer.merge_undo(2)
|
47
|
+
end
|
48
|
+
result = true
|
49
|
+
end
|
50
|
+
pos = @buffer.point
|
51
|
+
@buffer.beginning_of_line
|
52
|
+
@buffer.forward_char while /[ \t]/ =~ @buffer.char_after
|
53
|
+
if @buffer.point < pos
|
54
|
+
@buffer.goto_char(pos)
|
55
|
+
end
|
56
|
+
result
|
57
|
+
end
|
58
|
+
|
28
59
|
def newline_and_reindent
|
29
60
|
n = 1
|
30
61
|
if indent_line
|
@@ -44,5 +75,11 @@ module Textbringer
|
|
44
75
|
end
|
45
76
|
@buffer.merge_undo(n) if n > 1
|
46
77
|
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def calculate_indentation
|
82
|
+
raise EditorError, "indent_line is not defined in the current mode"
|
83
|
+
end
|
47
84
|
end
|
48
85
|
end
|