textbringer 0.1.8 → 0.1.9

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.
@@ -55,9 +55,10 @@ module Textbringer
55
55
  when ?n
56
56
  # do nothing
57
57
  when ?!
58
- replace_match(to_str)
59
- n += 1 + Buffer.current.replace_regexp_forward(regexp, to_str)
60
- Buffer.current.merge_undo(2)
58
+ Buffer.current.composite_edit do
59
+ replace_match(to_str)
60
+ n += 1 + Buffer.current.replace_regexp_forward(regexp, to_str)
61
+ end
61
62
  break
62
63
  when ?q
63
64
  break
@@ -2,63 +2,90 @@
2
2
 
3
3
  module Textbringer
4
4
  module Commands
5
- define_command(:resize_window) do
5
+ define_command(:resize_window,
6
+ doc: "Resize windows to fit the terminal size.") do
6
7
  Window.resize
7
8
  end
8
9
 
9
- define_command(:recenter) do
10
+ define_command(:recenter,
11
+ doc: "Center point in the current window.") do
10
12
  Window.current.recenter
11
13
  Window.redraw
12
14
  end
13
15
 
14
- define_command(:scroll_up) do
16
+ define_command(:scroll_up,
17
+ doc: "Scroll text of the current window upward.") do
15
18
  Window.current.scroll_up
16
19
  end
17
20
 
18
- define_command(:scroll_down) do
21
+ define_command(:scroll_down,
22
+ doc: "Scroll text of the current window downward.") do
19
23
  Window.current.scroll_down
20
24
  end
21
25
 
22
- define_command(:delete_window) do
26
+ define_command(:delete_window,
27
+ doc: "Delete the current window.") do
23
28
  Window.delete_window
24
29
  end
25
30
 
26
- define_command(:delete_other_windows) do
31
+ define_command(:delete_other_windows,
32
+ doc: "Delete windows other than the current one.") do
27
33
  Window.delete_other_windows
28
34
  end
29
35
 
30
- define_command(:split_window) do
36
+ define_command(:split_window,
37
+ doc: "Split the current window vertically.") do
31
38
  Window.current.split
32
39
  end
33
40
 
34
- define_command(:other_window) do
41
+ define_command(:other_window,
42
+ doc: "Switch to another window.") do
35
43
  Window.other_window
36
44
  end
37
45
 
38
- define_command(:enlarge_window) do |n = number_prefix_arg|
46
+ define_command(:enlarge_window, doc: <<~EOD) do
47
+ Make the current window n lines taller.
48
+
49
+ If n is negative, shrink the window -n lines.
50
+ See [shrink_window] for details.
51
+ EOD
52
+ |n = number_prefix_arg|
39
53
  Window.current.enlarge(n)
40
54
  end
41
55
 
42
- define_command(:switch_to_buffer) do
43
- |buffer_name = read_buffer("Switch to buffer: ")|
44
- if buffer_name.is_a?(Buffer)
45
- buffer = buffer_name
46
- else
47
- buffer = Buffer[buffer_name]
56
+ define_command(:shrink_window, doc: <<~EOD) do |n = number_prefix_arg|
57
+ Make the current window n lines smaller.
58
+
59
+ If n is negative, enlarge the window -n lines.
60
+ See [enlarge_window] for details.
61
+ EOD
62
+ Window.current.shrink(n)
63
+ end
64
+
65
+ define_command(:shrink_window_if_larger_than_buffer, doc: <<~EOD) do
66
+ Shrink the current window if it's larger than the buffer.
67
+ EOD
68
+ Window.current.shrink_if_larger_than_buffer
69
+ end
70
+
71
+ define_command(:switch_to_buffer, doc: <<~EOD) do
72
+ Display buffer in the current window.
73
+ EOD
74
+ |buffer = read_buffer("Switch to buffer: ")|
75
+ if buffer.is_a?(String)
76
+ buffer = Buffer[buffer]
48
77
  end
