textbringer 0.1.0
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 +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,277 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Textbringer
|
4
|
+
module Utils
|
5
|
+
def message(msg, log: true, sit_for: nil, sleep_for: nil)
|
6
|
+
if log && Buffer.current.name != "*Messages*"
|
7
|
+
buffer = Buffer["*Messages*"] ||
|
8
|
+
Buffer.new_buffer("*Messages*", undo_limit: 0).tap { |b|
|
9
|
+
b[:top_of_window] = b.new_mark
|
10
|
+
}
|
11
|
+
buffer.read_only = false
|
12
|
+
begin
|
13
|
+
buffer.end_of_buffer
|
14
|
+
buffer.insert(msg + "\n")
|
15
|
+
if buffer.current_line > 1000
|
16
|
+
buffer.beginning_of_buffer
|
17
|
+
10.times do
|
18
|
+
buffer.next_line
|
19
|
+
end
|
20
|
+
buffer.delete_region(buffer.point_min, buffer.point)
|
21
|
+
buffer.end_of_buffer
|
22
|
+
end
|
23
|
+
ensure
|
24
|
+
buffer.read_only = true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
Window.echo_area.show(msg)
|
28
|
+
if sit_for
|
29
|
+
sit_for(sit_for)
|
30
|
+
Window.echo_area.clear_message
|
31
|
+
end
|
32
|
+
if sleep_for
|
33
|
+
sleep_for(sleep_for)
|
34
|
+
Window.echo_area.clear_message
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def sit_for(secs, no_redisplay = false)
|
39
|
+
Window.redisplay unless no_redisplay
|
40
|
+
Controller.current.wait_input((secs * 1000).to_i)
|
41
|
+
end
|
42
|
+
|
43
|
+
def sleep_for(secs)
|
44
|
+
sleep(secs)
|
45
|
+
end
|
46
|
+
|
47
|
+
def read_char
|
48
|
+
Controller.current.read_char
|
49
|
+
end
|
50
|
+
|
51
|
+
def received_keyboard_quit?
|
52
|
+
Controller.current.received_keyboard_quit?
|
53
|
+
end
|
54
|
+
|
55
|
+
def handle_exception(e)
|
56
|
+
if e.is_a?(SystemExit)
|
57
|
+
raise
|
58
|
+
end
|
59
|
+
if Buffer.current.name != "*Backtrace*"
|
60
|
+
buffer = Buffer.find_or_new("*Backtrace*", undo_limit: 0)
|
61
|
+
if !buffer.mode.is_a?(BacktraceMode)
|
62
|
+
buffer.apply_mode(BacktraceMode)
|
63
|
+
end
|
64
|
+
buffer.read_only = false
|
65
|
+
begin
|
66
|
+
buffer.delete_region(buffer.point_min, buffer.point_max)
|
67
|
+
buffer.insert("#{e.class}: #{e}\n")
|
68
|
+
e.backtrace.each do |line|
|
69
|
+
buffer.insert(line + "\n")
|
70
|
+
end
|
71
|
+
buffer.beginning_of_buffer
|
72
|
+
ensure
|
73
|
+
buffer.read_only = true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
message(e.to_s.chomp)
|
77
|
+
Window.beep
|
78
|
+
end
|
79
|
+
|
80
|
+
def read_from_minibuffer(prompt, completion_proc: nil, default: nil,
|
81
|
+
keymap: MINIBUFFER_LOCAL_MAP)
|
82
|
+
if Window.echo_area.active?
|
83
|
+
raise EditorError,
|
84
|
+
"Command attempted to use minibuffer while in minibuffer"
|
85
|
+
end
|
86
|
+
old_buffer = Buffer.current
|
87
|
+
old_window = Window.current
|
88
|
+
old_completion_proc = Buffer.minibuffer[:completion_proc]
|
89
|
+
old_current_prefix_arg = Controller.current.current_prefix_arg
|
90
|
+
old_minibuffer_map = Buffer.minibuffer.keymap
|
91
|
+
Buffer.minibuffer.keymap = keymap
|
92
|
+
Buffer.minibuffer[:completion_proc] = completion_proc
|
93
|
+
Window.echo_area.active = true
|
94
|
+
begin
|
95
|
+
Buffer.minibuffer.delete_region(Buffer.minibuffer.point_min,
|
96
|
+
Buffer.minibuffer.point_max)
|
97
|
+
Window.current = Window.echo_area
|
98
|
+
if default
|
99
|
+
prompt = prompt.sub(/:/, " (default #{default}):")
|
100
|
+
end
|
101
|
+
Window.echo_area.prompt = prompt
|
102
|
+
Window.echo_area.redisplay
|
103
|
+
Window.update
|
104
|
+
recursive_edit
|
105
|
+
s = Buffer.minibuffer.to_s.chomp
|
106
|
+
if default && s.empty?
|
107
|
+
default
|
108
|
+
else
|
109
|
+
s
|
110
|
+
end
|
111
|
+
ensure
|
112
|
+
Window.echo_area.clear
|
113
|
+
Window.echo_area.redisplay
|
114
|
+
Window.update
|
115
|
+
Window.echo_area.active = false
|
116
|
+
Window.current = old_window
|
117
|
+
# Just in case old_window has been deleted by resize,
|
118
|
+
# in which case Window.current is set to the first window.
|
119
|
+
Window.current.buffer = Buffer.current = old_buffer
|
120
|
+
Buffer.minibuffer[:completion_proc] = old_completion_proc
|
121
|
+
Buffer.minibuffer.keymap = old_minibuffer_map
|
122
|
+
Controller.current.current_prefix_arg = old_current_prefix_arg
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def read_file_name(prompt, default: nil)
|
127
|
+
f = ->(s) {
|
128
|
+
s = File.expand_path(s) if s.start_with?("~")
|
129
|
+
files = Dir.glob(s + "*")
|
130
|
+
if files.size > 0
|
131
|
+
x, *xs = files
|
132
|
+
file = x.size.downto(1).lazy.map { |i|
|
133
|
+
x[0, i]
|
134
|
+
}.find { |i|
|
135
|
+
xs.all? { |j| j.start_with?(i) }
|
136
|
+
}
|
137
|
+
if file && files.size == 1 &&
|
138
|
+
File.directory?(file) && !file.end_with?(?/)
|
139
|
+
file + "/"
|
140
|
+
else
|
141
|
+
file
|
142
|
+
end
|
143
|
+
else
|
144
|
+
nil
|
145
|
+
end
|
146
|
+
}
|
147
|
+
file = read_from_minibuffer(prompt, completion_proc: f, default: default)
|
148
|
+
File.expand_path(file)
|
149
|
+
end
|
150
|
+
|
151
|
+
def complete(s, candidates)
|
152
|
+
xs = candidates.select { |i| i.start_with?(s) }
|
153
|
+
if xs.size > 0
|
154
|
+
y, *ys = xs
|
155
|
+
y.size.downto(1).lazy.map { |i|
|
156
|
+
y[0, i]
|
157
|
+
}.find { |i|
|
158
|
+
ys.all? { |j| j.start_with?(i) }
|
159
|
+
}
|
160
|
+
else
|
161
|
+
nil
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def read_buffer(prompt, default: (Buffer.last || Buffer.current)&.name)
|
166
|
+
f = ->(s) { complete(s, Buffer.names) }
|
167
|
+
read_from_minibuffer(prompt, completion_proc: f, default: default)
|
168
|
+
end
|
169
|
+
|
170
|
+
def read_command_name(prompt)
|
171
|
+
f = ->(s) {
|
172
|
+
complete(s.tr("-", "_"), Commands.list.map(&:to_s))
|
173
|
+
}
|
174
|
+
read_from_minibuffer(prompt, completion_proc: f)
|
175
|
+
end
|
176
|
+
|
177
|
+
def yes_or_no?(prompt)
|
178
|
+
loop {
|
179
|
+
s = read_from_minibuffer(prompt + " (yes or no) ")
|
180
|
+
case s
|
181
|
+
when "yes"
|
182
|
+
return true
|
183
|
+
when "no"
|
184
|
+
return false
|
185
|
+
else
|
186
|
+
message("Please answer yes or no.", sit_for: 2)
|
187
|
+
end
|
188
|
+
}
|
189
|
+
end
|
190
|
+
|
191
|
+
Y_OR_N_MAP = Keymap.new
|
192
|
+
Y_OR_N_MAP.define_key(?y, :self_insert_and_exit_minibuffer)
|
193
|
+
Y_OR_N_MAP.define_key(?n, :self_insert_and_exit_minibuffer)
|
194
|
+
Y_OR_N_MAP.define_key(?\C-g, :abort_recursive_edit)
|
195
|
+
Y_OR_N_MAP.handle_undefined_key do |key|
|
196
|
+
:exit_recursive_edit
|
197
|
+
end
|
198
|
+
|
199
|
+
def self_insert_and_exit_minibuffer
|
200
|
+
self_insert
|
201
|
+
exit_recursive_edit
|
202
|
+
end
|
203
|
+
|
204
|
+
def y_or_n?(prompt)
|
205
|
+
new_prompt = prompt + " (y or n) "
|
206
|
+
prompt_modified = false
|
207
|
+
loop do
|
208
|
+
s = read_from_minibuffer(new_prompt, keymap: Y_OR_N_MAP)
|
209
|
+
case s
|
210
|
+
when ?y
|
211
|
+
break true
|
212
|
+
when ?n
|
213
|
+
break false
|
214
|
+
else
|
215
|
+
unless prompt_modified
|
216
|
+
new_prompt.prepend("Answer y or n. ")
|
217
|
+
prompt_modified = true
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def read_single_char(prompt, chars)
|
224
|
+
map = Keymap.new
|
225
|
+
chars.each do |c|
|
226
|
+
map.define_key(c, :self_insert_and_exit_minibuffer)
|
227
|
+
end
|
228
|
+
map.define_key(?\C-g, :abort_recursive_edit)
|
229
|
+
char_options = chars.join(?/)
|
230
|
+
map.handle_undefined_key do |key|
|
231
|
+
-> { message("Invalid key. Type C-g to quit.", sit_for: 2) }
|
232
|
+
end
|
233
|
+
read_from_minibuffer(prompt + " (#{char_options}) ", keymap: map)
|
234
|
+
end
|
235
|
+
|
236
|
+
HOOKS = Hash.new { |h, k| h[k] = [] }
|
237
|
+
|
238
|
+
def add_hook(name, func)
|
239
|
+
HOOKS[name].unshift(func)
|
240
|
+
end
|
241
|
+
|
242
|
+
def remove_hook(name, func)
|
243
|
+
HOOKS[name].delete(func)
|
244
|
+
end
|
245
|
+
|
246
|
+
def run_hooks(name, remove_on_error: false)
|
247
|
+
HOOKS[name].delete_if do |func|
|
248
|
+
begin
|
249
|
+
case func
|
250
|
+
when Symbol
|
251
|
+
send(func)
|
252
|
+
else
|
253
|
+
func.call
|
254
|
+
end
|
255
|
+
false
|
256
|
+
rescue Exception => e
|
257
|
+
raise if e.is_a?(SystemExit)
|
258
|
+
if remove_on_error
|
259
|
+
true
|
260
|
+
else
|
261
|
+
raise
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def set_transient_map(map)
|
268
|
+
old_overriding_map = Controller.current.overriding_map
|
269
|
+
hook = -> {
|
270
|
+
Controller.current.overriding_map = old_overriding_map
|
271
|
+
remove_hook(:pre_command_hook, hook)
|
272
|
+
}
|
273
|
+
add_hook(:pre_command_hook, hook)
|
274
|
+
Controller.current.overriding_map = map
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|