textbringer 12 → 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: 04d0f8e23504a6d3ae1ce1ccc4cf1ca4c8308525cd61356d22e50631474408bc
4
- data.tar.gz: a16eefeef6df621bf2272a4437b1321c999b7ffd3f0cf7f1353d0a41baabd7b6
3
+ metadata.gz: 8b974ebb2c6338de9890aee4aa219529d5d81f9b838b21bb6e738ddf205015d5
4
+ data.tar.gz: fe3ed3ec2825a26e9e0c6eaa777a6e5c53549ac604ffe38eceb653ca2e6ef6b2
5
5
  SHA512:
6
- metadata.gz: f03f8d34e41b099d3b9dcaecbb1fa40b3e9c9f3c43a7aba7917dcfe8a7cccbd1dec97599c46e657839af70d8abf67681f339d5ba97f6abb308e76fe1ffbd93f0
7
- data.tar.gz: 643f15490cc3ce708c8b6aced38b1249b7c4074a6a5fcb9c7fd927ee1493778fdefd9c9f393a968630e89a85823e13deb036c481691651b02580b2bc150a1a14
6
+ metadata.gz: ddcdd02dc9fd8d7397ef7994d000d470268aff0052dcab907a2a6cb073a9f39847180c1dfc40984e36d19e4bd64459cdeed8ab6ba493cdf4d1df542d9f4ac3cb
7
+ data.tar.gz: cf0095973d9e53072a6243568ffe70aaf3a33b5091767c00ff0064ed9dcfc7f0f9c053b7d146abf69ec0cacb88f4495f16a283e05383246a4a84a001413b8cfd
@@ -550,6 +550,7 @@ module Textbringer
550
550
  end
551
551
 
552
552
  def goto_char(pos)
553
+ return pos if pos == @point
553
554
  if pos < 0 || pos > size
554
555
  raise RangeError, "Out of buffer"
555
556
  end
@@ -1089,185 +1090,6 @@ module Textbringer
1089
1090
  insert_for_yank(KILL_RING.rotate(1))
1090
1091
  end
1091
1092
 
