textbringer 0.1.6 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Textbringer
4
4
  CONFIG = {
5
+ east_asian_ambiguous_width: 1,
5
6
  default_file_encoding: Encoding::UTF_8,
6
7
  default_file_format: :unix,
7
8
  tab_width: 8,
@@ -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