49
78
  if buffer
50
79
  Window.current.buffer = Buffer.current = buffer
51
80
  else
52
- raise EditorError, "No such buffer: #{buffer_name}"
81
+ raise EditorError, "No such buffer: #{buffer}"
53
82
  end
54
83
  end
55
84
 
56
- define_command(:kill_buffer) do
57
- |name = read_buffer("Kill buffer: ", default: Buffer.current.name)|
58
- if name.is_a?(Buffer)
59
- buffer = name
60
- else
61
- buffer = Buffer[name]
85
+ define_command(:kill_buffer, doc: "Kill buffer.") do
86
+ |buffer = read_buffer("Kill buffer: ", default: Buffer.current.name)|
87
+ if buffer.is_a?(String)
88
+ buffer = Buffer[buffer]
62
89
  end
63
90
  if buffer.modified?
64
91
  next unless yes_or_no?("The last change is not saved; kill anyway?")
@@ -9,7 +9,8 @@ module Textbringer
9
9
  indent_tabs_mode: false,
10
10
  case_fold_search: true,
11
11
  buffer_dump_dir: File.expand_path("~/.textbringer/buffer_dump"),
12
- tag_mark_limit: 16,
12
+ mark_ring_max: 16,
13
+ global_mark_ring_max: 16,
13
14
  window_min_height: 4,
14
15
  syntax_highlight: true,
15
16
  highlight_buffer_size_limit: 102400,
@@ -5,9 +5,11 @@ module Textbringer
5
5
  RECURSIVE_EDIT_TAG = Object.new
6
6
 
7
7
  class Controller
8
+ attr_reader :this_command_keys
8
9
  attr_accessor :this_command, :last_command, :overriding_map
9
10
  attr_accessor :prefix_arg, :current_prefix_arg
10
11
  attr_reader :key_sequence, :last_key, :recursive_edit_level
12
+ attr_reader :last_keyboard_macro
11
13
 
12
14
  @@current = nil
13
15
 
@@ -20,15 +22,20 @@ module Textbringer
20
22
  end
21
23
 
22
24
  def initialize
25
+ @top_self = eval("self", TOPLEVEL_BINDING)
23
26
  @key_sequence = []
24
27
  @last_key = nil
25
28
  @recursive_edit_level = 0
29
+ @this_command_keys = nil
26
30
  @this_command = nil
27
31
  @last_command = nil
28
32
  @overriding_map = nil
29
33
  @prefix_arg = nil
30
34
  @current_prefix_arg = nil
31
35
  @echo_immediately = false
36
+ @recording_keyboard_macro = nil
37
+ @last_keyboard_macro = nil
38
+ @executing_keyboard_macro = nil
32
39
  end
33
40
 
34
41
  def command_loop(tag)
@@ -43,14 +50,15 @@ module Textbringer
43
50
  @key_sequence << @last_key
44
51
  cmd = key_binding(@key_sequence)
45
52
  if cmd.is_a?(Symbol) || cmd.respond_to?(:call)
46
- @key_sequence.clear
53
+ @this_command_keys = @key_sequence
54
+ @key_sequence = []
47
55
  @this_command = cmd
48
56
  @current_prefix_arg = @prefix_arg
49
57
  @prefix_arg = nil
50
58
  begin
51
59
  run_hooks(:pre_command_hook, remove_on_error: true)
52
60
  if cmd.is_a?(Symbol)
53
- send(cmd)
61
+ @top_self.send(cmd)
54
62
  else
55
63
  cmd.call
56
64
  end
@@ -61,31 +69,41 @@ module Textbringer
61
69
  end
62
70
  else
63
71
  if cmd.nil?
64
- keys = @key_sequence.map { |ch| key_name(ch) }.join(" ")
72
+ keys = Keymap.key_sequence_string(@key_sequence)
65
73
  @key_sequence.clear
66
74
  @prefix_arg = nil
67
75
  message("#{keys} is undefined")