1092
- def rectangle_boundaries(s = @point, e = mark)
1093
- s, e = Buffer.region_boundaries(s, e)
1094
- save_excursion do
1095
- goto_char(s)
1096
- start_line = @current_line
1097
- beginning_of_line
1098
- start_col = display_width(substring(@point, s)) + 1
1099
- goto_char(e)
1100
- end_line = @current_line
1101
- beginning_of_line
1102
- end_col = display_width(substring(@point, e)) + 1
1103
-
1104
- # Ensure start_col <= end_col
1105
- if start_col > end_col
1106
- start_col, end_col = end_col, start_col
1107
- end
1108
- [start_line, start_col, end_line, end_col]
1109
- end
1110
- end
1111
-
1112
- def extract_rectangle(s = @point, e = mark)
1113
- start_line, start_col, end_line, end_col = rectangle_boundaries(s, e)
1114
- lines = []
1115
- rectangle_width = end_col - start_col
1116
-
1117
- save_excursion do
1118
- goto_line(start_line)
1119
- (start_line..end_line).each do |line_num|
1120
- beginning_of_line
1121
- line_start = @point
1122
-
1123
- # Move to start column
1124
- col = 1
1125
- while col < start_col && !end_of_line?
1126
- forward_char
1127
- col = 1 + display_width(substring(line_start, @point))
1128
- end
1129
- start_pos = @point
1130
-
1131
- # If we haven't reached start_col, the line is too short
1132
- if col < start_col
1133
- # Line is shorter than start column, extract all spaces
1134
- lines << " " * rectangle_width
1135
- else
1136
- # Move to end column
1137
- while col < end_col && !end_of_line?
1138
- forward_char
1139
- col = 1 + display_width(substring(line_start, @point))
1140
- end
1141
- end_pos = @point
1142
-
1143
- # Extract the rectangle text for this line
1144
- if end_pos > start_pos
1145
- extracted = substring(start_pos, end_pos)
1146
- # Pad with spaces if the extracted text is shorter than rectangle width
1147
- extracted_width = display_width(extracted)
1148
- if extracted_width < rectangle_width
1149
- extracted += " " * (rectangle_width - extracted_width)
1150
- end
1151
- lines << extracted
1152
- else
1153
- lines << " " * rectangle_width
1154
- end
1155
- end
1156
-
1157
- # Move to next line
1158
- break if line_num == end_line
1159
- forward_line
1160
- end
1161
- end
1162
-
1163
- lines
1164
- end
1165
-
1166
- def copy_rectangle(s = @point, e = mark)
1167
- lines = extract_rectangle(s, e)
1168
- @@killed_rectangle = lines
1169
- end
1170
-
1171
- def kill_rectangle(s = @point, e = mark)
1172
- copy_rectangle(s, e)
1173
- delete_rectangle(s, e)
1174
- end
1175
-
1176
- def delete_rectangle(s = @point, e = mark)
1177
- check_read_only_flag
1178
- start_line, start_col, end_line, end_col = rectangle_boundaries(s, e)
1179
-
1180
- save_excursion do
1181
- # Delete from bottom to top to avoid position shifts
1182
- (start_line..end_line).reverse_each do |line_num|
1183
- goto_line(line_num)
1184
- beginning_of_line
1185
- line_start = @point
1186
-
1187
- # Move to start column
1188
- col = 1
1189
- while col < start_col && !end_of_line?
1190
- forward_char
1191
- col = 1 + display_width(substring(line_start, @point))
1192
- end
1193
- start_pos = @point
1194
-
1195
- # Only delete if we're within the line bounds
1196
- if col >= start_col
1197
- # Move to end column
1198
- while col < end_col && !end_of_line?
1199
- forward_char
1200
- col = 1 + display_width(substring(line_start, @point))
1201
- end
1202
- end_pos = @point
1203
-
1204
- # Delete the rectangle text for this line
1205
- if end_pos > start_pos
1206
- delete_region(start_pos, end_pos)
1207
- end
1208
- end
1209
- end
1210
- end
1211
- end
1212
-
1213
- def yank_rectangle
1214
- raise "No rectangle in kill ring" if @@killed_rectangle.nil?
1215
- lines = @@killed_rectangle
1216
- start_line, start_col = get_line_and_column(@point)
1217
-
1218
- save_excursion do
1219
- lines.each_with_index do |line, i|
1220
- goto_line(start_line + i)
1221
- beginning_of_line
1222
- line_start = @point
1223
-
1224
- # Move to start column, extending line if necessary
1225
- col = 1
1226
- while col < start_col && !end_of_line?
1227
- forward_char
1228
- col = 1 + display_width(substring(line_start, @point))
1229
- end
1230
-
1231
- # If line is shorter than start_col, extend it with spaces
1232
- if col < start_col
1233
- insert(" " * (start_col - col))
1234
- end
1235
-
1236
- # Insert the rectangle line
1237
- insert(line)
1238
- end
1239
- end
1240
- end
1241
-
1242
- def open_rectangle(s = @point, e = mark)
1243
- check_read_only_flag
1244
- start_line, start_col, end_line, end_col = rectangle_boundaries(s, e)
1245
- width = end_col - start_col
1246
-
1247
- save_excursion do
1248
- (start_line..end_line).each do |line_num|
1249
- goto_line(line_num)
1250
- beginning_of_line
1251
- line_start = @point
1252
-
1253
- # Move to start column, extending line if necessary
1254
- col = 1
1255
- while col < start_col && !end_of_line?
1256
- forward_char
1257
- col = 1 + display_width(substring(line_start, @point))
1258
- end
1259
-
1260
- # If line is shorter than start_col, extend it with spaces
1261
- if col < start_col
1262
- insert(" " * (start_col - col))
1263
- end
1264
-
1265
- # Insert spaces to create the rectangle
1266
- insert(" " * width)
1267
- end
1268
- end
1269
- end
1270
-
1271
1093
  def undo
1272
1094
  undo_or_redo(:undo, @undo_stack, @redo_stack)
1273
1095
  end
@@ -1276,7 +1098,7 @@ module Textbringer
1276
1098
  undo_or_redo(:redo, @redo_stack, @undo_stack)
1277
1099
  end
1278
1100
 
1279
- def re_search_forward(s, raise_error: true, count: 1)
1101
+ def re_search_forward(s, raise_error: true, goto_beginning: false, count: 1)
1280
1102
  if count < 0
1281
1103
  return re_search_backward(s, raise_error: raise_error, count: -count)
1282
1104
  end
@@ -1291,7 +1113,7 @@ module Textbringer
1291
1113
  return nil
1292
1114
  end
1293
1115
  end
1294
- pos = match_end(0)
1116
+ pos = goto_beginning ? match_beginning(0) : match_end(0)
1295
1117
  end
1296
1118
  goto_char(pos)
1297
1119
  end
@@ -1435,14 +1257,14 @@ module Textbringer
1435
1257
  end
1436
1258
 
1437
1259
  def composite_edit
1260
+ location = @point
1438
1261
  @composite_edit_level += 1
