textbringer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +13 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +41 -0
- data/LICENSE.txt +21 -0
- data/README.md +58 -0
- data/Rakefile +9 -0
- data/bin/console +9 -0
- data/exe/tb +40 -0
- data/lib/textbringer/buffer.rb +1367 -0
- data/lib/textbringer/commands.rb +690 -0
- data/lib/textbringer/config.rb +9 -0
- data/lib/textbringer/controller.rb +129 -0
- data/lib/textbringer/errors.rb +12 -0
- data/lib/textbringer/keymap.rb +141 -0
- data/lib/textbringer/mode.rb +64 -0
- data/lib/textbringer/modes/backtrace_mode.rb +38 -0
- data/lib/textbringer/modes/fundamental_mode.rb +6 -0
- data/lib/textbringer/modes/programming_mode.rb +43 -0
- data/lib/textbringer/modes/ruby_mode.rb +192 -0
- data/lib/textbringer/modes.rb +7 -0
- data/lib/textbringer/utils.rb +277 -0
- data/lib/textbringer/version.rb +3 -0
- data/lib/textbringer/window.rb +646 -0
- data/lib/textbringer.rb +10 -0
- data/textbringer.gemspec +30 -0
- metadata +170 -0
@@ -0,0 +1,690 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "open3"
|
4
|
+
require "io/wait"
|
5
|
+
|
6
|
+
module Textbringer
|
7
|
+
module Commands
|
8
|
+
include Utils
|
9
|
+
|
10
|
+
@@command_list = []
|
11
|
+
|
12
|
+
def self.list
|
13
|
+
@@command_list
|
14
|
+
end
|
15
|
+
|
16
|
+
def define_command(name, &block)
|
17
|
+
Commands.send(:define_method, name, &block)
|
18
|
+
@@command_list << name if !@@command_list.include?(name)
|
19
|
+
end
|
20
|
+
module_function :define_command
|
21
|
+
|
22
|
+
def undefine_command(name)
|
23
|
+
if @@command_list.include?(name)
|
24
|
+
Commands.send(:undef_method, name)
|
25
|
+
@@command_list.delete(name)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
module_function :undefine_command
|
29
|
+
|
30
|
+
define_command(:version) do
|
31
|
+
message("Textbringer #{Textbringer::VERSION} "\
|
32
|
+
"(ruby #{RUBY_VERSION} [#{RUBY_PLATFORM}])")
|
33
|
+
end
|
34
|
+
|
35
|
+
[
|
36
|
+
:forward_char,
|
37
|
+
:backward_char,
|
38
|
+
:forward_word,
|
39
|
+
:backward_word,
|
40
|
+
:next_line,
|
41
|
+
:previous_line,
|
42
|
+
:delete_char,
|
43
|
+
:backward_delete_char,
|
44
|
+
].each do |name|
|
45
|
+
define_command(name) do |n = number_prefix_arg|
|
46
|
+
Buffer.current.send(name, n)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
[
|
51
|
+
:beginning_of_line,
|
52
|
+
:end_of_line,
|
53
|
+
:beginning_of_buffer,
|
54
|
+
:end_of_buffer,
|
55
|
+
:set_mark,
|
56
|
+
:exchange_point_and_mark,
|
57
|
+
:copy_region,
|
58
|
+
:kill_region,
|
59
|
+
:yank,
|
60
|
+
:newline,
|
61
|
+
:delete_region,
|
62
|
+
:transpose_chars
|
63
|
+
].each do |name|
|
64
|
+
define_command(name) do
|
65
|
+
Buffer.current.send(name)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
define_command(:goto_char) do
|
70
|
+
|n = read_from_minibuffer("Go to char: ")|
|
71
|
+
Buffer.current.goto_char(n.to_i)
|
72
|
+
Window.current.recenter_if_needed
|
73
|
+
end
|
74
|
+
|
75
|
+
define_command(:goto_line) do
|
76
|
+
|n = read_from_minibuffer("Go to line: ")|
|
77
|
+
Buffer.current.goto_line(n.to_i)
|
78
|
+
Window.current.recenter_if_needed
|
79
|
+
end
|
80
|
+
|
81
|
+
define_command(:self_insert) do |n = number_prefix_arg|
|
82
|
+
c = Controller.current.last_key.chr(Encoding::UTF_8)
|
83
|
+
merge_undo = Controller.current.last_command == :self_insert
|
84
|
+
n.times do
|
85
|
+
Buffer.current.insert(c, merge_undo)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
define_command(:quoted_insert) do |n = number_prefix_arg|
|
90
|
+
c = Controller.current.read_char
|
91
|
+
if !c.is_a?(Integer)
|
92
|
+
raise "Invalid key"
|
93
|
+
end
|
94
|
+
ch = c.chr(Encoding::UTF_8)
|
95
|
+
n.times do
|
96
|
+
Buffer.current.insert(ch)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
define_command(:kill_line) do
|
101
|
+
Buffer.current.kill_line(Controller.current.last_command == :kill_region)
|
102
|
+
Controller.current.this_command = :kill_region
|
103
|
+
end
|
104
|
+
|
105
|
+
define_command(:kill_word) do
|
106
|
+
Buffer.current.kill_word(Controller.current.last_command == :kill_region)
|
107
|
+
Controller.current.this_command = :kill_region
|
108
|
+
end
|
109
|
+
|
110
|
+
define_command(:yank_pop) do
|
111
|
+
if Controller.current.last_command != :yank
|
112
|
+
raise EditorError, "Previous command was not a yank"
|
113
|
+
end
|
114
|
+
Buffer.current.yank_pop
|
115
|
+
Controller.current.this_command = :yank
|
116
|
+
end
|
117
|
+
|
118
|
+
RE_SEARCH_STATUS = {
|
119
|
+
last_regexp: nil
|
120
|
+
}
|
121
|
+
|
122
|
+
define_command(:re_search_forward) do
|
123
|
+
|s = read_from_minibuffer("RE search: ",
|
124
|
+
default: RE_SEARCH_STATUS[:last_regexp])|
|
125
|
+
RE_SEARCH_STATUS[:last_regexp] = s
|
126
|
+
Buffer.current.re_search_forward(s)
|
127
|
+
end
|
128
|
+
|
129
|
+
def match_beginning(n)
|
130
|
+
Buffer.current.match_beginning(n)
|
131
|
+
end
|
132
|
+
|
133
|
+
def match_end(n)
|
134
|
+
Buffer.current.match_end(n)
|
135
|
+
end
|
136
|
+
|
137
|
+
def match_string(n)
|
138
|
+
Buffer.current.match_string(n)
|
139
|
+
end
|
140
|
+
|
141
|
+
def replace_match(s)
|
142
|
+
Buffer.current.replace_match(s)
|
143
|
+
end
|
144
|
+
|
145
|
+
define_command(:query_replace_regexp) do
|
146
|
+
|regexp = read_from_minibuffer("Query replace regexp: "),
|
147
|
+
to_str = read_from_minibuffer("Query replace regexp #{regexp} with: ")|
|
148
|
+
n = 0
|
149
|
+
begin
|
150
|
+
loop do
|
151
|
+
re_search_forward(regexp)
|
152
|
+
Window.current.recenter_if_needed
|
153
|
+
Buffer.current.set_visible_mark(match_beginning(0))
|
154
|
+
begin
|
155
|
+
Window.redisplay
|
156
|
+
c = read_single_char("Replace?", [?y, ?n, ?!, ?q, ?.])
|
157
|
+
case c
|
158
|
+
when ?y
|
159
|
+
replace_match(to_str)
|
160
|
+
n += 1
|
161
|
+
when ?n
|
162
|
+
# do nothing
|
163
|
+
when ?!
|
164
|
+
replace_match(to_str)
|
165
|
+
n += 1 + Buffer.current.replace_regexp_forward(regexp, to_str)
|
166
|
+
Buffer.current.merge_undo(2)
|
167
|
+
break
|
168
|
+
when ?q
|
169
|
+
break
|
170
|
+
when ?.
|
171
|
+
replace_match(to_str)
|
172
|
+
n += 1
|
173
|
+
break
|
174
|
+
end
|
175
|
+
ensure
|
176
|
+
Buffer.current.delete_visible_mark
|
177
|
+
end
|
178
|
+
end
|
179
|
+
rescue SearchError
|
180
|
+
end
|
181
|
+
if n == 1
|
182
|
+
message("Replaced 1 occurrence")
|
183
|
+
else
|
184
|
+
message("Replaced #{n} occurrences")
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
define_command(:undo) do
|
189
|
+
Buffer.current.undo
|
190
|
+
message("Undo!")
|
191
|
+
end
|
192
|
+
|
193
|
+
define_command(:redo) do
|
194
|
+
Buffer.current.redo
|
195
|
+
message("Redo!")
|
196
|
+
end
|
197
|
+
|
198
|
+
define_command(:resize_window) do
|
199
|
+
Window.resize
|
200
|
+
end
|
201
|
+
|
202
|
+
define_command(:recenter) do
|
203
|
+
Window.current.recenter
|
204
|
+
Window.redraw
|
205
|
+
end
|
206
|
+
|
207
|
+
define_command(:scroll_up) do
|
208
|
+
Window.current.scroll_up
|
209
|
+
end
|
210
|
+
|
211
|
+
define_command(:scroll_down) do
|
212
|
+
Window.current.scroll_down
|
213
|
+
end
|
214
|
+
|
215
|
+
define_command(:delete_window) do
|
216
|
+
Window.delete_window
|
217
|
+
end
|
218
|
+
|
219
|
+
define_command(:delete_other_windows) do
|
220
|
+
Window.delete_other_windows
|
221
|
+
end
|
222
|
+
|
223
|
+
define_command(:split_window) do
|
224
|
+
Window.current.split
|
225
|
+
end
|
226
|
+
|
227
|
+
define_command(:other_window) do
|
228
|
+
Window.other_window
|
229
|
+
end
|
230
|
+
|
231
|
+
define_command(:exit_textbringer) do |status = 0|
|
232
|
+
if Buffer.any? { |buffer| /\A\*/ !~ buffer.name && buffer.modified? }
|
233
|
+
return unless yes_or_no?("Unsaved buffers exist; exit anyway?")
|
234
|
+
end
|
235
|
+
exit(status)
|
236
|
+
end
|
237
|
+
|
238
|
+
define_command(:suspend_textbringer) do
|
239
|
+
Ncurses.endwin
|
240
|
+
Process.kill(:STOP, $$)
|
241
|
+
end
|
242
|
+
|
243
|
+
define_command(:pwd) do
|
244
|
+
message(Dir.pwd)
|
245
|
+
end
|
246
|
+
|
247
|
+
define_command(:chdir) do
|
248
|
+
|dir_name = read_file_name("Change directory: ")|
|
249
|
+
Dir.chdir(dir_name)
|
250
|
+
end
|
251
|
+
|
252
|
+
define_command(:find_file) do
|
253
|
+
|file_name = read_file_name("Find file: ")|
|
254
|
+
buffer = Buffer.find_file(file_name)
|
255
|
+
if buffer.new_file?
|
256
|
+
message("New file")
|
257
|
+
end
|
258
|
+
switch_to_buffer(buffer)
|
259
|
+
mode = Mode.list.find { |mode|
|
260
|
+
mode.file_name_pattern &&
|
261
|
+
mode.file_name_pattern =~ File.basename(buffer.file_name)
|
262
|
+
} || FundamentalMode
|
263
|
+
send(mode.command_name)
|
264
|
+
end
|
265
|
+
|
266
|
+
define_command(:switch_to_buffer) do
|
267
|
+
|buffer_name = read_buffer("Switch to buffer: ")|
|
268
|
+
if buffer_name.is_a?(Buffer)
|
269
|
+
buffer = buffer_name
|
270
|
+
else
|
271
|
+
buffer = Buffer[buffer_name]
|
272
|
+
end
|
273
|
+
if buffer
|
274
|
+
Window.current.buffer = Buffer.current = buffer
|
275
|
+
else
|
276
|
+
message("No such buffer: #{buffer_name}")
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
define_command(:save_buffer) do
|
281
|
+
if Buffer.current.file_name.nil?
|
282
|
+
Buffer.current.file_name = read_file_name("File to save in: ")
|
283
|
+
next if Buffer.current.file_name.nil?
|
284
|
+
end
|
285
|
+
if Buffer.current.file_modified?
|
286
|
+
unless yes_or_no?("File changed on disk. Save anyway?")
|
287
|
+
message("Cancelled")
|
288
|
+
next
|
289
|
+
end
|
290
|
+
end
|
291
|
+
Buffer.current.save
|
292
|
+
message("Wrote #{Buffer.current.file_name}")
|
293
|
+
end
|
294
|
+
|
295
|
+
define_command(:write_file) do
|
296
|
+
|file_name = read_file_name("Write file: ")|
|
297
|
+
if File.directory?(file_name)
|
298
|
+
file_name = File.expand_path(Buffer.current.name, file_name)
|
299
|
+
end
|
300
|
+
if File.exist?(file_name)
|
301
|
+
unless y_or_n?("File `#{file_name}' exists; overwrite?")
|
302
|
+
message("Cancelled")
|
303
|
+
next
|
304
|
+
end
|
305
|
+
end
|
306
|
+
Buffer.current.save(file_name)
|
307
|
+
message("Wrote #{Buffer.current.file_name}")
|
308
|
+
end
|
309
|
+
|
310
|
+
define_command(:kill_buffer) do
|
311
|
+
|name = read_buffer("Kill buffer: ", default: Buffer.current.name)|
|
312
|
+
if name.is_a?(Buffer)
|
313
|
+
buffer = name
|
314
|
+
else
|
315
|
+
buffer = Buffer[name]
|
316
|
+
end
|
317
|
+
if buffer.modified?
|
318
|
+
next unless yes_or_no?("The last change is not saved; kill anyway?")
|
319
|
+
message("Arioch! Arioch! Blood and souls for my Lord Arioch!")
|
320
|
+
end
|
321
|
+
buffer.kill
|
322
|
+
if Buffer.count == 0
|
323
|
+
buffer = Buffer.new_buffer("*scratch*")
|
324
|
+
switch_to_buffer(buffer)
|
325
|
+
elsif Buffer.current.nil?
|
326
|
+
switch_to_buffer(Buffer.last)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
define_command(:set_buffer_file_encoding) do
|
331
|
+
|enc = read_from_minibuffer("File encoding: ",
|
332
|
+
default: Buffer.current.file_encoding.name)|
|
333
|
+
Buffer.current.file_encoding = Encoding.find(enc)
|
334
|
+
end
|
335
|
+
|
336
|
+
define_command(:set_buffer_file_format) do
|
337
|
+
|format = read_from_minibuffer("File format: ",
|
338
|
+
default: Buffer.current.file_format.to_s)|
|
339
|
+
Buffer.current.file_format = format
|
340
|
+
end
|
341
|
+
|
342
|
+
define_command(:execute_command) do
|
343
|
+
|cmd = read_command_name("M-x ").strip.intern|
|
344
|
+
unless Commands.list.include?(cmd)
|
345
|
+
raise EditorError, "Undefined command: #{cmd}"
|
346
|
+
end
|
347
|
+
Controller.current.this_command = cmd
|
348
|
+
send(cmd)
|
349
|
+
end
|
350
|
+
|
351
|
+
define_command(:eval_expression) do
|
352
|
+
|s = read_from_minibuffer("Eval: ")|
|
353
|
+
message(eval(s, TOPLEVEL_BINDING, "(eval_expression)", 1).inspect)
|
354
|
+
end
|
355
|
+
|
356
|
+
define_command(:eval_buffer) do
|
357
|
+
buffer = Buffer.current
|
358
|
+
result = eval(buffer.to_s, TOPLEVEL_BINDING,
|
359
|
+
buffer.file_name || buffer.name, 1)
|
360
|
+
message(result.inspect)
|
361
|
+
end
|
362
|
+
|
363
|
+
define_command(:eval_region) do
|
364
|
+
buffer = Buffer.current
|
365
|
+
b, e = buffer.point, buffer.mark
|
366
|
+
if e < b
|
367
|
+
b, e = e, b
|
368
|
+
end
|
369
|
+
result = eval(buffer.substring(b, e), TOPLEVEL_BINDING,
|
370
|
+
"(eval_region)", 1)
|
371
|
+
message(result.inspect)
|
372
|
+
end
|
373
|
+
|
374
|
+
define_command(:exit_recursive_edit) do
|
375
|
+
if @recursive_edit_level == 0
|
376
|
+
raise EditorError, "No recursive edit is in progress"
|
377
|
+
end
|
378
|
+
throw RECURSIVE_EDIT_TAG, false
|
379
|
+
end
|
380
|
+
|
381
|
+
define_command(:abort_recursive_edit) do
|
382
|
+
if @recursive_edit_level == 0
|
383
|
+
raise EditorError, "No recursive edit is in progress"
|
384
|
+
end
|
385
|
+
throw RECURSIVE_EDIT_TAG, true
|
386
|
+
end
|
387
|
+
|
388
|
+
define_command(:top_level) do
|
389
|
+
throw TOP_LEVEL_TAG
|
390
|
+
end
|
391
|
+
|
392
|
+
define_command(:complete_minibuffer) do
|
393
|
+
minibuffer = Buffer.minibuffer
|
394
|
+
completion_proc = minibuffer[:completion_proc]
|
395
|
+
if completion_proc
|
396
|
+
s = completion_proc.call(minibuffer.to_s)
|
397
|
+
if s
|
398
|
+
minibuffer.delete_region(minibuffer.point_min,
|
399
|
+
minibuffer.point_max)
|
400
|
+
minibuffer.insert(s)
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
UNIVERSAL_ARGUMENT_MAP = Keymap.new
|
406
|
+
(?0..?9).each do |c|
|
407
|
+
UNIVERSAL_ARGUMENT_MAP.define_key(c, :digit_argument)
|
408
|
+
GLOBAL_MAP.define_key("\e#{c}", :digit_argument)
|
409
|
+
end
|
410
|
+
UNIVERSAL_ARGUMENT_MAP.define_key(?-, :negative_argument)
|
411
|
+
UNIVERSAL_ARGUMENT_MAP.define_key(?\C-u, :universal_argument_more)
|
412
|
+
|
413
|
+
def universal_argument_mode
|
414
|
+
set_transient_map(UNIVERSAL_ARGUMENT_MAP)
|
415
|
+
end
|
416
|
+
|
417
|
+
define_command(:universal_argument) do
|
418
|
+
Controller.current.prefix_arg = [4]
|
419
|
+
universal_argument_mode
|
420
|
+
end
|
421
|
+
|
422
|
+
def current_prefix_arg
|
423
|
+
Controller.current.current_prefix_arg
|
424
|
+
end
|
425
|
+
|
426
|
+
def number_prefix_arg
|
427
|
+
arg = current_prefix_arg
|
428
|
+
case arg
|
429
|
+
when Integer
|
430
|
+
arg
|
431
|
+
when Array
|
432
|
+
arg.first
|
433
|
+
when :-
|
434
|
+
-1
|
435
|
+
else
|
436
|
+
1
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
define_command(:digit_argument) do
|
441
|
+
|arg = current_prefix_arg|
|
442
|
+
n = last_key.chr.to_i
|
443
|
+
Controller.current.prefix_arg =
|
444
|
+
case arg
|
445
|
+
when Integer
|
446
|
+
arg * 10 + (arg < 0 ? -n : n)
|
447
|
+
when :-
|
448
|
+
-n
|
449
|
+
else
|
450
|
+
n
|
451
|
+
end
|
452
|
+
universal_argument_mode
|
453
|
+
end
|
454
|
+
|
455
|
+
define_command(:negative_argument) do
|
456
|
+
|arg = current_prefix_arg|
|
457
|
+
Controller.current.prefix_arg =
|
458
|
+
case arg
|
459
|
+
when Integer
|
460
|
+
-arg
|
461
|
+
when :-
|
462
|
+
nil
|
463
|
+
else
|
464
|
+
:-
|
465
|
+
end
|
466
|
+
universal_argument_mode
|
467
|
+
end
|
468
|
+
|
469
|
+
define_command(:universal_argument_more) do
|
470
|
+
|arg = current_prefix_arg|
|
471
|
+
Controller.current.prefix_arg =
|
472
|
+
case arg
|
473
|
+
when Array
|
474
|
+
[4 * arg.first]
|
475
|
+
when :-
|
476
|
+
[-4]
|
477
|
+
else
|
478
|
+
nil
|
479
|
+
end
|
480
|
+
if Controller.current.prefix_arg
|
481
|
+
universal_argument_mode
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
define_command(:keyboard_quit) do
|
486
|
+
raise Quit
|
487
|
+
end
|
488
|
+
|
489
|
+
define_command(:recursive_edit) do
|
490
|
+
Controller.current.recursive_edit
|
491
|
+
end
|
492
|
+
|
493
|
+
ISEARCH_MODE_MAP = Keymap.new
|
494
|
+
(0x20..0x7e).each do |c|
|
495
|
+
ISEARCH_MODE_MAP.define_key(c, :isearch_printing_char)
|
496
|
+
end
|
497
|
+
ISEARCH_MODE_MAP.define_key(?\t, :isearch_printing_char)
|
498
|
+
ISEARCH_MODE_MAP.handle_undefined_key do |key|
|
499
|
+
if key.is_a?(Integer) && key > 0x80
|
500
|
+
begin
|
501
|
+
key.chr(Encoding::UTF_8)
|
502
|
+
:isearch_printing_char
|
503
|
+
rescue RangeError
|
504
|
+
nil
|
505
|
+
end
|
506
|
+
else
|
507
|
+
nil
|
508
|
+
end
|
509
|
+
end
|
510
|
+
ISEARCH_MODE_MAP.define_key(:backspace, :isearch_delete_char)
|
511
|
+
ISEARCH_MODE_MAP.define_key(?\C-h, :isearch_delete_char)
|
512
|
+
ISEARCH_MODE_MAP.define_key(?\C-s, :isearch_repeat_forward)
|
513
|
+
ISEARCH_MODE_MAP.define_key(?\C-r, :isearch_repeat_backward)
|
514
|
+
ISEARCH_MODE_MAP.define_key(?\n, :isearch_exit)
|
515
|
+
ISEARCH_MODE_MAP.define_key(?\C-g, :isearch_abort)
|
516
|
+
|
517
|
+
ISEARCH_STATUS = {
|
518
|
+
forward: true,
|
519
|
+
string: "",
|
520
|
+
last_string: "",
|
521
|
+
start: 0,
|
522
|
+
last_pos: 0
|
523
|
+
}
|
524
|
+
|
525
|
+
define_command(:isearch_forward) do
|
526
|
+
isearch_mode(true)
|
527
|
+
end
|
528
|
+
|
529
|
+
define_command(:isearch_backward) do
|
530
|
+
isearch_mode(false)
|
531
|
+
end
|
532
|
+
|
533
|
+
def isearch_mode(forward)
|
534
|
+
ISEARCH_STATUS[:forward] = forward
|
535
|
+
ISEARCH_STATUS[:string] = String.new
|
536
|
+
Controller.current.overriding_map = ISEARCH_MODE_MAP
|
537
|
+
run_hooks(:isearch_mode_hook)
|
538
|
+
add_hook(:pre_command_hook, :isearch_pre_command_hook)
|
539
|
+
ISEARCH_STATUS[:start] = ISEARCH_STATUS[:last_pos] = Buffer.current.point
|
540
|
+
if Buffer.current != Buffer.minibuffer
|
541
|
+
message(isearch_prompt, log: false)
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
def isearch_prompt
|
546
|
+
if ISEARCH_STATUS[:forward]
|
547
|
+
"I-search: "
|
548
|
+
else
|
549
|
+
"I-search backward: "
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
def isearch_pre_command_hook
|
554
|
+
if /\Aisearch_/ !~ Controller.current.this_command
|
555
|
+
isearch_done
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
def isearch_done
|
560
|
+
Buffer.current.delete_visible_mark
|
561
|
+
Controller.current.overriding_map = nil
|
562
|
+
remove_hook(:pre_command_hook, :isearch_pre_command_hook)
|
563
|
+
ISEARCH_STATUS[:last_string] = ISEARCH_STATUS[:string]
|
564
|
+
end
|
565
|
+
|
566
|
+
define_command(:isearch_exit) do
|
567
|
+
isearch_done
|
568
|
+
end
|
569
|
+
|
570
|
+
define_command(:isearch_abort) do
|
571
|
+
goto_char(Buffer.current[:isearch_start])
|
572
|
+
isearch_done
|
573
|
+
raise Quit
|
574
|
+
end
|
575
|
+
|
576
|
+
define_command(:isearch_printing_char) do
|
577
|
+
c = Controller.current.last_key.chr(Encoding::UTF_8)
|
578
|
+
ISEARCH_STATUS[:string].concat(c)
|
579
|
+
isearch_search
|
580
|
+
end
|
581
|
+
|
582
|
+
define_command(:isearch_delete_char) do
|
583
|
+
ISEARCH_STATUS[:string].chop!
|
584
|
+
isearch_search
|
585
|
+
end
|
586
|
+
|
587
|
+
def isearch_search
|
588
|
+
forward = ISEARCH_STATUS[:forward]
|
589
|
+
options = if /\A[A-Z]/ =~ ISEARCH_STATUS[:string]
|
590
|
+
nil
|
591
|
+
else
|
592
|
+
Regexp::IGNORECASE
|
593
|
+
end
|
594
|
+
re = Regexp.new(Regexp.quote(ISEARCH_STATUS[:string]), options)
|
595
|
+
last_pos = ISEARCH_STATUS[:last_pos]
|
596
|
+
offset = forward ? last_pos : last_pos - ISEARCH_STATUS[:string].bytesize
|
597
|
+
if Buffer.current.byteindex(forward, re, offset)
|
598
|
+
if Buffer.current != Buffer.minibuffer
|
599
|
+
message(isearch_prompt + ISEARCH_STATUS[:string], log: false)
|
600
|
+
end
|
601
|
+
Buffer.current.set_visible_mark(forward ? match_beginning(0) :
|
602
|
+
match_end(0))
|
603
|
+
goto_char(forward ? match_end(0) : match_beginning(0))
|
604
|
+
else
|
605
|
+
if Buffer.current != Buffer.minibuffer
|
606
|
+
message("Falling " + isearch_prompt + ISEARCH_STATUS[:string],
|
607
|
+
log: false)
|
608
|
+
end
|
609
|
+
end
|
610
|
+
end
|
611
|
+
|
612
|
+
def isearch_repeat_forward
|
613
|
+
isearch_repeat(true)
|
614
|
+
end
|
615
|
+
|
616
|
+
def isearch_repeat_backward
|
617
|
+
isearch_repeat(false)
|
618
|
+
end
|
619
|
+
|
620
|
+
def isearch_repeat(forward)
|
621
|
+
ISEARCH_STATUS[:forward] = forward
|
622
|
+
ISEARCH_STATUS[:last_pos] = Buffer.current.point
|
623
|
+
if ISEARCH_STATUS[:string].empty?
|
624
|
+
ISEARCH_STATUS[:string] = ISEARCH_STATUS[:last_string]
|
625
|
+
end
|
626
|
+
isearch_search
|
627
|
+
end
|
628
|
+
|
629
|
+
define_command(:shell_execute) do
|
630
|
+
|cmd = read_from_minibuffer("Shell execute: "),
|
631
|
+
buffer_name = "*Shell output*"|
|
632
|
+
buffer = Buffer.find_or_new(buffer_name)
|
633
|
+
switch_to_buffer(buffer)
|
634
|
+
buffer.read_only = false
|
635
|
+
buffer.clear
|
636
|
+
Window.redisplay
|
637
|
+
signals = [:INT, :TERM, :KILL]
|
638
|
+
begin
|
639
|
+
Open3.popen2e(cmd) do |input, output, wait_thread|
|
640
|
+
input.close
|
641
|
+
loop do
|
642
|
+
status = output.wait_readable(0.5)
|
643
|
+
if status == false
|
644
|
+
break # EOF
|
645
|
+
end
|
646
|
+
if status
|
647
|
+
begin
|
648
|
+
s = output.read_nonblock(1024)
|
649
|
+
buffer.insert(s)
|
650
|
+
Window.redisplay
|
651
|
+
rescue EOFError
|
652
|
+
break
|
653
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
654
|
+
next
|
655
|
+
end
|
656
|
+
end
|
657
|
+
if received_keyboard_quit?
|
658
|
+
if signals.empty?
|
659
|
+
keyboard_quit
|
660
|
+
else
|
661
|
+
sig = signals.shift
|
662
|
+
message("Send #{sig} to #{wait_thread.pid}")
|
663
|
+
Process.kill(sig, wait_thread.pid)
|
664
|
+
end
|
665
|
+
end
|
666
|
+
end
|
667
|
+
status = wait_thread.value
|
668
|
+
pid = status.pid
|
669
|
+
if status.exited?
|
670
|
+
code = status.exitstatus
|
671
|
+
message("Process #{pid} exited with status code #{code}")
|
672
|
+
elsif status.signaled?
|
673
|
+
signame = Signal.signame(status.termsig)
|
674
|
+
message("Process #{pid} was killed by #{signame}")
|
675
|
+
else
|
676
|
+
message("Process #{pid} exited")
|
677
|
+
end
|
678
|
+
end
|
679
|
+
ensure
|
680
|
+
buffer.read_only = true
|
681
|
+
end
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
class Quit < StandardError
|
686
|
+
def initialize
|
687
|
+
super("Quit")
|
688
|
+
end
|
689
|
+
end
|
690
|
+
end
|