textbringer 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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) ||