1439
1262
  begin
1440
1263
  yield
1441
1264
  ensure
1442
1265
  @composite_edit_level -= 1
1443
1266
  if @composite_edit_level == 0 && !@composite_edit_actions.empty?
1444
- action = CompositeAction.new(self,
1445
- @composite_edit_actions.first.location)
1267
+ action = CompositeAction.new(self, location)
1446
1268
  @composite_edit_actions.each do |i|
1447
1269
  action.add_action(i)
1448
1270
  end
@@ -1929,7 +1751,6 @@ module Textbringer
1929
1751
  end
1930
1752
 
1931
1753
  KILL_RING = Ring.new
1932
- @@killed_rectangle = nil
1933
1754
 
1934
1755
  class UndoableAction
1935
1756
  attr_accessor :version
@@ -2006,6 +1827,7 @@ module Textbringer
2006
1827
  @actions.reverse_each do |action|
2007
1828
  action.undo
2008
1829
  end
1830
+ @buffer.goto_char(@location)
2009
1831
  end
2010
1832
 
2011
1833
  def redo
@@ -129,31 +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
132
  define_command(:transpose_chars,
158
133
  doc: "Transpose characters.") do
159
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
@@ -208,6 +208,9 @@ module Textbringer
208
208
  GLOBAL_MAP.define_key("\C-xrd", :delete_rectangle)
209
209
  GLOBAL_MAP.define_key("\C-xry", :yank_rectangle)
210
210
  GLOBAL_MAP.define_key("\C-xro", :open_rectangle)
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)
211
214
  GLOBAL_MAP.define_key("\C-x(", :start_keyboard_macro)
212
215
  GLOBAL_MAP.define_key(:f3, :start_keyboard_macro)
213
216
  GLOBAL_MAP.define_key("\C-x)", :end_keyboard_macro)
@@ -1,3 +1,3 @@
1
1
  module Textbringer
2
- VERSION = "12"
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"
data/textbringer.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  # coding: utf-8
2
2
  lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'textbringer/version'
4
+ require_relative 'lib/textbringer/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "textbringer"
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
14
14
  spec.homepage = "https://github.com/shugo/textbringer"
15
15
  spec.license = "MIT"
16
16
 
17
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^((test|spec|features|logo)/|\.)}) }
18
18
  spec.bindir = "exe"
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ["lib"]
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: '12'
4
+ version: '14'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shugo Maeda
@@ -328,14 +328,6 @@ executables:
328
328
  extensions: []
329
329
  extra_rdoc_files: []
330
330
  files:
331
- - ".editorconfig"
332
- - ".gitattributes"
333
- - ".github/dependabot.yml"
334
- - ".github/workflows/macos.yml"
335
- - ".github/workflows/push_gem.yml"
336
- - ".github/workflows/ubuntu.yml"
337
- - ".github/workflows/windows.yml"
338
- - ".gitignore"
339
331
  - CHANGES.md
340
332
  - Gemfile
341
333
  - LICENSE.txt
@@ -360,8 +352,10 @@ files:
360
352
  - lib/textbringer/commands/help.rb
361
353
  - lib/textbringer/commands/input_method.rb
362
354
  - lib/textbringer/commands/isearch.rb
355
+ - lib/textbringer/commands/ispell.rb
363
356
  - lib/textbringer/commands/keyboard_macro.rb
364
357
  - lib/textbringer/commands/misc.rb
358
+ - lib/textbringer/commands/rectangle.rb
365
359
  - lib/textbringer/commands/register.rb
366
360
  - lib/textbringer/commands/replace.rb
367
361
  - lib/textbringer/commands/server.rb
@@ -397,8 +391,6 @@ files:
397
391
  - lib/textbringer/version.rb
398
392
  - lib/textbringer/window.rb
399
393
  - lib/textbringer/window/fallback_characters.rb
400
- - logo/logo.jpg
401
- - logo/logo.png
402
394
  - screenshot.png
403
395
  - textbringer.gemspec
404
396
  homepage: https://github.com/shugo/textbringer
@@ -419,7 +411,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
419
411
  - !ruby/object:Gem::Version
420
412
  version: '0'
421
413
  requirements: []
422
- rubygems_version: 3.6.7
414
+ rubygems_version: 3.6.9
423
415
  specification_version: 4
424
416
  summary: An Emacs-like text editor
425
417
  test_files: []
