textbringer 13 → 14
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/lib/textbringer/buffer.rb +2 -200
- data/lib/textbringer/commands/buffers.rb +0 -30
- data/lib/textbringer/commands/ispell.rb +213 -0
- data/lib/textbringer/commands/rectangle.rb +290 -0
- data/lib/textbringer/keymap.rb +2 -0
- data/lib/textbringer/version.rb +1 -1
- data/lib/textbringer/window.rb +4 -4
- data/lib/textbringer.rb +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8b974ebb2c6338de9890aee4aa219529d5d81f9b838b21bb6e738ddf205015d5
|
4
|
+
data.tar.gz: fe3ed3ec2825a26e9e0c6eaa777a6e5c53549ac604ffe38eceb653ca2e6ef6b2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ddcdd02dc9fd8d7397ef7994d000d470268aff0052dcab907a2a6cb073a9f39847180c1dfc40984e36d19e4bd64459cdeed8ab6ba493cdf4d1df542d9f4ac3cb
|
7
|
+
data.tar.gz: cf0095973d9e53072a6243568ffe70aaf3a33b5091767c00ff0064ed9dcfc7f0f9c053b7d146abf69ec0cacb88f4495f16a283e05383246a4a84a001413b8cfd
|
data/lib/textbringer/buffer.rb
CHANGED
@@ -1090,203 +1090,6 @@ module Textbringer
|
|
1090
1090
|
insert_for_yank(KILL_RING.rotate(1))
|
1091
1091
|
end
|
1092
1092
|
|
1093
|
-
# Returns start_line, start_col, end_line, and end_col of the rectangle region
|
1094
|
-
# Note that start_col and end_col are 0-origin and width-based (neither 1-origin nor codepoint-based)
|
1095
|
-
def rectangle_boundaries(s = @point, e = mark)
|
1096
|
-
s, e = Buffer.region_boundaries(s, e)
|
1097
|
-
save_excursion do
|
1098
|
-
goto_char(s)
|
1099
|
-
start_line = @current_line
|
1100
|
-
beginning_of_line
|
1101
|
-
start_col = display_width(substring(@point, s))
|
1102
|
-
goto_char(e)
|
1103
|
-
end_line = @current_line
|
1104
|
-
beginning_of_line
|
1105
|
-
end_col = display_width(substring(@point, e))
|
1106
|
-
|
1107
|
-
# Ensure start_col <= end_col
|
1108
|
-
if start_col > end_col
|
1109
|
-
start_col, end_col = end_col, start_col
|
1110
|
-
end
|
1111
|
-
[start_line, start_col, end_line, end_col]
|
1112
|
-
end
|
1113
|
-
end
|
1114
|
-
|
1115
|
-
def apply_on_rectangle(s = @point, e = mark, reverse: false)
|
1116
|
-
start_line, start_col, end_line, end_col = rectangle_boundaries(s, e)
|
1117
|
-
|
1118
|
-
save_excursion do
|
1119
|
-
composite_edit do
|
1120
|
-
if reverse
|
1121
|
-
goto_line(end_line)
|
1122
|
-
else
|
1123
|
-
goto_line(start_line)
|
1124
|
-
end
|
1125
|
-
|
1126
|
-
loop do
|
1127
|
-
beginning_of_line
|
1128
|
-
line_start = @point
|
1129
|
-
|
1130
|
-
# Move to start column
|
1131
|
-
col = 0
|
1132
|
-
while col < start_col && !end_of_line?
|
1133
|
-
forward_char
|
1134
|
-
col = display_width(substring(line_start, @point))
|
1135
|
-
end
|
1136
|
-
|
1137
|
-
yield(start_col, end_col, col, line_start)
|
1138
|
-
|
1139
|
-
# Move to next line for forward iteration
|
1140
|
-
if reverse
|
1141
|
-
break if @current_line <= start_line
|
1142
|
-
backward_line
|
1143
|
-
else
|
1144
|
-
break if @current_line >= end_line
|
1145
|
-
forward_line
|
1146
|
-
end
|
1147
|
-
end
|
1148
|
-
end
|
1149
|
-
end
|
1150
|
-
end
|
1151
|
-
|
1152
|
-
def extract_rectangle(s = @point, e = mark)
|
1153
|
-
lines = []
|
1154
|
-
apply_on_rectangle(s, e) do |start_col, end_col, col, line_start|
|
1155
|
-
start_pos = @point
|
1156
|
-
width = end_col - start_col
|
1157
|
-
|
1158
|
-
# If we haven't reached start_col, the line is too short
|
1159
|
-
if col < start_col
|
1160
|
-
# Line is shorter than start column, extract all spaces
|
1161
|
-
lines << " " * width
|
1162
|
-
else
|
1163
|
-
# Move to end column
|
1164
|
-
while col < end_col && !end_of_line?
|
1165
|
-
forward_char
|
1166
|
-
col = display_width(substring(line_start, @point))
|
1167
|
-
end
|
1168
|
-
end_pos = @point
|
1169
|
-
|
1170
|
-
# Extract the rectangle text for this line
|
1171
|
-
if end_pos > start_pos
|
1172
|
-
extracted = substring(start_pos, end_pos)
|
1173
|
-
# Pad with spaces if the extracted text is shorter than rectangle width
|
1174
|
-
extracted_width = display_width(extracted)
|
1175
|
-
if extracted_width < width
|
1176
|
-
extracted += " " * (width - extracted_width)
|
1177
|
-
end
|
1178
|
-
lines << extracted
|
1179
|
-
else
|
1180
|
-
lines << " " * width
|
1181
|
-
end
|
1182
|
-
end
|
1183
|
-
end
|
1184
|
-
|
1185
|
-
lines
|
1186
|
-
end
|
1187
|
-
|
1188
|
-
def copy_rectangle(s = @point, e = mark)
|
1189
|
-
lines = extract_rectangle(s, e)
|
1190
|
-
@@killed_rectangle = lines
|
1191
|
-
end
|
1192
|
-
|
1193
|
-
def kill_rectangle(s = @point, e = mark)
|
1194
|
-
copy_rectangle(s, e)
|
1195
|
-
delete_rectangle(s, e)
|
1196
|
-
end
|
1197
|
-
|
1198
|
-
def delete_rectangle(s = @point, e = mark)
|
1199
|
-
check_read_only_flag
|
1200
|
-
|
1201
|
-
apply_on_rectangle(s, e, reverse: true) do |start_col, end_col, col, line_start|
|
1202
|
-
start_pos = @point
|
1203
|
-
|
1204
|
-
# Only delete if we're within the line bounds
|
1205
|
-
if col >= start_col
|
1206
|
-
# Move to end column
|
1207
|
-
while col < end_col && !end_of_line?
|
1208
|
-
forward_char
|
1209
|
-
col = display_width(substring(line_start, @point))
|
1210
|
-
end
|
1211
|
-
end_pos = @point
|
1212
|
-
|
1213
|
-
# Delete the rectangle text for this line
|
1214
|
-
if end_pos > start_pos
|
1215
|
-
delete_region(start_pos, end_pos)
|
1216
|
-
end
|
1217
|
-
end
|
1218
|
-
end
|
1219
|
-
end
|
1220
|
-
|
1221
|
-
def yank_rectangle
|
1222
|
-
raise "No rectangle in kill ring" if @@killed_rectangle.nil?
|
1223
|
-
lines = @@killed_rectangle
|
1224
|
-
start_line = @current_line
|
1225
|
-
start_point = @point
|
1226
|
-
start_col = save_excursion {
|
1227
|
-
beginning_of_line
|
1228
|
-
display_width(substring(@point, start_point))
|
1229
|
-
}
|
1230
|
-
composite_edit do
|
1231
|
-
lines.each_with_index do |line, i|
|
1232
|
-
goto_line(start_line + i)
|
1233
|
-
beginning_of_line
|
1234
|
-
line_start = @point
|
1235
|
-
|
1236
|
-
# Move to start column, extending line if necessary
|
1237
|
-
col = 0
|
1238
|
-
while col < start_col && !end_of_line?
|
1239
|
-
forward_char
|
1240
|
-
col = display_width(substring(line_start, @point))
|
1241
|
-
end
|
1242
|
-
|
1243
|
-
# If line is shorter than start_col, extend it with spaces
|
1244
|
-
if col < start_col
|
1245
|
-
insert(" " * (start_col - col))
|
1246
|
-
end
|
1247
|
-
|
1248
|
-
# Insert the rectangle line
|
1249
|
-
insert(line)
|
1250
|
-
end
|
1251
|
-
end
|
1252
|
-
end
|
1253
|
-
|
1254
|
-
def open_rectangle(s = @point, e = mark)
|
1255
|
-
check_read_only_flag
|
1256
|
-
s, e = Buffer.region_boundaries(s, e)
|
1257
|
-
composite_edit do
|
1258
|
-
apply_on_rectangle(s, e) do |start_col, end_col, col, line_start|
|
1259
|
-
# If line is shorter than start_col, extend it with spaces
|
1260
|
-
if col < start_col
|
1261
|
-
insert(" " * (start_col - col))
|
1262
|
-
end
|
1263
|
-
|
1264
|
-
# Insert spaces to create the rectangle
|
1265
|
-
insert(" " * (end_col - start_col))
|
1266
|
-
end
|
1267
|
-
goto_char(s)
|
1268
|
-
end
|
1269
|
-
end
|
1270
|
-
|
1271
|
-
def clear_rectangle(s = @point, e = mark)
|
1272
|
-
check_read_only_flag
|
1273
|
-
apply_on_rectangle(s, e, reverse: true) do |start_col, end_col, col, line_start|
|
1274
|
-
start_pos = @point
|
1275
|
-
if col < start_col
|
1276
|
-
insert(" " * (end_col - start_col))
|
1277
|
-
else
|
1278
|
-
while col < end_col && !end_of_line?
|
1279
|
-
forward_char
|
1280
|
-
col = display_width(substring(line_start, @point))
|
1281
|
-
end
|
1282
|
-
end_pos = @point
|
1283
|
-
|
1284
|
-
delete_region(start_pos, end_pos) if end_pos > start_pos
|
1285
|
-
insert(" " * (end_col - start_col))
|
1286
|
-
end
|
1287
|
-
end
|
1288
|
-
end
|
1289
|
-
|
1290
1093
|
def undo
|
1291
1094
|
undo_or_redo(:undo, @undo_stack, @redo_stack)
|
1292
1095
|
end
|
@@ -1295,7 +1098,7 @@ module Textbringer
|
|
1295
1098
|
undo_or_redo(:redo, @redo_stack, @undo_stack)
|
1296
1099
|
end
|
1297
1100
|
|
1298
|
-
def re_search_forward(s, raise_error: true, count: 1)
|
1101
|
+
def re_search_forward(s, raise_error: true, goto_beginning: false, count: 1)
|
1299
1102
|
if count < 0
|
1300
1103
|
return re_search_backward(s, raise_error: raise_error, count: -count)
|
1301
1104
|
end
|
@@ -1310,7 +1113,7 @@ module Textbringer
|
|
1310
1113
|
return nil
|
1311
1114
|
end
|
1312
1115
|
end
|
1313
|
-
pos = match_end(0)
|
1116
|
+
pos = goto_beginning ? match_beginning(0) : match_end(0)
|
1314
1117
|
end
|
1315
1118
|
goto_char(pos)
|
1316
1119
|
end
|
@@ -1948,7 +1751,6 @@ module Textbringer
|
|
1948
1751
|
end
|
1949
1752
|
|
1950
1753
|
KILL_RING = Ring.new
|
1951
|
-
@@killed_rectangle = nil
|
1952
1754
|
|
1953
1755
|
class UndoableAction
|
1954
1756
|
attr_accessor :version
|
@@ -129,36 +129,6 @@ module Textbringer
|
|
129
129
|
Buffer.current.delete_region
|
130
130
|
end
|
131
131
|
|
132
|
-
define_command(:kill_rectangle,
|
133
|
-
doc: "Kill the text of the region-rectangle, saving its contents as the last killed rectangle.") do
|
134
|
-
Buffer.current.kill_rectangle
|
135
|
-
end
|
136
|
-
|
137
|
-
define_command(:copy_rectangle_as_kill,
|
138
|
-
doc: "Save the text of the region-rectangle as the last killed rectangle.") do
|
139
|
-
Buffer.current.copy_rectangle
|
140
|
-
end
|
141
|
-
|
142
|
-
define_command(:delete_rectangle,
|
143
|
-
doc: "Delete the text of the region-rectangle.") do
|
144
|
-
Buffer.current.delete_rectangle
|
145
|
-
end
|
146
|
-
|
147
|
-
define_command(:yank_rectangle,
|
148
|
-
doc: "Yank the last killed rectangle with its upper left corner at point.") do
|
149
|
-
Buffer.current.yank_rectangle
|
150
|
-
end
|
151
|
-
|
152
|
-
define_command(:open_rectangle,
|
153
|
-
doc: "Insert blank space to fill the space of the region-rectangle. This pushes the previous contents of the region-rectangle to the right.") do
|
154
|
-
Buffer.current.open_rectangle
|
155
|
-
end
|
156
|
-
|
157
|
-
define_command(:clear_rectangle,
|
158
|
-
doc: "Clear the region-rectangle by replacing its contents with spaces.") do
|
159
|
-
Buffer.current.clear_rectangle
|
160
|
-
end
|
161
|
-
|
162
132
|
define_command(:transpose_chars,
|
163
133
|
doc: "Transpose characters.") do
|
164
134
|
Buffer.current.transpose_chars
|
@@ -0,0 +1,213 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "open3"
|
4
|
+
|
5
|
+
module Textbringer
|
6
|
+
module Commands
|
7
|
+
class Ispell
|
8
|
+
def initialize
|
9
|
+
@personal_dictionary_modified = false
|
10
|
+
@stdin, @stdout, @stderr, @wait_thr =
|
11
|
+
Open3.popen3("aspell -a")
|
12
|
+
@stdout.gets # consume the banner
|
13
|
+
end
|
14
|
+
|
15
|
+
def check_word(word)
|
16
|
+
send_command("^" + word)
|
17
|
+
result = @stdout.gets
|
18
|
+
@stdout.gets
|
19
|
+
case result
|
20
|
+
when /\A&\s+([^\s]+)\s+\d+\s+\d+:\s+(.*)/
|
21
|
+
[$1, $2.split(/, /)]
|
22
|
+
when /\A\*/, /\A\+/, /\A\-/, /\A\%/
|
23
|
+
[word, []]
|
24
|
+
when /\A#/
|
25
|
+
[word, nil]
|
26
|
+
else
|
27
|
+
raise "unexpected output from aspell: #{result}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_to_session_dictionary(word)
|
32
|
+
send_command("@" + word)
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_to_personal_dictionary(word)
|
36
|
+
send_command("*" + word)
|
37
|
+
@personal_dictionary_modified = true
|
38
|
+
end
|
39
|
+
|
40
|
+
def personal_dictionary_modified?
|
41
|
+
@personal_dictionary_modified
|
42
|
+
end
|
43
|
+
|
44
|
+
def save_personal_dictionary
|
45
|
+
send_command("#")
|
46
|
+
end
|
47
|
+
|
48
|
+
def send_command(line)
|
49
|
+
@stdin.puts(line)
|
50
|
+
@stdin.flush
|
51
|
+
end
|
52
|
+
|
53
|
+
def close
|
54
|
+
@stdin.close
|
55
|
+
@stdout.close
|
56
|
+
@stderr.close
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
define_keymap :ISPELL_MODE_MAP
|
61
|
+
(?\x20..?\x7e).each do |c|
|
62
|
+
ISPELL_MODE_MAP.define_key(c, :ispell_unknown_command)
|
63
|
+
end
|
64
|
+
ISPELL_MODE_MAP.define_key(?\t, :ispell_unknown_command)
|
65
|
+
ISPELL_MODE_MAP.handle_undefined_key do |key|
|
66
|
+
ispell_unknown_command
|
67
|
+
end
|
68
|
+
ISPELL_MODE_MAP.define_key(?r, :ispell_replace)
|
69
|
+
ISPELL_MODE_MAP.define_key(?a, :ispell_accept)
|
70
|
+
ISPELL_MODE_MAP.define_key(?i, :ispell_insert)
|
71
|
+
ISPELL_MODE_MAP.define_key(" ", :ispell_skip)
|
72
|
+
ISPELL_MODE_MAP.define_key(?q, :ispell_quit)
|
73
|
+
|
74
|
+
ISPELL_STATUS = {}
|
75
|
+
|
76
|
+
ISPELL_WORD_REGEXP = /[A-Za-z]+/
|
77
|
+
|
78
|
+
define_command(:ispell_word) do
|
79
|
+
buffer = Buffer.current
|
80
|
+
word = buffer.save_excursion {
|
81
|
+
while !buffer.beginning_of_buffer? && buffer.char_after =~ /[A-Za-z]/
|
82
|
+
buffer.backward_char
|
83
|
+
end
|
84
|
+
buffer.re_search_forward(/[A-Za-z]+/, raise_error: false) &&
|
85
|
+
buffer.match_string(0)
|
86
|
+
}
|
87
|
+
if word.nil?
|
88
|
+
message("No word at point.")
|
89
|
+
return
|
90
|
+
end
|
91
|
+
start_pos = buffer.match_beginning(0)
|
92
|
+
end_pos = buffer.match_end(0)
|
93
|
+
ispell = Ispell.new
|
94
|
+
begin
|
95
|
+
_original, suggestions = ispell.check_word(word)
|
96
|
+
if suggestions.nil? || suggestions.empty?
|
97
|
+
message("#{word.inspect} is spelled correctly.")
|
98
|
+
else
|
99
|
+
s = read_from_minibuffer("Correct #{word} with: ",
|
100
|
+
completion_proc: ->(s) {
|
101
|
+
suggestions.grep(/^#{Regexp.quote(s)}/)
|
102
|
+
})
|
103
|
+
if s
|
104
|
+
buffer.composite_edit do
|
105
|
+
buffer.delete_region(start_pos, end_pos)
|
106
|
+
buffer.insert(s)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
ensure
|
111
|
+
ispell.close
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
define_command(:ispell_buffer) do |recursive_edit: false|
|
116
|
+
Buffer.current.beginning_of_buffer
|
117
|
+
ispell_mode
|
118
|
+
ispell_forward
|
119
|
+
ISPELL_STATUS[:recursive_edit] = recursive_edit
|
120
|
+
if recursive_edit
|
121
|
+
recursive_edit()
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def ispell_done
|
126
|
+
Buffer.current.delete_visible_mark
|
127
|
+
Controller.current.overriding_map = nil
|
128
|
+
ISPELL_STATUS[:ispell]&.close
|
129
|
+
ISPELL_STATUS[:ispell] = nil
|
130
|
+
if ISPELL_STATUS[:recursive_edit]
|
131
|
+
exit_recursive_edit
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def ispell_mode
|
136
|
+
ISPELL_STATUS[:ispell] = Ispell.new
|
137
|
+
Controller.current.overriding_map = ISPELL_MODE_MAP
|
138
|
+
end
|
139
|
+
|
140
|
+
def ispell_forward
|
141
|
+
buffer = Buffer.current
|
142
|
+
while buffer.re_search_forward(ISPELL_WORD_REGEXP, raise_error: false,
|
143
|
+
goto_beginning: true)
|
144
|
+
ispell_beginning = buffer.point
|
145
|
+
buffer.set_visible_mark
|
146
|
+
buffer.goto_char(buffer.match_end(0))
|
147
|
+
word = buffer.match_string(0)
|
148
|
+
_original, suggestions = ISPELL_STATUS[:ispell].check_word(word)
|
149
|
+
if !suggestions.nil? && !suggestions.empty?
|
150
|
+
ISPELL_STATUS[:beginning] = ispell_beginning
|
151
|
+
ISPELL_STATUS[:word] = word
|
152
|
+
ISPELL_STATUS[:suggestions] = suggestions
|
153
|
+
message("Mispelled: #{word} [r]eplace, [a]ccept, [i]nsert, [SPC] to skip, [q]uit")
|
154
|
+
recenter
|
155
|
+
return
|
156
|
+
end
|
157
|
+
end
|
158
|
+
Controller.current.overriding_map = nil
|
159
|
+
if ISPELL_STATUS[:ispell]&.personal_dictionary_modified? &&
|
160
|
+
y_or_n?("Personal dictionary modified. Save?")
|
161
|
+
ISPELL_STATUS[:ispell].save_personal_dictionary
|
162
|
+
end
|
163
|
+
message("Finished spelling check.")
|
164
|
+
ispell_done
|
165
|
+
end
|
166
|
+
|
167
|
+
define_command(:ispell_replace) do
|
168
|
+
word = ISPELL_STATUS[:word]
|
169
|
+
suggestions = ISPELL_STATUS[:suggestions]
|
170
|
+
Controller.current.overriding_map = nil
|
171
|
+
s = read_from_minibuffer("Correct #{word} with: ",
|
172
|
+
completion_proc: ->(s) {
|
173
|
+
suggestions.grep(/^#{Regexp.quote(s)}/)
|
174
|
+
})
|
175
|
+
Controller.current.overriding_map = ISPELL_MODE_MAP
|
176
|
+
if !s.empty?
|
177
|
+
buffer = Buffer.current
|
178
|
+
pos = buffer.point
|
179
|
+
buffer.goto_char(ISPELL_STATUS[:beginning])
|
180
|
+
buffer.composite_edit do
|
181
|
+
buffer.delete_region(buffer.point, pos)
|
182
|
+
buffer.insert(s)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
ispell_forward
|
186
|
+
end
|
187
|
+
|
188
|
+
define_command(:ispell_accept) do
|
189
|
+
ISPELL_STATUS[:ispell].add_to_session_dictionary(ISPELL_STATUS[:word])
|
190
|
+
ispell_forward
|
191
|
+
end
|
192
|
+
|
193
|
+
define_command(:ispell_insert) do
|
194
|
+
ISPELL_STATUS[:ispell].add_to_personal_dictionary(ISPELL_STATUS[:word])
|
195
|
+
ispell_forward
|
196
|
+
end
|
197
|
+
|
198
|
+
define_command(:ispell_skip) do
|
199
|
+
ispell_forward
|
200
|
+
end
|
201
|
+
|
202
|
+
define_command(:ispell_quit) do
|
203
|
+
message("Quitting spell check.")
|
204
|
+
ispell_done
|
205
|
+
end
|
206
|
+
|
207
|
+
define_command(:ispell_unknown_command) do
|
208
|
+
word = ISPELL_STATUS[:word]
|
209
|
+
message("Mispelled: #{word} [r]eplace, [a]ccept, [i]nsert, [SPC] to skip, [q]uit")
|
210
|
+
Window.beep
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,290 @@
|
|
1
|
+
module Textbringer::Buffer::RectangleMethods
|
2
|
+
SHARED_VALUES = {}
|
3
|
+
|
4
|
+
refine Textbringer::Buffer do
|
5
|
+
# Returns start_line, start_col, end_line, and end_col of the rectangle region
|
6
|
+
# Note that start_col and end_col are 0-origin and width-based (neither 1-origin nor codepoint-based)
|
7
|
+
def rectangle_boundaries(s = @point, e = mark)
|
8
|
+
s, e = Buffer.region_boundaries(s, e)
|
9
|
+
save_excursion do
|
10
|
+
goto_char(s)
|
11
|
+
start_line = @current_line
|
12
|
+
beginning_of_line
|
13
|
+
start_col = display_width(substring(@point, s))
|
14
|
+
goto_char(e)
|
15
|
+
end_line = @current_line
|
16
|
+
beginning_of_line
|
17
|
+
end_col = display_width(substring(@point, e))
|
18
|
+
|
19
|
+
# Ensure start_col <= end_col
|
20
|
+
if start_col > end_col
|
21
|
+
start_col, end_col = end_col, start_col
|
22
|
+
end
|
23
|
+
[start_line, start_col, end_line, end_col]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def apply_on_rectangle(s = @point, e = mark)
|
28
|
+
start_line, start_col, end_line, end_col = rectangle_boundaries(s, e)
|
29
|
+
|
30
|
+
save_excursion do
|
31
|
+
composite_edit do
|
32
|
+
goto_line(start_line)
|
33
|
+
|
34
|
+
loop do
|
35
|
+
beginning_of_line
|
36
|
+
line_start = @point
|
37
|
+
|
38
|
+
# Move to start column
|
39
|
+
col = 0
|
40
|
+
while col < start_col && !end_of_line?
|
41
|
+
forward_char
|
42
|
+
col = display_width(substring(line_start, @point))
|
43
|
+
end
|
44
|
+
|
45
|
+
yield(start_col, end_col, col, line_start, start_line, end_line)
|
46
|
+
|
47
|
+
# Move to next line for forward iteration
|
48
|
+
break if @current_line >= end_line
|
49
|
+
forward_line
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def extract_rectangle(s = @point, e = mark)
|
56
|
+
lines = []
|
57
|
+
apply_on_rectangle(s, e) do |start_col, end_col, col, line_start|
|
58
|
+
start_pos = @point
|
59
|
+
width = end_col - start_col
|
60
|
+
|
61
|
+
# If we haven't reached start_col, the line is too short
|
62
|
+
if col < start_col
|
63
|
+
# Line is shorter than start column, extract all spaces
|
64
|
+
lines << " " * width
|
65
|
+
else
|
66
|
+
# Move to end column
|
67
|
+
while col < end_col && !end_of_line?
|
68
|
+
forward_char
|
69
|
+
col = display_width(substring(line_start, @point))
|
70
|
+
end
|
71
|
+
end_pos = @point
|
72
|
+
|
73
|
+
# Extract the rectangle text for this line
|
74
|
+
if end_pos > start_pos
|
75
|
+
extracted = substring(start_pos, end_pos)
|
76
|
+
# Pad with spaces if the extracted text is shorter than rectangle width
|
77
|
+
extracted_width = display_width(extracted)
|
78
|
+
if extracted_width < width
|
79
|
+
extracted += " " * (width - extracted_width)
|
80
|
+
end
|
81
|
+
lines << extracted
|
82
|
+
else
|
83
|
+
lines << " " * width
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
lines
|
89
|
+
end
|
90
|
+
|
91
|
+
def copy_rectangle(s = @point, e = mark)
|
92
|
+
lines = extract_rectangle(s, e)
|
93
|
+
SHARED_VALUES[:killed_rectangle] = lines
|
94
|
+
end
|
95
|
+
|
96
|
+
def kill_rectangle(s = @point, e = mark)
|
97
|
+
copy_rectangle(s, e)
|
98
|
+
delete_rectangle(s, e)
|
99
|
+
end
|
100
|
+
|
101
|
+
def delete_rectangle(s = @point, e = mark)
|
102
|
+
check_read_only_flag
|
103
|
+
|
104
|
+
apply_on_rectangle(s, e) do |start_col, end_col, col, line_start|
|
105
|
+
start_pos = @point
|
106
|
+
|
107
|
+
# Only delete if we're within the line bounds
|
108
|
+
if col >= start_col
|
109
|
+
# Move to end column
|
110
|
+
while col < end_col && !end_of_line?
|
111
|
+
forward_char
|
112
|
+
col = display_width(substring(line_start, @point))
|
113
|
+
end
|
114
|
+
end_pos = @point
|
115
|
+
|
116
|
+
# Delete the rectangle text for this line
|
117
|
+
if end_pos > start_pos
|
118
|
+
delete_region(start_pos, end_pos)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def yank_rectangle
|
125
|
+
raise "No rectangle in kill ring" if SHARED_VALUES[:killed_rectangle].nil?
|
126
|
+
lines = SHARED_VALUES[:killed_rectangle]
|
127
|
+
start_line = @current_line
|
128
|
+
start_point = @point
|
129
|
+
start_col = save_excursion {
|
130
|
+
beginning_of_line
|
131
|
+
display_width(substring(@point, start_point))
|
132
|
+
}
|
133
|
+
composite_edit do
|
134
|
+
lines.each_with_index do |line, i|
|
135
|
+
goto_line(start_line + i)
|
136
|
+
beginning_of_line
|
137
|
+
line_start = @point
|
138
|
+
|
139
|
+
# Move to start column, extending line if necessary
|
140
|
+
col = 0
|
141
|
+
while col < start_col && !end_of_line?
|
142
|
+
forward_char
|
143
|
+
col = display_width(substring(line_start, @point))
|
144
|
+
end
|
145
|
+
|
146
|
+
# If line is shorter than start_col, extend it with spaces
|
147
|
+
if col < start_col
|
148
|
+
insert(" " * (start_col - col))
|
149
|
+
end
|
150
|
+
|
151
|
+
# Insert the rectangle line
|
152
|
+
insert(line)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def open_rectangle(s = @point, e = mark)
|
158
|
+
check_read_only_flag
|
159
|
+
s, e = Buffer.region_boundaries(s, e)
|
160
|
+
composite_edit do
|
161
|
+
apply_on_rectangle(s, e) do |start_col, end_col, col, line_start|
|
162
|
+
# If line is shorter than start_col, extend it with spaces
|
163
|
+
if col < start_col
|
164
|
+
insert(" " * (start_col - col))
|
165
|
+
end
|
166
|
+
|
167
|
+
# Insert spaces to create the rectangle
|
168
|
+
insert(" " * (end_col - start_col))
|
169
|
+
end
|
170
|
+
goto_char(s)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def clear_rectangle(s = @point, e = mark)
|
175
|
+
check_read_only_flag
|
176
|
+
apply_on_rectangle(s, e) do |start_col, end_col, col, line_start|
|
177
|
+
start_pos = @point
|
178
|
+
if col < start_col
|
179
|
+
insert(" " * (end_col - start_col))
|
180
|
+
else
|
181
|
+
while col < end_col && !end_of_line?
|
182
|
+
forward_char
|
183
|
+
col = display_width(substring(line_start, @point))
|
184
|
+
end
|
185
|
+
end_pos = @point
|
186
|
+
|
187
|
+
delete_region(start_pos, end_pos) if end_pos > start_pos
|
188
|
+
insert(" " * (end_col - start_col))
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def string_rectangle(str, s = @point, e = mark)
|
194
|
+
check_read_only_flag
|
195
|
+
apply_on_rectangle(s, e) do |start_col, end_col, col, line_start|
|
196
|
+
start_pos = @point
|
197
|
+
if col < start_col
|
198
|
+
insert(" " * (start_col - col))
|
199
|
+
insert(str)
|
200
|
+
else
|
201
|
+
while col < end_col && !end_of_line?
|
202
|
+
forward_char
|
203
|
+
col = display_width(substring(line_start, @point))
|
204
|
+
end
|
205
|
+
end_pos = @point
|
206
|
+
|
207
|
+
delete_region(start_pos, end_pos) if end_pos > start_pos
|
208
|
+
insert(str)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def string_insert_rectangle(str, s = @point, e = mark)
|
214
|
+
check_read_only_flag
|
215
|
+
apply_on_rectangle(s, e) do |start_col, end_col, col, line_start|
|
216
|
+
if col < start_col
|
217
|
+
insert(" " * (start_col - col))
|
218
|
+
end
|
219
|
+
insert(str)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def rectangle_number_lines(s = @point, e = mark)
|
224
|
+
check_read_only_flag
|
225
|
+
n = 1
|
226
|
+
number_width = nil
|
227
|
+
apply_on_rectangle(s, e) do |start_col, end_col, col, line_start, start_line, end_line|
|
228
|
+
number_width ||= (1 + (end_line - start_line)).to_s.size
|
229
|
+
if col < start_col
|
230
|
+
insert(" " * (start_col - col))
|
231
|
+
end
|
232
|
+
insert(n.to_s.rjust(number_width) + " ")
|
233
|
+
n += 1
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
using Textbringer::Buffer::RectangleMethods
|
240
|
+
|
241
|
+
module Textbringer
|
242
|
+
module Commands
|
243
|
+
define_command(:kill_rectangle,
|
244
|
+
doc: "Kill the text of the region-rectangle, saving its contents as the last killed rectangle.") do
|
245
|
+
Buffer.current.kill_rectangle
|
246
|
+
end
|
247
|
+
|
248
|
+
define_command(:copy_rectangle_as_kill,
|
249
|
+
doc: "Save the text of the region-rectangle as the last killed rectangle.") do
|
250
|
+
Buffer.current.copy_rectangle
|
251
|
+
end
|
252
|
+
|
253
|
+
define_command(:delete_rectangle,
|
254
|
+
doc: "Delete the text of the region-rectangle.") do
|
255
|
+
Buffer.current.delete_rectangle
|
256
|
+
end
|
257
|
+
|
258
|
+
define_command(:yank_rectangle,
|
259
|
+
doc: "Yank the last killed rectangle with its upper left corner at point.") do
|
260
|
+
Buffer.current.yank_rectangle
|
261
|
+
end
|
262
|
+
|
263
|
+
define_command(:open_rectangle,
|
264
|
+
doc: "Insert blank space to fill the space of the region-rectangle. This pushes the previous contents of the region-rectangle to the right.") do
|
265
|
+
Buffer.current.open_rectangle
|
266
|
+
end
|
267
|
+
|
268
|
+
define_command(:clear_rectangle,
|
269
|
+
doc: "Clear the region-rectangle by replacing its contents with spaces.") do
|
270
|
+
Buffer.current.clear_rectangle
|
271
|
+
end
|
272
|
+
|
273
|
+
define_command(:string_rectangle,
|
274
|
+
doc: "Replace rectangle contents with the specified string on each line.") do
|
275
|
+
|str = read_from_minibuffer("String rectangle: ")|
|
276
|
+
Buffer.current.string_rectangle(str)
|
277
|
+
end
|
278
|
+
|
279
|
+
define_command(:string_insert_rectangle,
|
280
|
+
doc: "Insert the specified string on each line of the rectangle.") do
|
281
|
+
|str = read_from_minibuffer("String insert rectangle: ")|
|
282
|
+
Buffer.current.string_insert_rectangle(str)
|
283
|
+
end
|
284
|
+
|
285
|
+
define_command(:rectangle_number_lines,
|
286
|
+
doc: "Insert numbers in front of the region-rectangle.") do
|
287
|
+
Buffer.current.rectangle_number_lines
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
data/lib/textbringer/keymap.rb
CHANGED
@@ -209,6 +209,8 @@ module Textbringer
|
|
209
209
|
GLOBAL_MAP.define_key("\C-xry", :yank_rectangle)
|
210
210
|
GLOBAL_MAP.define_key("\C-xro", :open_rectangle)
|
211
211
|
GLOBAL_MAP.define_key("\C-xrc", :clear_rectangle)
|
212
|
+
GLOBAL_MAP.define_key("\C-xrt", :string_rectangle)
|
213
|
+
GLOBAL_MAP.define_key("\C-xrN", :rectangle_number_lines)
|
212
214
|
GLOBAL_MAP.define_key("\C-x(", :start_keyboard_macro)
|
213
215
|
GLOBAL_MAP.define_key(:f3, :start_keyboard_macro)
|
214
216
|
GLOBAL_MAP.define_key("\C-x)", :end_keyboard_macro)
|
data/lib/textbringer/version.rb
CHANGED
data/lib/textbringer/window.rb
CHANGED
@@ -405,7 +405,7 @@ module Textbringer
|
|
405
405
|
@window.erase
|
406
406
|
@window.setpos(0, 0)
|
407
407
|
@window.attrset(0)
|
408
|
-
if
|
408
|
+
if @buffer.visible_mark &&
|
409
409
|
@buffer.point_after_mark?(@buffer.visible_mark)
|
410
410
|
@window.attron(Curses::A_REVERSE)
|
411
411
|
end
|
@@ -450,7 +450,7 @@ module Textbringer
|
|
450
450
|
break if newx == columns && cury == lines - 2
|
451
451
|
@buffer.forward_char
|
452
452
|
end
|
453
|
-
if
|
453
|
+
if @buffer.visible_mark
|
454
454
|
@window.attroff(Curses::A_REVERSE)
|
455
455
|
end
|
456
456
|
@buffer.mark_to_point(@bottom_of_window)
|
@@ -679,7 +679,7 @@ module Textbringer
|
|
679
679
|
if @buffer.point_at_mark?(point)
|
680
680
|
@cursor.y = cury
|
681
681
|
@cursor.x = curx
|
682
|
-
if
|
682
|
+
if @buffer.visible_mark
|
683
683
|
if @buffer.point_after_mark?(@buffer.visible_mark)
|
684
684
|
@window.attroff(Curses::A_REVERSE)
|
685
685
|
elsif @buffer.point_before_mark?(@buffer.visible_mark)
|
@@ -687,7 +687,7 @@ module Textbringer
|
|
687
687
|
end
|
688
688
|
end
|
689
689
|
end
|
690
|
-
if
|
690
|
+
if @buffer.visible_mark &&
|
691
691
|
@buffer.point_at_mark?(@buffer.visible_mark)
|
692
692
|
if @buffer.point_after_mark?(point)
|
693
693
|
@window.attroff(Curses::A_REVERSE)
|
data/lib/textbringer.rb
CHANGED
@@ -14,7 +14,9 @@ require_relative "textbringer/commands/windows"
|
|
14
14
|
require_relative "textbringer/commands/files"
|
15
15
|
require_relative "textbringer/commands/misc"
|
16
16
|
require_relative "textbringer/commands/isearch"
|
17
|
+
require_relative "textbringer/commands/ispell"
|
17
18
|
require_relative "textbringer/commands/replace"
|
19
|
+
require_relative "textbringer/commands/rectangle"
|
18
20
|
require_relative "textbringer/commands/dabbrev"
|
19
21
|
require_relative "textbringer/commands/ctags"
|
20
22
|
require_relative "textbringer/commands/clipboard"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: textbringer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '
|
4
|
+
version: '14'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shugo Maeda
|
@@ -352,8 +352,10 @@ files:
|
|
352
352
|
- lib/textbringer/commands/help.rb
|
353
353
|
- lib/textbringer/commands/input_method.rb
|
354
354
|
- lib/textbringer/commands/isearch.rb
|
355
|
+
- lib/textbringer/commands/ispell.rb
|
355
356
|
- lib/textbringer/commands/keyboard_macro.rb
|
356
357
|
- lib/textbringer/commands/misc.rb
|
358
|
+
- lib/textbringer/commands/rectangle.rb
|
357
359
|
- lib/textbringer/commands/register.rb
|
358
360
|
- lib/textbringer/commands/replace.rb
|
359
361
|
- lib/textbringer/commands/server.rb
|
@@ -409,7 +411,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
409
411
|
- !ruby/object:Gem::Version
|
410
412
|
version: '0'
|
411
413
|
requirements: []
|
412
|
-
rubygems_version: 3.6.
|
414
|
+
rubygems_version: 3.6.9
|
413
415
|
specification_version: 4
|
414
416
|
summary: An Emacs-like text editor
|
415
417
|
test_files: []
|