68
76
  end
69
77
  end
78
+ Window.redisplay
70
79
  rescue Exception => e
71
80
  show_exception(e)
72
81
  @prefix_arg = nil
82
+ @recording_keyboard_macro = nil
83
+ Window.redisplay
84
+ if Window.echo_area.active?
85
+ wait_input(2000)
86
+ Window.echo_area.clear_message
87
+ Window.redisplay
88
+ end
73
89
  end
74
- Window.redisplay
75
90
  end
76
91
  end
77
92
  end
78
93
 
79
94
  def wait_input(msecs)
95
+ if executing_keyboard_macro?
96
+ return @executing_keyboard_macro.first
97
+ end
80
98
  Window.current.wait_input(msecs)
81
99
  end
82
100
 
83
101
  def read_char
84
- Window.current.read_char
102
+ read_char_with_keyboard_macro(:read_char)
85
103
  end
86
104
 
87
105
  def read_char_nonblock
88
- Window.current.read_char_nonblock
106
+ read_char_with_keyboard_macro(:read_char_nonblock)
89
107
  end
90
108
 
91
109
  def received_keyboard_quit?
@@ -108,22 +126,8 @@ module Textbringer
108
126
  end
109
127
  end
110
128
 
111
- def key_name(key)
112
- case key
113
- when Symbol
114
- "<#{key}>"
115
- when "\e"
116
- "ESC"
117
- when "\C-m"
118
- "RET"
119
- when /\A[\0-\b\v-\x1f\x7f]\z/
120
- "C-" + (key.ord ^ 0x40).chr.downcase
121
- else
122
- key.to_s
123
- end
124
- end
125
-
126
129
  def echo_input
130
+ return if executing_keyboard_macro?
127
131
  if @prefix_arg || !@key_sequence.empty?
128
132
  if !@echo_immediately
129
133
  return if wait_input(1000)
@@ -138,7 +142,7 @@ module Textbringer
138
142
  end
139
143
  if !@key_sequence.empty?
140
144
  s << " " if !s.empty?
141
- s << @key_sequence.map { |ch| key_name(ch) }.join(" ")
145
+ s << Keymap.key_sequence_string(@key_sequence)
142
146
  end
143
147
  s << "-"
144
148
  Window.echo_area.show(s)
@@ -150,12 +154,74 @@ module Textbringer
150
154
  end
151
155
  end
152
156
 
153
- private
157
+ def start_keyboard_macro
158
+ if @recording_keyboard_macro
159
+ @recording_keyboard_macro = nil
160
+ raise EditorError, "Already recording keyboard macro"
161
+ end
162
+ @recording_keyboard_macro = []
163
+ end
164
+
165
+ def end_keyboard_macro
166
+ if @recording_keyboard_macro.nil?
167
+ raise EditorError, "Not recording keyboard macro"
168
+ end
169
+ if @recording_keyboard_macro.empty?
170
+ raise EditorError, "Empty keyboard macro"
171
+ end
172
+ @recording_keyboard_macro.pop(@this_command_keys.size)
173
+ @last_keyboard_macro = @recording_keyboard_macro
174
+ @recording_keyboard_macro = nil
175
+ end
176
+
177
+ def execute_keyboard_macro(macro, n = 1)
178
+ n.times do
179
+ @executing_keyboard_macro = macro.dup
180
+ begin
181
+ recursive_edit
182
+ ensure
183
+ @executing_keyboard_macro = nil
184
+ end
185
+ end
186
+ end
187
+
188
+ def call_last_keyboard_macro(n)
189
+ if @last_keyboard_macro.nil?
190
+ raise EditorError, "Keyboard macro not defined"
191
+ end
192
+ execute_keyboard_macro(@last_keyboard_macro, n)
193
+ end
194
+
195
+ def recording_keyboard_macro?
196
+ !@recording_keyboard_macro.nil?
197
+ end
198
+
199
+ def executing_keyboard_macro?
200
+ !@executing_keyboard_macro.nil?
201
+ end
154
202
 
