textbringer 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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