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.
- 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
|