155
203
  def key_binding(key_sequence)
156
204
  @overriding_map&.lookup(key_sequence) ||
157
205
  Buffer.current&.keymap&.lookup(key_sequence) ||
158
206
  GLOBAL_MAP.lookup(key_sequence)
159
207
  end
208
+
209
+ private
210
+
211
+ def read_char_with_keyboard_macro(read_char_method)
212
+ if !executing_keyboard_macro?
213
+ c = call_read_char_method(read_char_method)
214
+ if @recording_keyboard_macro
215
+ @recording_keyboard_macro.push(c)
216
+ end
217
+ c
218
+ else
219
+ @executing_keyboard_macro.shift
220
+ end
221
+ end
222
+
223
+ def call_read_char_method(read_char_method)
224
+ Window.current.send(read_char_method)
225
+ end
160
226
  end
161
227
  end
@@ -4,6 +4,8 @@ require "curses"
4
4
 
5
5
  module Textbringer
6
6
  class Keymap
7
+ include Enumerable
8
+
7
9
  def initialize
8
10
  @map = {}
9
11
  end
@@ -35,10 +37,43 @@ module Textbringer
35
37
  end
36
38
  end
37
39
 
40
+ def each(prefixes = [], &block)
41
+ @map.each do |key, val|
42
+ if val.is_a?(Keymap)
43
+ val.each([*prefixes, key], &block)
44
+ else
45
+ yield([*prefixes, key], val)
46
+ end
47
+ end
48
+ end
49
+
38
50
  def handle_undefined_key
39
51
  @map.default_proc = Proc.new { |h, k| yield(k) }
40
52
  end
41
53
 
54
+ def self.key_name(key)
55
+ case key
56
+ when Symbol
57
+ "<#{key}>"
58
+ when " "
59
+ "SPC"
60
+ when "\t"
61
+ "TAB"
62
+ when "\e"
63
+ "ESC"
64
+ when "\C-m"
65
+ "RET"
66
+ when /\A[\0-\x1f\x7f]\z/
67
+ "C-" + (key.ord ^ 0x40).chr.downcase
68
+ else
69
+ key.to_s
70
+ end
71
+ end
72
+
73
+ def self.key_sequence_string(key_sequence)
74
+ key_sequence.map { |key| key_name(key) }.join(" ")
75
+ end
76
+
42
77
  private
43
78
 
44
79
  def kbd(key)
@@ -85,7 +120,10 @@ module Textbringer
85
120
  end
86
121
  GLOBAL_MAP.define_key(?\t, :self_insert)
87
122
  GLOBAL_MAP.define_key(?\C-q, :quoted_insert)
88
- GLOBAL_MAP.define_key("\C- ", :set_mark_command)
123
+ GLOBAL_MAP.define_key("\C-@", :set_mark_command)
124
+ GLOBAL_MAP.define_key("\C-x\C-@", :pop_global_mark)
125
+ GLOBAL_MAP.define_key("\e*", :next_global_mark)
126
+ GLOBAL_MAP.define_key("\e?", :previous_global_mark)
89
127
  GLOBAL_MAP.define_key("\C-x\C-x", :exchange_point_and_mark)
90
128
  GLOBAL_MAP.define_key("\ew", :copy_region)
91
129
  GLOBAL_MAP.define_key(?\C-w, :kill_region)
@@ -94,10 +132,12 @@ module Textbringer
94
132
  GLOBAL_MAP.define_key(?\C-y, :yank)
95
133
  GLOBAL_MAP.define_key("\ey", :yank_pop)
96
134
  GLOBAL_MAP.define_key(?\C-_, :undo)
97
- GLOBAL_MAP.define_key("\C-x\C-_", :redo)
135
+ GLOBAL_MAP.define_key("\C-x\C-_", :redo_command)
98
136
  GLOBAL_MAP.define_key("\C-t", :transpose_chars)