data/.editorconfig DELETED
@@ -1,12 +0,0 @@
1
- root = true
2
-
3
- [*]
4
- end_of_line = lf
5
-
6
- [*.rb]
7
- indent_style = space
8
- indent_size = 2
9
-
10
- [lib/**.rb]
11
- trim_trailing_whitespace = true
12
- insert_final_newline = true
data/.gitattributes DELETED
@@ -1 +0,0 @@
1
- *.rb diff=ruby
@@ -1,6 +0,0 @@
1
- version: 2
2
- updates:
3
- - package-ecosystem: 'github-actions'
4
- directory: '/'
5
- schedule:
6
- interval: 'weekly'
@@ -1,23 +0,0 @@
1
- name: macos
2
-
3
- on: [push]
4
-
5
- jobs:
6
- test:
7
- runs-on: macos-latest
8
- strategy:
9
- matrix:
10
- ruby: [ head, 3.4 ]
11
- timeout-minutes: 10
12
- env:
13
- RUBYOPT: --enable-frozen-string-literal --debug-frozen-string-literal
14
- steps:
15
- - uses: actions/checkout@v4
16
- - uses: ruby/setup-ruby@v1
17
- with:
18
- ruby-version: ${{ matrix.ruby }}
19
- - name: Install dependencies
20
- run: |
21
- bundle install
22
- - name: Run test
23
- run: bundle exec rake test
@@ -1,54 +0,0 @@
1
- name: Publish gem to rubygems.org
2
-
3
- on:
4
- push:
5
- tags:
6
- - 'v*'
7
-
8
- permissions:
9
- contents: read
10
-
11
- jobs:
12
- push:
13
- if: github.repository == 'shugo/textbringer'
14
- runs-on: ubuntu-latest
15
-
16
- environment:
17
- name: rubygems.org
18
- url: https://rubygems.org/gems/textbringer
19
-
20
- permissions:
21
- contents: write
22
- id-token: write
23
-
24
- steps:
25
- # Set up
26
- - name: Harden Runner
27
- uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
28
- with:
29
- egress-policy: audit
30
-
31
- - uses: actions/checkout@v4
32
-
33
- - name: Set up Ruby
34
- uses: ruby/setup-ruby@v1
35
- with:
36
- bundler-cache: true
37
- ruby-version: ruby
38
-
39
- # Release
40
- - name: Publish to RubyGems
41
- uses: rubygems/release-gem@v1
42
-
43
- - name: Create GitHub release
44
- run: |
45
- tag_name="$(git describe --tags --abbrev=0)"
46
- gh release create "${tag_name}" --verify-tag --generate-notes
47
- env:
48
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
49
-
50
- - name: Purge GitHub cache
51
- shell: bash
52
- run: |
53
- urls=$(curl -sL "https://github.com/${{ github.repository_owner }}/${{ github.event.repository.name }}/tree/main" | grep -Eo "(http|https)://camo.githubusercontent.com[a-zA-Z0-9./?=_%:-]*")
54
- while IFS= read -r url; do echo -e "\nPurge $url"; curl -X PURGE "$url"; done <<< "$urls"
@@ -1,24 +0,0 @@
1
- name: ubuntu
2
-
3
- on: [push]
4
-
5
- jobs:
6
- test:
7
- runs-on: ubuntu-latest
8
- strategy:
9
- matrix:
10
- ruby: [ head, 3.4, 3.3, 3.2 ]
11
- timeout-minutes: 10
12
- env:
13
- RUBYOPT: --enable-frozen-string-literal --debug-frozen-string-literal
14
- steps:
15
- - uses: actions/checkout@v4
16
- - uses: ruby/setup-ruby@v1
17
- with:
18
- ruby-version: ${{ matrix.ruby }}
19
- - name: Install dependencies
20
- run: |
21
- sudo apt install libncursesw5-dev
22
- bundle install
23
- - name: Run test
24
- run: xvfb-run bundle exec rake test
@@ -1,22 +0,0 @@
1
- name: windows
2
-
3
- on: [push]
4
-
5
- jobs:
6
- test:
7
- runs-on: windows-latest
8
- strategy:
9
- matrix:
10
- ruby: [ '3.4' ]
11
- timeout-minutes: 10
12
- env:
13
- RUBYOPT: --enable-frozen-string-literal --debug-frozen-string-literal
14
- steps:
15
- - uses: actions/checkout@v4
16
- - name: Set up Ruby
17
- uses: ruby/setup-ruby@v1
18
- with:
19
- ruby-version: ${{ matrix.ruby }}
20
- bundler-cache: true
21
- - name: Run test
22
- run: bundle exec rake test
data/.gitignore DELETED
@@ -1,10 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
- /tags
data/logo/logo.jpg DELETED
Binary file
data/logo/logo.png DELETED
Binary file