textbringer 0.1.8 → 0.1.9

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