textbringer 0.1.4 → 0.1.5
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.travis.yml +1 -1
- data/CHANGES.md +6 -0
- data/README.md +1 -1
- data/lib/textbringer.rb +12 -1
- data/lib/textbringer/buffer.rb +7 -2
- data/lib/textbringer/commands.rb +0 -677
- data/lib/textbringer/commands/buffers.rb +101 -0
- data/lib/textbringer/commands/clipboard.rb +35 -0
- data/lib/textbringer/commands/ctags.rb +14 -7
- data/lib/textbringer/commands/dabbrev.rb +39 -32
- data/lib/textbringer/commands/files.rb +76 -0
- data/lib/textbringer/commands/isearch.rb +144 -0
- data/lib/textbringer/commands/misc.rb +240 -0
- data/lib/textbringer/commands/replace.rb +82 -0
- data/lib/textbringer/commands/windows.rb +72 -0
- data/lib/textbringer/controller.rb +17 -19
- data/lib/textbringer/errors.rb +6 -0
- data/lib/textbringer/modes/ruby_mode.rb +73 -53
- data/lib/textbringer/utils.rb +21 -7
- data/lib/textbringer/version.rb +1 -1
- data/lib/textbringer/window.rb +27 -9
- data/textbringer.gemspec +6 -3
- metadata +57 -8
- data/lib/textbringer/modes.rb +0 -7
@@ -0,0 +1,240 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Textbringer
|
4
|
+
module Commands
|
5
|
+
define_command(:version) do
|
6
|
+
message("Textbringer #{Textbringer::VERSION} "\
|
7
|
+
"(ruby #{RUBY_VERSION} [#{RUBY_PLATFORM}])")
|
8
|
+
end
|
9
|
+
|
10
|
+
define_command(:exit_textbringer) do |status = 0|
|
11
|
+
if Buffer.any? { |buffer| /\A\*/ !~ buffer.name && buffer.modified? }
|
12
|
+
return unless yes_or_no?("Unsaved buffers exist; exit anyway?")
|
13
|
+
end
|
14
|
+
exit(status)
|
15
|
+
end
|
16
|
+
|
17
|
+
define_command(:suspend_textbringer) do
|
18
|
+
Curses.close_screen
|
19
|
+
Process.kill(:STOP, $$)
|
20
|
+
end
|
21
|
+
|
22
|
+
define_command(:execute_command) do
|
23
|
+
|cmd = read_command_name("M-x ").strip.intern|
|
24
|
+
unless Commands.list.include?(cmd)
|
25
|
+
raise EditorError, "Undefined command: #{cmd}"
|
26
|
+
end
|
27
|
+
Controller.current.this_command = cmd
|
28
|
+
send(cmd)
|
29
|
+
end
|
30
|
+
|
31
|
+
define_command(:eval_expression) do
|
32
|
+
|s = read_from_minibuffer("Eval: ")|
|
33
|
+
result = eval(s, TOPLEVEL_BINDING, "(eval_expression)", 1)
|
34
|
+
message(result.inspect)
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
38
|
+
define_command(:eval_buffer) do
|
39
|
+
buffer = Buffer.current
|
40
|
+
result = eval(buffer.to_s, TOPLEVEL_BINDING,
|
41
|
+
buffer.file_name || buffer.name, 1)
|
42
|
+
message(result.inspect)
|
43
|
+
result
|
44
|
+
end
|
45
|
+
|
46
|
+
define_command(:eval_region) do
|
47
|
+
buffer = Buffer.current
|
48
|
+
b, e = buffer.point, buffer.mark
|
49
|
+
if e < b
|
50
|
+
b, e = e, b
|
51
|
+
end
|
52
|
+
result = eval(buffer.substring(b, e), TOPLEVEL_BINDING,
|
53
|
+
"(eval_region)", 1)
|
54
|
+
message(result.inspect)
|
55
|
+
result
|
56
|
+
end
|
57
|
+
|
58
|
+
define_command(:exit_recursive_edit) do
|
59
|
+
if Controller.current.recursive_edit_level == 0
|
60
|
+
raise EditorError, "No recursive edit is in progress"
|
61
|
+
end
|
62
|
+
throw RECURSIVE_EDIT_TAG, false
|
63
|
+
end
|
64
|
+
|
65
|
+
define_command(:abort_recursive_edit) do
|
66
|
+
if Controller.current.recursive_edit_level == 0
|
67
|
+
raise EditorError, "No recursive edit is in progress"
|
68
|
+
end
|
69
|
+
throw RECURSIVE_EDIT_TAG, true
|
70
|
+
end
|
71
|
+
|
72
|
+
define_command(:top_level) do
|
73
|
+
throw TOP_LEVEL_TAG
|
74
|
+
end
|
75
|
+
|
76
|
+
define_command(:keyboard_quit) do
|
77
|
+
raise Quit
|
78
|
+
end
|
79
|
+
|
80
|
+
define_command(:complete_minibuffer) do
|
81
|
+
minibuffer = Buffer.minibuffer
|
82
|
+
completion_proc = minibuffer[:completion_proc]
|
83
|
+
if completion_proc
|
84
|
+
s = completion_proc.call(minibuffer.to_s)
|
85
|
+
if s
|
86
|
+
minibuffer.delete_region(minibuffer.point_min,
|
87
|
+
minibuffer.point_max)
|
88
|
+
minibuffer.insert(s)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
UNIVERSAL_ARGUMENT_MAP = Keymap.new
|
94
|
+
(?0..?9).each do |c|
|
95
|
+
UNIVERSAL_ARGUMENT_MAP.define_key(c, :digit_argument)
|
96
|
+
GLOBAL_MAP.define_key("\e#{c}", :digit_argument)
|
97
|
+
end
|
98
|
+
UNIVERSAL_ARGUMENT_MAP.define_key(?-, :negative_argument)
|
99
|
+
UNIVERSAL_ARGUMENT_MAP.define_key(?\C-u, :universal_argument_more)
|
100
|
+
|
101
|
+
def universal_argument_mode
|
102
|
+
set_transient_map(UNIVERSAL_ARGUMENT_MAP)
|
103
|
+
end
|
104
|
+
|
105
|
+
define_command(:universal_argument) do
|
106
|
+
Controller.current.prefix_arg = [4]
|
107
|
+
universal_argument_mode
|
108
|
+
end
|
109
|
+
|
110
|
+
def current_prefix_arg
|
111
|
+
Controller.current.current_prefix_arg
|
112
|
+
end
|
113
|
+
|
114
|
+
def number_prefix_arg
|
115
|
+
arg = current_prefix_arg
|
116
|
+
case arg
|
117
|
+
when Integer
|
118
|
+
arg
|
119
|
+
when Array
|
120
|
+
arg.first
|
121
|
+
when :-
|
122
|
+
-1
|
123
|
+
else
|
124
|
+
1
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
define_command(:digit_argument) do
|
129
|
+
|arg = current_prefix_arg|
|
130
|
+
n = Controller.current.last_key.to_i
|
131
|
+
Controller.current.prefix_arg =
|
132
|
+
case arg
|
133
|
+
when Integer
|
134
|
+
arg * 10 + (arg < 0 ? -n : n)
|
135
|
+
when :-
|
136
|
+
-n
|
137
|
+
else
|
138
|
+
n
|
139
|
+
end
|
140
|
+
universal_argument_mode
|
141
|
+
end
|
142
|
+
|
143
|
+
define_command(:negative_argument) do
|
144
|
+
|arg = current_prefix_arg|
|
145
|
+
Controller.current.prefix_arg =
|
146
|
+
case arg
|
147
|
+
when Integer
|
148
|
+
-arg
|
149
|
+
when :-
|
150
|
+
nil
|
151
|
+
else
|
152
|
+
:-
|
153
|
+
end
|
154
|
+
universal_argument_mode
|
155
|
+
end
|
156
|
+
|
157
|
+
define_command(:universal_argument_more) do
|
158
|
+
|arg = current_prefix_arg|
|
159
|
+
Controller.current.prefix_arg =
|
160
|
+
case arg
|
161
|
+
when Array
|
162
|
+
[4 * arg.first]
|
163
|
+
when :-
|
164
|
+
[-4]
|
165
|
+
else
|
166
|
+
nil
|
167
|
+
end
|
168
|
+
if Controller.current.prefix_arg
|
169
|
+
universal_argument_mode
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
define_command(:recursive_edit) do
|
174
|
+
Controller.current.recursive_edit
|
175
|
+
end
|
176
|
+
|
177
|
+
define_command(:shell_execute) do
|
178
|
+
|cmd = read_from_minibuffer("Shell execute: "),
|
179
|
+
buffer_name = "*Shell output*"|
|
180
|
+
buffer = Buffer.find_or_new(buffer_name)
|
181
|
+
switch_to_buffer(buffer)
|
182
|
+
buffer.read_only = false
|
183
|
+
buffer.clear
|
184
|
+
Window.redisplay
|
185
|
+
signals = [:INT, :TERM, :KILL]
|
186
|
+
begin
|
187
|
+
if /mswin32|mingw32/ =~ RUBY_PLATFORM
|
188
|
+
opts = {}
|
189
|
+
else
|
190
|
+
opts = {pgroup: true}
|
191
|
+
end
|
192
|
+
Open3.popen2e(cmd, opts) do |input, output, wait_thread|
|
193
|
+
input.close
|
194
|
+
loop do
|
195
|
+
status = output.wait_readable(0.5)
|
196
|
+
if status == false
|
197
|
+
break # EOF
|
198
|
+
end
|
199
|
+
if status
|
200
|
+
begin
|
201
|
+
s = output.read_nonblock(1024).force_encoding("utf-8").
|
202
|
+
scrub("\u{3013}").gsub(/\r\n/, "\n")
|
203
|
+
buffer.insert(s)
|
204
|
+
Window.redisplay
|
205
|
+
rescue EOFError
|
206
|
+
break
|
207
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
208
|
+
next
|
209
|
+
end
|
210
|
+
end
|
211
|
+
if received_keyboard_quit?
|
212
|
+
if signals.empty?
|
213
|
+
keyboard_quit
|
214
|
+
else
|
215
|
+
sig = signals.shift
|
216
|
+
pid = wait_thread.pid
|
217
|
+
pid = -pid if /mswin32|mingw32/ !~ RUBY_PLATFORM
|
218
|
+
message("Send #{sig} to #{pid}")
|
219
|
+
Process.kill(sig, pid)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
status = wait_thread.value
|
224
|
+
pid = status.pid
|
225
|
+
if status.exited?
|
226
|
+
code = status.exitstatus
|
227
|
+
message("Process #{pid} exited with status code #{code}")
|
228
|
+
elsif status.signaled?
|
229
|
+
signame = Signal.signame(status.termsig)
|
230
|
+
message("Process #{pid} was killed by #{signame}")
|
231
|
+
else
|
232
|
+
message("Process #{pid} exited")
|
233
|
+
end
|
234
|
+
end
|
235
|
+
ensure
|
236
|
+
buffer.read_only = true
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Textbringer
|
4
|
+
module Commands
|
5
|
+
RE_SEARCH_STATUS = {
|
6
|
+
last_regexp: nil
|
7
|
+
}
|
8
|
+
|
9
|
+
define_command(:re_search_forward) do
|
10
|
+
|s = read_from_minibuffer("RE search: ",
|
11
|
+
default: RE_SEARCH_STATUS[:last_regexp])|
|
12
|
+
RE_SEARCH_STATUS[:last_regexp] = s
|
13
|
+
Buffer.current.re_search_forward(s)
|
14
|
+
end
|
15
|
+
|
16
|
+
define_command(:re_search_backward) do
|
17
|
+
|s = read_from_minibuffer("RE search backward: ",
|
18
|
+
default: RE_SEARCH_STATUS[:last_regexp])|
|
19
|
+
RE_SEARCH_STATUS[:last_regexp] = s
|
20
|
+
Buffer.current.re_search_backward(s)
|
21
|
+
end
|
22
|
+
|
23
|
+
def match_beginning(n)
|
24
|
+
Buffer.current.match_beginning(n)
|
25
|
+
end
|
26
|
+
|
27
|
+
def match_end(n)
|
28
|
+
Buffer.current.match_end(n)
|
29
|
+
end
|
30
|
+
|
31
|
+
def match_string(n)
|
32
|
+
Buffer.current.match_string(n)
|
33
|
+
end
|
34
|
+
|
35
|
+
def replace_match(s)
|
36
|
+
Buffer.current.replace_match(s)
|
37
|
+
end
|
38
|
+
|
39
|
+
define_command(:query_replace_regexp) do
|
40
|
+
|regexp = read_from_minibuffer("Query replace regexp: "),
|
41
|
+
to_str = read_from_minibuffer("with: ")|
|
42
|
+
n = 0
|
43
|
+
begin
|
44
|
+
loop do
|
45
|
+
re_search_forward(regexp)
|
46
|
+
Window.current.recenter_if_needed
|
47
|
+
Buffer.current.set_visible_mark(match_beginning(0))
|
48
|
+
begin
|
49
|
+
Window.redisplay
|
50
|
+
c = read_single_char("Replace?", [?y, ?n, ?!, ?q, ?.])
|
51
|
+
case c
|
52
|
+
when ?y
|
53
|
+
replace_match(to_str)
|
54
|
+
n += 1
|
55
|
+
when ?n
|
56
|
+
# do nothing
|
57
|
+
when ?!
|
58
|
+
replace_match(to_str)
|
59
|
+
n += 1 + Buffer.current.replace_regexp_forward(regexp, to_str)
|
60
|
+
Buffer.current.merge_undo(2)
|
61
|
+
break
|
62
|
+
when ?q
|
63
|
+
break
|
64
|
+
when ?.
|
65
|
+
replace_match(to_str)
|
66
|
+
n += 1
|
67
|
+
break
|
68
|
+
end
|
69
|
+
ensure
|
70
|
+
Buffer.current.delete_visible_mark
|
71
|
+
end
|
72
|
+
end
|
73
|
+
rescue SearchError
|
74
|
+
end
|
75
|
+
if n == 1
|
76
|
+
message("Replaced 1 occurrence")
|
77
|
+
else
|
78
|
+
message("Replaced #{n} occurrences")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Textbringer
|
4
|
+
module Commands
|
5
|
+
define_command(:resize_window) do
|
6
|
+
Window.resize
|
7
|
+
end
|
8
|
+
|
9
|
+
define_command(:recenter) do
|
10
|
+
Window.current.recenter
|
11
|
+
Window.redraw
|
12
|
+
end
|
13
|
+
|
14
|
+
define_command(:scroll_up) do
|
15
|
+
Window.current.scroll_up
|
16
|
+
end
|
17
|
+
|
18
|
+
define_command(:scroll_down) do
|
19
|
+
Window.current.scroll_down
|
20
|
+
end
|
21
|
+
|
22
|
+
define_command(:delete_window) do
|
23
|
+
Window.delete_window
|
24
|
+
end
|
25
|
+
|
26
|
+
define_command(:delete_other_windows) do
|
27
|
+
Window.delete_other_windows
|
28
|
+
end
|
29
|
+
|
30
|
+
define_command(:split_window) do
|
31
|
+
Window.current.split
|
32
|
+
end
|
33
|
+
|
34
|
+
define_command(:other_window) do
|
35
|
+
Window.other_window
|
36
|
+
end
|
37
|
+
|
38
|
+
define_command(:switch_to_buffer) do
|
39
|
+
|buffer_name = read_buffer("Switch to buffer: ")|
|
40
|
+
if buffer_name.is_a?(Buffer)
|
41
|
+
buffer = buffer_name
|
42
|
+
else
|
43
|
+
buffer = Buffer[buffer_name]
|
44
|
+
end
|
45
|
+
if buffer
|
46
|
+
Window.current.buffer = Buffer.current = buffer
|
47
|
+
else
|
48
|
+
raise EditorError, "No such buffer: #{buffer_name}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
define_command(:kill_buffer) do
|
53
|
+
|name = read_buffer("Kill buffer: ", default: Buffer.current.name)|
|
54
|
+
if name.is_a?(Buffer)
|
55
|
+
buffer = name
|
56
|
+
else
|
57
|
+
buffer = Buffer[name]
|
58
|
+
end
|
59
|
+
if buffer.modified?
|
60
|
+
next unless yes_or_no?("The last change is not saved; kill anyway?")
|
61
|
+
message("Arioch! Arioch! Blood and souls for my Lord Arioch!")
|
62
|
+
end
|
63
|
+
buffer.kill
|
64
|
+
if Buffer.count == 0
|
65
|
+
buffer = Buffer.new_buffer("*scratch*")
|
66
|
+
switch_to_buffer(buffer)
|
67
|
+
elsif Buffer.current.nil?
|
68
|
+
switch_to_buffer(Buffer.last)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -7,7 +7,7 @@ module Textbringer
|
|
7
7
|
class Controller
|
8
8
|
attr_accessor :this_command, :last_command, :overriding_map
|
9
9
|
attr_accessor :prefix_arg, :current_prefix_arg
|
10
|
-
attr_reader :last_key
|
10
|
+
attr_reader :last_key, :recursive_edit_level
|
11
11
|
|
12
12
|
@@current = nil
|
13
13
|
|
@@ -34,7 +34,7 @@ module Textbringer
|
|
34
34
|
catch(tag) do
|
35
35
|
loop do
|
36
36
|
begin
|
37
|
-
c =
|
37
|
+
c = read_char
|
38
38
|
Window.echo_area.clear_message
|
39
39
|
@last_key = c
|
40
40
|
@key_sequence << @last_key
|
@@ -60,11 +60,11 @@ module Textbringer
|
|
60
60
|
if cmd.nil?
|
61
61
|
keys = @key_sequence.map { |ch| key_name(ch) }.join(" ")
|
62
62
|
@key_sequence.clear
|
63
|
-
|
63
|
+
message("#{keys} is undefined")
|
64
64
|
end
|
65
65
|
end
|
66
66
|
rescue Exception => e
|
67
|
-
|
67
|
+
show_exception(e)
|
68
68
|
end
|
69
69
|
Window.redisplay
|
70
70
|
end
|
@@ -79,8 +79,12 @@ module Textbringer
|
|
79
79
|
Window.current.read_char
|
80
80
|
end
|
81
81
|
|
82
|
+
def read_char_nonblock
|
83
|
+
Window.current.read_char_nonblock
|
84
|
+
end
|
85
|
+
|
82
86
|
def received_keyboard_quit?
|
83
|
-
while key =
|
87
|
+
while key = read_char_nonblock
|
84
88
|
if GLOBAL_MAP.lookup([key]) == :keyboard_quit
|
85
89
|
return true
|
86
90
|
end
|
@@ -99,27 +103,21 @@ module Textbringer
|
|
99
103
|
end
|
100
104
|
end
|
101
105
|
|
102
|
-
private
|
103
|
-
|
104
106
|
def key_name(key)
|
105
107
|
case key
|
106
|
-
when
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
else
|
113
|
-
s
|
114
|
-
end
|
115
|
-
else
|
116
|
-
key.chr(Encoding::UTF_8)
|
117
|
-
end
|
108
|
+
when Symbol
|
109
|
+
"<#{key}>"
|
110
|
+
when "\e"
|
111
|
+
"ESC"
|
112
|
+
when /\A[\0-\b\v-\x1f\x7f]\z/
|
113
|
+
"C-" + (key.ord ^ 0x40).chr.downcase
|
118
114
|
else
|
119
115
|
key.to_s
|
120
116
|
end
|
121
117
|
end
|
122
118
|
|
119
|
+
private
|
120
|
+
|
123
121
|
def key_binding(key_sequence)
|
124
122
|
@overriding_map&.lookup(key_sequence) ||
|
125
123
|
Buffer.current&.keymap&.lookup(key_sequence) ||
|