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.
@@ -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
@@ -0,0 +1,3 @@
1
+ module Textbringer
2
+ VERSION = "0.1.0"
3
+ end