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.
@@ -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 = Window.current.read_char
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
- Window.echo_area.show("#{keys} is undefined")
63
+ message("#{keys} is undefined")
64
64
  end
65
65
  end
66
66
  rescue Exception => e
67
- handle_exception(e)
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 = Window.current.read_char_nonblock
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 Integer
107
- if key < 0x80
108
- s = Curses.keyname(key)
109
- case s
110
- when /\AKEY_(.*)/
111
- "<#{$1.downcase}>"
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) ||