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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2ebcccec1dcc4c9b2116dc02f0e4548d30f019c0da7fc1d0573f84cf08b8590d
4
- data.tar.gz: 1f2b135717e1fde5ca8f13fdb9a967d5a7cad5aea6af83c040f35bec85c3bf23
3
+ metadata.gz: 8b974ebb2c6338de9890aee4aa219529d5d81f9b838b21bb6e738ddf205015d5
4
+ data.tar.gz: fe3ed3ec2825a26e9e0c6eaa777a6e5c53549ac604ffe38eceb653ca2e6ef6b2
5
5
  SHA512:
6
- metadata.gz: a9a9ea8e0234c2fda07af756eb12e8acc53a7f0c3e9cee57276adb742bda13a896875f4b21334794737a55d6e7c7c07168baea74304336e687834f3e5ba4b5d2
7
- data.tar.gz: 1c3a84fc349f0d68cd55498d6ae45d159f672b824c8307e89e30bd4eb68c70126de3c358a9255177c74230b747b6255dbdfd36182fe9d56b34927b8a88f60fbb
6
+ metadata.gz: ddcdd02dc9fd8d7397ef7994d000d470268aff0052dcab907a2a6cb073a9f39847180c1dfc40984e36d19e4bd64459cdeed8ab6ba493cdf4d1df542d9f4ac3cb
7
+ data.tar.gz: cf0095973d9e53072a6243568ffe70aaf3a33b5091767c00ff0064ed9dcfc7f0f9c053b7d146abf69ec0cacb88f4495f16a283e05383246a4a84a001413b8cfd
@@ -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
@@ -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)
@@ -1,3 +1,3 @@
1
1
  module Textbringer
2
- VERSION = "13"
2
+ VERSION = "14"
3
3
  end
@@ -405,7 +405,7 @@ module Textbringer
405
405
  @window.erase
406
406
  @window.setpos(0, 0)
407
407
  @window.attrset(0)
408
- if current? && @buffer.visible_mark &&
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 current? && @buffer.visible_mark
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 current? && @buffer.visible_mark
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 current? && @buffer.visible_mark &&
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: '13'
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.7
414
+ rubygems_version: 3.6.9
413
415
  specification_version: 4
414
416
  summary: An Emacs-like text editor
415
417
  test_files: []