99
137
  GLOBAL_MAP.define_key("\C-j", :newline)
100
138
  GLOBAL_MAP.define_key("\C-m", :newline)
139
+ GLOBAL_MAP.define_key("\em", :back_to_indentation)
140
+ GLOBAL_MAP.define_key("\e^", :delete_indentation)
101
141
  GLOBAL_MAP.define_key("\C-l", :recenter)
102
142
  GLOBAL_MAP.define_key("\C-v", :scroll_up)
103
143
  GLOBAL_MAP.define_key(:npage, :scroll_up)
@@ -108,6 +148,7 @@ module Textbringer
108
148
  GLOBAL_MAP.define_key("\C-x2", :split_window)
109
149
  GLOBAL_MAP.define_key("\C-xo", :other_window)
110
150
  GLOBAL_MAP.define_key("\C-x^", :enlarge_window)
151
+ GLOBAL_MAP.define_key("\C-x-", :shrink_window_if_larger_than_buffer)
111
152
  GLOBAL_MAP.define_key("\C-x\C-c", :exit_textbringer)
112
153
  GLOBAL_MAP.define_key("\C-z", :suspend_textbringer)
113
154
  GLOBAL_MAP.define_key("\C-x\C-f", :find_file)
@@ -117,6 +158,7 @@ module Textbringer
117
158
  GLOBAL_MAP.define_key("\C-xk", :kill_buffer)
118
159
  GLOBAL_MAP.define_key("\C-x\C-mf", :set_buffer_file_encoding)
119
160
  GLOBAL_MAP.define_key("\C-x\C-mn", :set_buffer_file_format)
161
+ GLOBAL_MAP.define_key("\e.", :find_tag)
120
162
  GLOBAL_MAP.define_key("\ex", :execute_command)
121
163
  GLOBAL_MAP.define_key("\e:", :eval_expression)
122
164
  GLOBAL_MAP.define_key(?\C-u, :universal_argument)
@@ -125,6 +167,22 @@ module Textbringer
125
167
  GLOBAL_MAP.define_key(?\C-r, :isearch_backward)
126
168
  GLOBAL_MAP.define_key("\e%", :query_replace_regexp)
127
169
  GLOBAL_MAP.define_key("\e!", :shell_execute)
170
+ GLOBAL_MAP.define_key("\C-xr ", :point_to_register)
171
+ GLOBAL_MAP.define_key("\C-xrj", :jump_to_register)
172
+ GLOBAL_MAP.define_key("\C-xrx", :copy_to_register)
173
+ GLOBAL_MAP.define_key("\C-xrs", :copy_to_register)
174
+ GLOBAL_MAP.define_key("\C-xrg", :insert_register)
175
+ GLOBAL_MAP.define_key("\C-xri", :insert_register)
176
+ GLOBAL_MAP.define_key("\C-xrn", :number_to_register)
177
+ GLOBAL_MAP.define_key("\C-xr+", :increment_register)
178
+ GLOBAL_MAP.define_key("\C-x(", :start_keyboard_macro)
179
+ GLOBAL_MAP.define_key(:f3, :start_keyboard_macro)
180
+ GLOBAL_MAP.define_key("\C-x)", :end_keyboard_macro)
181
+ GLOBAL_MAP.define_key("\C-xe", :end_and_call_keyboard_macro)
182
+ GLOBAL_MAP.define_key(:f4, :end_or_call_keyboard_macro)
183
+ GLOBAL_MAP.define_key([:f1, "b"], :describe_bindings)
184
+ GLOBAL_MAP.define_key([:f1, "f"], :describe_command)
185
+ GLOBAL_MAP.define_key([:f1, "k"], :describe_key)
128
186
  GLOBAL_MAP.handle_undefined_key do |key|
129
187
  if key.is_a?(String) && /[\0-\x7f]/ !~ key
130
188
  :self_insert