textbringer 0.1.8 → 0.1.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +22 -8
- data/CHANGES.md +11 -0
- data/Guardfile +1 -1
- data/exe/tbtags +26 -0
- data/exe/textbringer +1 -0
- data/lib/textbringer.rb +6 -0
- data/lib/textbringer/buffer.rb +158 -83
- data/lib/textbringer/commands.rb +18 -6
- data/lib/textbringer/commands/buffers.rb +187 -43
- data/lib/textbringer/commands/clipboard.rb +23 -6
- data/lib/textbringer/commands/ctags.rb +3 -23
- data/lib/textbringer/commands/files.rb +10 -7
- data/lib/textbringer/commands/help.rb +114 -0
- data/lib/textbringer/commands/isearch.rb +13 -7
- data/lib/textbringer/commands/keyboard_macro.rb +86 -0
- data/lib/textbringer/commands/misc.rb +43 -2
- data/lib/textbringer/commands/register.rb +128 -0
- data/lib/textbringer/commands/replace.rb +4 -3
- data/lib/textbringer/commands/windows.rb +49 -22
- data/lib/textbringer/config.rb +2 -1
- data/lib/textbringer/controller.rb +89 -23
- data/lib/textbringer/keymap.rb +60 -2
- data/lib/textbringer/modes/help_mode.rb +42 -0
- data/lib/textbringer/modes/programming_mode.rb +35 -28
- data/lib/textbringer/modes/ruby_mode.rb +28 -10
- data/lib/textbringer/plugin.rb +21 -0
- data/lib/textbringer/ring.rb +84 -0
- data/lib/textbringer/utils.rb +28 -0
- data/lib/textbringer/version.rb +1 -1
- data/lib/textbringer/window.rb +59 -5
- data/textbringer.gemspec +1 -1
- metadata +12 -4
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Textbringer
|
4
|
+
module Commands
|
5
|
+
HELP_RING = Ring.new
|
6
|
+
|
7
|
+
def push_help_command(cmd)
|
8
|
+
if HELP_RING.empty? || HELP_RING.current != cmd
|
9
|
+
HELP_RING.push(cmd)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
private :push_help_command
|
13
|
+
|
14
|
+
def show_help
|
15
|
+
help = Buffer.find_or_new("*Help*", undo_limit: 0)
|
16
|
+
help.read_only_edit do
|
17
|
+
help.clear
|
18
|
+
yield(help)
|
19
|
+
help.beginning_of_buffer
|
20
|
+
switch_to_buffer(help)
|
21
|
+
help_mode
|
22
|
+
end
|
23
|
+
end
|
24
|
+
private :show_help
|
25
|
+
|
26
|
+
define_command(:describe_bindings,
|
27
|
+
doc: "Display the key bindings.") do
|
28
|
+
show_help do |help|
|
29
|
+
s = format("%-16s %s\n", "Key", "Binding")
|
30
|
+
s << format("%-16s %s\n", "---", "-------")
|
31
|
+
s << "\n"
|
32
|
+
bindings = {}
|
33
|
+
[
|
34
|
+
GLOBAL_MAP,
|
35
|
+
Buffer.current.keymap,
|
36
|
+
Controller.current.overriding_map
|
37
|
+
].each do |map|
|
38
|
+
map&.each do |key_sequence, command|
|
39
|
+
bindings[key_sequence] = command
|
40
|
+
end
|
41
|
+
end
|
42
|
+
bindings.each do |key_sequence, command|
|
43
|
+
s << format("%-16s [%s]\n",
|
44
|
+
Keymap.key_sequence_string(key_sequence),
|
45
|
+
command)
|
46
|
+
end
|
47
|
+
help.insert(s)
|
48
|
+
end
|
49
|
+
push_help_command([:describe_bindings])
|
50
|
+
end
|
51
|
+
|
52
|
+
def command_help(cmd)
|
53
|
+
s = format("%s:%d\n", *cmd.block.source_location)
|
54
|
+
s << "-" * (Window.columns - 2) + "\n"
|
55
|
+
s << "#{cmd.name}"
|
56
|
+
if !cmd.block.parameters.empty?
|
57
|
+
s << "("
|
58
|
+
s << cmd.block.parameters.map { |_, param| param }.join(", ")
|
59
|
+
s << ")"
|
60
|
+
end
|
61
|
+
s << "\n\n"
|
62
|
+
s << "-" * (Window.columns - 2) + "\n\n"
|
63
|
+
s << cmd.doc
|
64
|
+
s << "\n"
|
65
|
+
s
|
66
|
+
end
|
67
|
+
|
68
|
+
define_command(:describe_command,
|
69
|
+
doc: "Display the documentation of the command.") do
|
70
|
+
|name = read_command_name("Describe command: ")|
|
71
|
+
cmd = Commands[name]
|
72
|
+
if cmd.nil?
|
73
|
+
raise EditorError, "No such command: #{name}"
|
74
|
+
end
|
75
|
+
show_help do |help|
|
76
|
+
help.insert(command_help(cmd))
|
77
|
+
end
|
78
|
+
push_help_command([:describe_command, name])
|
79
|
+
end
|
80
|
+
|
81
|
+
define_command(:describe_key,
|
82
|
+
doc: <<~EOD) do
|
83
|
+
Display the documentation of the command invoked by key.
|
84
|
+
EOD
|
85
|
+
|key = read_key_sequence("Describe key: ")|
|
86
|
+
name = Buffer.current.keymap&.lookup(key) ||
|
87
|
+
GLOBAL_MAP.lookup(key)
|
88
|
+
cmd = Commands[name]
|
89
|
+
show_help do |help|
|
90
|
+
s = Keymap.key_sequence_string(key)
|
91
|
+
s << " runs the command #{name}, which is defined in\n"
|
92
|
+
s << command_help(cmd)
|
93
|
+
help.insert(s)
|
94
|
+
end
|
95
|
+
push_help_command([:describe_key, key])
|
96
|
+
end
|
97
|
+
|
98
|
+
define_command(:help_go_back, doc: "Go back to the previous help.") do
|
99
|
+
if !HELP_RING.empty?
|
100
|
+
HELP_RING.rotate(1)
|
101
|
+
cmd, *args = HELP_RING.current
|
102
|
+
send(cmd, *args)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
define_command(:help_go_forward, doc: "Go back to the next help.") do
|
107
|
+
if !HELP_RING.empty?
|
108
|
+
HELP_RING.rotate(-1)
|
109
|
+
cmd, *args = HELP_RING.current
|
110
|
+
send(cmd, *args)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -18,7 +18,7 @@ module Textbringer
|
|
18
18
|
ISEARCH_MODE_MAP.define_key(?\C-h, :isearch_delete_char)
|
19
19
|
ISEARCH_MODE_MAP.define_key(?\C-s, :isearch_repeat_forward)
|
20
20
|
ISEARCH_MODE_MAP.define_key(?\C-r, :isearch_repeat_backward)
|
21
|
-
ISEARCH_MODE_MAP.define_key(?\
|
21
|
+
ISEARCH_MODE_MAP.define_key(?\C-m, :isearch_exit)
|
22
22
|
ISEARCH_MODE_MAP.define_key(?\C-g, :isearch_abort)
|
23
23
|
|
24
24
|
ISEARCH_STATUS = {
|
@@ -30,11 +30,13 @@ module Textbringer
|
|
30
30
|
recursive_edit: false
|
31
31
|
}
|
32
32
|
|
33
|
-
define_command(:isearch_forward
|
33
|
+
define_command(:isearch_forward,
|
34
|
+
doc: "Incrementally search forward.") do |**options|
|
34
35
|
isearch_mode(true, **options)
|
35
36
|
end
|
36
37
|
|
37
|
-
define_command(:isearch_backward
|
38
|
+
define_command(:isearch_backward,
|
39
|
+
doc: "Incrementally search backward.") do |**options|
|
38
40
|
isearch_mode(false, **options)
|
39
41
|
end
|
40
42
|
|
@@ -78,23 +80,27 @@ module Textbringer
|
|
78
80
|
end
|
79
81
|
end
|
80
82
|
|
81
|
-
define_command(:isearch_exit) do
|
83
|
+
define_command(:isearch_exit, doc: "Exit incremental search.") do
|
82
84
|
isearch_done
|
83
85
|
end
|
84
86
|
|
85
|
-
define_command(:isearch_abort) do
|
87
|
+
define_command(:isearch_abort, doc: "Abort incremental search.") do
|
86
88
|
goto_char(Buffer.current[:isearch_start])
|
87
89
|
isearch_done
|
88
90
|
raise Quit
|
89
91
|
end
|
90
92
|
|
91
|
-
define_command(:isearch_printing_char) do
|
93
|
+
define_command(:isearch_printing_char, doc: <<~EOD) do
|
94
|
+
Add the typed character to the search string and search.
|
95
|
+
EOD
|
92
96
|
c = Controller.current.last_key
|
93
97
|
ISEARCH_STATUS[:string].concat(c)
|
94
98
|
isearch_search
|
95
99
|
end
|
96
100
|
|
97
|
-
define_command(:isearch_delete_char) do
|
101
|
+
define_command(:isearch_delete_char, doc: <<~EOD) do
|
102
|
+
Delete the last character from the search string and search.
|
103
|
+
EOD
|
98
104
|
ISEARCH_STATUS[:string].chop!
|
99
105
|
isearch_search
|
100
106
|
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Textbringer
|
4
|
+
module Commands
|
5
|
+
KEYBOARD_MACROS = {}
|
6
|
+
|
7
|
+
define_command(:start_keyboard_macro) do
|
8
|
+
message("Recording keyboard macro...")
|
9
|
+
Controller.current.start_keyboard_macro
|
10
|
+
end
|
11
|
+
|
12
|
+
define_command(:end_keyboard_macro) do
|
13
|
+
Controller.current.end_keyboard_macro
|
14
|
+
message("Keyboard macro defined")
|
15
|
+
end
|
16
|
+
|
17
|
+
define_command(:call_last_keyboard_macro) do |n = number_prefix_arg|
|
18
|
+
key = Controller.current.last_key
|
19
|
+
Controller.current.call_last_keyboard_macro(n)
|
20
|
+
map = Keymap.new
|
21
|
+
map.define_key(key, :call_last_keyboard_macro)
|
22
|
+
set_transient_map(map)
|
23
|
+
end
|
24
|
+
|
25
|
+
define_command(:end_and_call_keyboard_macro) do |n = number_prefix_arg|
|
26
|
+
if Controller.current.recording_keyboard_macro?
|
27
|
+
end_keyboard_macro
|
28
|
+
end
|
29
|
+
call_last_keyboard_macro(n)
|
30
|
+
end
|
31
|
+
|
32
|
+
define_command(:end_or_call_keyboard_macro) do |n = number_prefix_arg|
|
33
|
+
if Controller.current.recording_keyboard_macro?
|
34
|
+
end_keyboard_macro
|
35
|
+
else
|
36
|
+
call_last_keyboard_macro(n)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def execute_keyboard_macro(macro, n = 1)
|
41
|
+
Controller.current.execute_keyboard_macro(macro, n)
|
42
|
+
end
|
43
|
+
|
44
|
+
define_command(:name_last_keyboard_macro) do
|
45
|
+
|name = read_from_minibuffer("Name for last keyboard macro: ")|
|
46
|
+
last_keyboard_macro = Controller.current.last_keyboard_macro
|
47
|
+
if last_keyboard_macro.nil?
|
48
|
+
raise EditorError, "Keyboard macro not defined"
|
49
|
+
end
|
50
|
+
KEYBOARD_MACROS[name] = last_keyboard_macro
|
51
|
+
define_command(name) do |n = number_prefix_arg|
|
52
|
+
execute_keyboard_macro(last_keyboard_macro, n)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def read_keyboard_macro(prompt)
|
57
|
+
macros = KEYBOARD_MACROS.keys.map(&:to_s)
|
58
|
+
f = ->(s) { complete_for_minibuffer(s, macros) }
|
59
|
+
read_from_minibuffer(prompt, completion_proc: f)
|
60
|
+
end
|
61
|
+
|
62
|
+
module SymbolDump
|
63
|
+
refine Symbol do
|
64
|
+
def dump
|
65
|
+
":" + to_s.dump
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
using SymbolDump
|
71
|
+
|
72
|
+
define_command(:insert_keyboard_macro) do
|
73
|
+
|name = read_keyboard_macro("Insert keyboard macro: ")|
|
74
|
+
macro = KEYBOARD_MACROS[name]
|
75
|
+
if macro.nil?
|
76
|
+
raise EditorError, "No such macro: #{name}"
|
77
|
+
end
|
78
|
+
macro_literal = "[" + macro.map { |key| key.dump }.join(",") + "]"
|
79
|
+
insert(<<~EOF)
|
80
|
+
define_command(:#{name}) do |n = number_prefix_arg|
|
81
|
+
execute_keyboard_macro(#{macro_literal}, n)
|
82
|
+
end
|
83
|
+
EOF
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -162,8 +162,7 @@ module Textbringer
|
|
162
162
|
Controller.current.current_prefix_arg
|
163
163
|
end
|
164
164
|
|
165
|
-
def
|
166
|
-
arg = current_prefix_arg
|
165
|
+
def prefix_numeric_value(arg)
|
167
166
|
case arg
|
168
167
|
when Integer
|
169
168
|
arg
|
@@ -176,6 +175,10 @@ module Textbringer
|
|
176
175
|
end
|
177
176
|
end
|
178
177
|
|
178
|
+
def number_prefix_arg
|
179
|
+
prefix_numeric_value(current_prefix_arg)
|
180
|
+
end
|
181
|
+
|
179
182
|
define_command(:digit_argument) do
|
180
183
|
|arg = current_prefix_arg|
|
181
184
|
n = Controller.current.last_key.to_i
|
@@ -225,6 +228,44 @@ module Textbringer
|
|
225
228
|
Controller.current.recursive_edit
|
226
229
|
end
|
227
230
|
|
231
|
+
def goto_global_mark
|
232
|
+
global_mark_ring = Buffer.global_mark_ring
|
233
|
+
mark = yield(global_mark_ring)
|
234
|
+
if mark.buffer&.current? && Buffer.current.point_at_mark?(mark)
|
235
|
+
mark = yield(global_mark_ring)
|
236
|
+
end
|
237
|
+
if mark.detached?
|
238
|
+
find_file(mark.file_name)
|
239
|
+
goto_char(mark.location)
|
240
|
+
else
|
241
|
+
switch_to_buffer(mark.buffer)
|
242
|
+
mark.buffer.point_to_mark(mark)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
private :goto_global_mark
|
246
|
+
|
247
|
+
define_command(:next_global_mark) do
|
248
|
+
if Buffer.global_mark_ring.empty?
|
249
|
+
raise EditorError, "Global mark ring is empty"
|
250
|
+
end
|
251
|
+
if Buffer.current.push_global_mark
|
252
|
+
Buffer.global_mark_ring.pop
|
253
|
+
end
|
254
|
+
goto_global_mark do |mark_ring|
|
255
|
+
mark_ring.pop
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
define_command(:previous_global_mark) do
|
260
|
+
if Buffer.global_mark_ring.empty?
|
261
|
+
raise EditorError, "Global mark ring is empty"
|
262
|
+
end
|
263
|
+
Buffer.current.push_global_mark
|
264
|
+
goto_global_mark do |mark_ring|
|
265
|
+
mark_ring.rotate(-1)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
228
269
|
define_command(:shell_execute) do
|
229
270
|
|cmd = read_from_minibuffer("Shell execute: "),
|
230
271
|
buffer_name: "*Shell output*",
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Textbringer
|
4
|
+
module Commands
|
5
|
+
class BufferPosition
|
6
|
+
attr_reader :buffer, :mark
|
7
|
+
|
8
|
+
def initialize(buffer, mark)
|
9
|
+
@buffer = buffer
|
10
|
+
@mark = mark
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
@mark.location.to_s
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
REGISTERS = {}
|
19
|
+
|
20
|
+
def REGISTERS.[]=(name, val)
|
21
|
+
old_val = REGISTERS[name]
|
22
|
+
if old_val.is_a?(BufferPosition)
|
23
|
+
old_val.mark.delete
|
24
|
+
end
|
25
|
+
super(name, val)
|
26
|
+
end
|
27
|
+
|
28
|
+
def read_register(prompt)
|
29
|
+
Window.echo_area.show(prompt)
|
30
|
+
Window.redisplay
|
31
|
+
begin
|
32
|
+
register = read_char
|
33
|
+
register
|
34
|
+
ensure
|
35
|
+
Window.echo_area.clear
|
36
|
+
Window.redisplay
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
define_command(:point_to_register) do
|
41
|
+
|register = read_register("Point to register:")|
|
42
|
+
unless register.is_a?(String)
|
43
|
+
raise ArgumentError, "Invalid register: #{register}"
|
44
|
+
end
|
45
|
+
buffer = Buffer.current
|
46
|
+
mark = buffer.new_mark
|
47
|
+
REGISTERS[register] = BufferPosition.new(buffer, mark)
|
48
|
+
end
|
49
|
+
|
50
|
+
define_command(:jump_to_register) do
|
51
|
+
|register = read_register("Jump to register:")|
|
52
|
+
if !register.is_a?(String)
|
53
|
+
raise ArgumentError, "Invalid register: #{register}"
|
54
|
+
end
|
55
|
+
position = REGISTERS[register]
|
56
|
+
if !position.is_a?(BufferPosition)
|
57
|
+
raise ArgumentError, "Register doesn't contain a buffer position"
|
58
|
+
end
|
59
|
+
switch_to_buffer(position.buffer)
|
60
|
+
position.buffer.point_to_mark(position.mark)
|
61
|
+
end
|
62
|
+
|
63
|
+
define_command(:copy_to_register) do
|
64
|
+
|register = read_register("Copy to register:"),
|
65
|
+
s = Buffer.current.mark, e = Buffer.current.point,
|
66
|
+
delete_flag = current_prefix_arg|
|
67
|
+
buffer = Buffer.current
|
68
|
+
str = s <= e ? buffer.substring(s, e) : buffer.substring(e, s)
|
69
|
+
REGISTERS[register] = str
|
70
|
+
if delete_flag
|
71
|
+
buffer.delete_region(s, e)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
define_command(:append_to_register) do
|
76
|
+
|register = read_register("Append to register:"),
|
77
|
+
s = Buffer.current.mark, e = Buffer.current.point,
|
78
|
+
delete_flag = current_prefix_arg|
|
79
|
+
buffer = Buffer.current
|
80
|
+
str = s <= e ? buffer.substring(s, e) : buffer.substring(e, s)
|
81
|
+
val = REGISTERS[register]
|
82
|
+
if !val.is_a?(String)
|
83
|
+
raise ArgumentError, "Register doesn't contain text"
|
84
|
+
end
|
85
|
+
REGISTERS[register] = val + str
|
86
|
+
if delete_flag
|
87
|
+
buffer.delete_region(s, e)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
define_command(:insert_register) do
|
92
|
+
|register = read_register("Insert register:"),
|
93
|
+
arg = current_prefix_arg|
|
94
|
+
buffer = Buffer.current
|
95
|
+
str = REGISTERS[register]
|
96
|
+
if arg
|
97
|
+
buffer.push_mark
|
98
|
+
end
|
99
|
+
pos = buffer.point
|
100
|
+
insert(str)
|
101
|
+
if !arg
|
102
|
+
buffer.push_mark
|
103
|
+
buffer.goto_char(pos)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
define_command(:number_to_register) do
|
108
|
+
|n = number_prefix_arg,
|
109
|
+
register = read_register("Number to register:")|
|
110
|
+
REGISTERS[register] = n
|
111
|
+
end
|
112
|
+
|
113
|
+
define_command(:increment_register) do
|
114
|
+
|n = current_prefix_arg,
|
115
|
+
register = read_register("Increment register:")|
|
116
|
+
i = REGISTERS[register]
|
117
|
+
case i
|
118
|
+
when Integer
|
119
|
+
REGISTERS[register] = i + prefix_numeric_value(n)
|
120
|
+
when String
|
121
|
+
append_to_register(register,
|
122
|
+
Buffer.current.mark, Buffer.current.point, n)
|
123
|
+
else
|
124
|
+
raise ArgumentError, "Register doesn't contain a number or text"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|