textbringer 1.0.9 → 1.1.2

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: 42dc4dbbf6e190e38d32657942af39c2bf5afec5035a6674d54a7d621c755086
4
- data.tar.gz: dbaa67ad2fca2b24d661610e7467b94022da87928473a2a37a7169deda7a27ac
3
+ metadata.gz: 9641c360dd5bb038429536d85e666ff3518e37413eeece13898fa1a730665193
4
+ data.tar.gz: e2e9f71f90e060e30aac0bfa273fe65349eacc633f83c7c6dda034aa160436c6
5
5
  SHA512:
6
- metadata.gz: c765b54dc47b490c8fe9d53b84b00734281a1d5e5e22714eaba7c9a69c71480271fb0b34a2883fed4f7ad4be1fc2933871e1c0fe5585e16e12b87f569a8ee662
7
- data.tar.gz: '0684345317fcf495d5cf0f7951e120f88f24f025f8c14960dac33874c131f70f2c6a24e1ee6b8b8774783deea8b80c9047a063a8f0fe79666824578e5e54f5dc'
6
+ metadata.gz: 285df14ab50ac3b27e5c61a3c24503c7ce724b84a918072ada9e920c323f94c46564b329a3217950bddeee5e1c4b5e83f426ad577bb8cb6596004c2b374fd12d
7
+ data.tar.gz: 6ed66854359cdd89310f77cb79c2b14cdafe4b5c7636aee662c7d1a4103563388e183bf11b5b7aab45fd691b70fa16c5d33f66231f109c6a733d05c98f1087e2
@@ -5,6 +5,7 @@ on: [push]
5
5
  jobs:
6
6
  build:
7
7
  runs-on: macos-latest
8
+ timeout-minutes: 10
8
9
  steps:
9
10
  - uses: actions/checkout@master
10
11
  - name: Install dependencies
@@ -6,8 +6,9 @@ jobs:
6
6
  test:
7
7
  strategy:
8
8
  matrix:
9
- ruby: [ head, 3.0, 2.7, 2.6 ]
9
+ ruby: [ head, 3.1, 3.0, 2.7, 2.6 ]
10
10
  runs-on: ubuntu-latest
11
+ timeout-minutes: 10
11
12
  steps:
12
13
  - uses: actions/checkout@v2
13
14
  - uses: ruby/setup-ruby@v1
@@ -20,6 +21,3 @@ jobs:
20
21
  bundle install
21
22
  - name: Run test
22
23
  run: xvfb-run bundle exec rake test
23
- env:
24
- UPLOAD_TO_CODECOV: 1
25
- CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}}
@@ -8,6 +8,7 @@ jobs:
8
8
  strategy:
9
9
  matrix:
10
10
  ruby: [ 'mingw', 'mswin', '3.0', '2.7', '2.6' ]
11
+ timeout-minutes: 10
11
12
  steps:
12
13
  - uses: actions/checkout@v2
13
14
  - name: Set up Ruby
data/CHANGES.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## 1.1.2
2
+
3
+ * Buf fixes.
4
+
5
+ ## 1.1.1
6
+
7
+ * Bug fixes.
8
+
9
+ ## 1.1.0
10
+
11
+ * Show unsaved buffers on exit
12
+ * Bug fixes.
13
+
1
14
  ## 1.0.9
2
15
 
3
16
  * Remove mazegaki.dic and bushu.rev
data/README.md CHANGED
@@ -4,7 +4,6 @@
4
4
  [![ubuntu](https://github.com/shugo/textbringer/workflows/ubuntu/badge.svg)](https://github.com/shugo/textbringer/actions?query=workflow%3Aubuntu)
5
5
  [![windows](https://github.com/shugo/textbringer/workflows/windows/badge.svg)](https://github.com/shugo/textbringer/actions?query=workflow%3Awindows)
6
6
  [![macos](https://github.com/shugo/textbringer/workflows/macos/badge.svg)](https://github.com/shugo/textbringer/actions?query=workflow%3Amacos)
7
- [![codecov](https://codecov.io/gh/shugo/textbringer/branch/master/graph/badge.svg)](https://codecov.io/gh/shugo/textbringer)
8
7
 
9
8
  Textbringer is a member of a demon race that takes on the form of an Emacs-like
10
9
  text editor.
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ # Usage: merge_mazegaki_dic /path/to/mazegaki.dic /path/to/skkdic/SKK-JISYO.* > ~/.textbringer/tcode/mazegaki.dic
4
+
3
5
  MAZEGAKI_DIC = Hash.new([])
4
6
 
5
7
  ARGF.each_line do |line|
data/exe/textbringer CHANGED
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require "warning"
4
+
5
+ Warning.ignore(/already initialized constant /)
6
+ Warning.ignore(/previous definition of /)
7
+
3
8
  require "textbringer"
4
9
 
5
10
  include Textbringer
@@ -13,8 +18,6 @@ def load_user_config(path)
13
18
  end
14
19
  end
15
20
 
16
- $VERBOSE = nil
17
-
18
21
  unless STDIN.tty?
19
22
  STDERR.puts("textbringer: standard input is not a tty")
20
23
  exit 1
@@ -58,6 +58,8 @@ module Textbringer
58
58
  end
59
59
  }
60
60
 
61
+ STRING_HAS_BYTE_BASED_METHODS = String.instance_methods.include?(:bytesplice)
62
+
61
63
  if !defined?(@@detect_encoding_proc)
62
64
  @@detect_encoding_proc = DEFAULT_DETECT_ENCODING
63
65
 
@@ -268,7 +270,14 @@ module Textbringer
268
270
 
269
271
  def file_encoding=(enc)
270
272
  @file_encoding = Encoding.find(enc)
271
- @binary = enc == Encoding::ASCII_8BIT
273
+ @binary = @file_encoding == Encoding::ASCII_8BIT
274
+ if STRING_HAS_BYTE_BASED_METHODS
275
+ if @binary
276
+ @contents.force_encoding(Encoding::ASCII_8BIT)
277
+ else
278
+ @contents.force_encoding(Encoding::UTF_8)
279
+ end
280
+ end
272
281
  end
273
282
 
274
283
  def binary?
@@ -437,20 +446,26 @@ module Textbringer
437
446
  end
438
447
 
439
448
  def to_s
440
- result = (@contents[0...@gap_start] + @contents[@gap_end..-1])
441
- result.force_encoding(Encoding::UTF_8) unless @binary
449
+ result = @contents.byteslice(0...@gap_start) +
450
+ @contents.byteslice(@gap_end..-1)
451
+ if !@binary && !STRING_HAS_BYTE_BASED_METHODS
452
+ result.force_encoding(Encoding::UTF_8)
453
+ end
442
454
  result
443
455
  end
444
456
 
445
457
  def substring(s, e)
446
458
  result =
447
459
  if s > @gap_start || e <= @gap_start
448
- @contents[user_to_gap(s)...user_to_gap(e)]
460
+ @contents.byteslice(user_to_gap(s)...user_to_gap(e))
449
461
  else
450
462
  len = @gap_start - s
451
- @contents[user_to_gap(s), len] + @contents[@gap_end, e - s - len]
463
+ @contents.byteslice(user_to_gap(s), len) +
464
+ @contents.byteslice(@gap_end, e - s - len)
452
465
  end
453
- result.force_encoding(Encoding::UTF_8) unless @binary
466
+ if !@binary && !STRING_HAS_BYTE_BASED_METHODS
467
+ result.force_encoding(Encoding::UTF_8)
468
+ end
454
469
  result
455
470
  end
456
471
 
@@ -459,7 +474,7 @@ module Textbringer
459
474
  @contents.byteslice(location)
460
475
  else
461
476
  @contents.byteslice(location + gap_size)
462
- end
477
+ end&.force_encoding(Encoding::ASCII_8BIT)
463
478
  end
464
479
 
465
480
  def byte_before(location = @point)
@@ -506,11 +521,19 @@ module Textbringer
506
521
  end
507
522
 
508
523
  def get_line_and_column(pos)
509
- line = 1 + @contents[0...user_to_gap(pos)].count("\n")
524
+ line = 1 + @contents.byteslice(0...user_to_gap(pos)).count("\n")
510
525
  if pos == point_min
511
526
  column = 1
512
527
  else
513
- i = @contents.rindex("\n", user_to_gap(pos - 1))
528
+ if STRING_HAS_BYTE_BASED_METHODS
529
+ begin
530
+ i = @contents.byterindex("\n", user_to_gap(get_pos(pos, -1)))
531
+ rescue RangeError
532
+ i = nil
533
+ end
534
+ else
535
+ i = @contents.rindex("\n", user_to_gap(pos - 1))
536
+ end
514
537
  if i
515
538
  i += 1
516
539
  else
@@ -539,7 +562,11 @@ module Textbringer
539
562
  pos = point_min
540
563
  i = 1
541
564
  while i < n && pos < @contents.bytesize
542
- pos = @contents.index("\n", pos)
565
+ if STRING_HAS_BYTE_BASED_METHODS
566
+ pos = @contents.byteindex("\n", pos)
567
+ else
568
+ pos = @contents.index("\n", pos)
569
+ end
543
570
  break if pos.nil?
544
571
  i += 1
545
572
  pos += 1
@@ -552,11 +579,14 @@ module Textbringer
552
579
 
553
580
  def insert(x, merge_undo = false)
554
581
  s = x.to_s
582
+ if !binary? && !s.valid_encoding?
583
+ raise EditorError, "Invalid encoding: #{s.dump}"
584
+ end
555
585
  check_read_only_flag
556
586
  pos = @point
557
587
  size = s.bytesize
558
588
  adjust_gap(size)
559
- @contents[@point, size] = s.b
589
+ splice_contents(@point, size, STRING_HAS_BYTE_BASED_METHODS ? s : s.b)
560
590
  @marks.each do |m|
561
591
  if m.location > @point
562
592
  m.location += size
@@ -604,7 +634,7 @@ module Textbringer
604
634
  if n > 0
605
635
  str = substring(s, pos)
606
636
  # fill the gap with NUL to avoid invalid byte sequence in UTF-8
607
- @contents[@gap_end...user_to_gap(pos)] = "\0" * (pos - @point)
637
+ splice_contents(@gap_end...user_to_gap(pos), "\0" * (pos - @point))
608
638
  @gap_end += pos - @point
609
639
  @marks.each do |m|
610
640
  if m.location > pos
@@ -619,7 +649,7 @@ module Textbringer
619
649
  str = substring(pos, s)
620
650
  update_line_and_column(@point, pos)
621
651
  # fill the gap with NUL to avoid invalid byte sequence in UTF-8
622
- @contents[user_to_gap(pos)...@gap_start] = "\0" * (@point - pos)
652
+ splice_contents(user_to_gap(pos)...@gap_start, "\0" * (@point - pos))
623
653
  @marks.each do |m|
624
654
  if m.location >= @point
625
655
  m.location -= @point - pos
@@ -941,7 +971,7 @@ module Textbringer
941
971
  adjust_gap
942
972
  len = e - s
943
973
  # fill the gap with NUL to avoid invalid byte sequence in UTF-8
944
- @contents[@gap_end, len] = "\0" * len
974
+ splice_contents(@gap_end, len, "\0" * len)
945
975
  @gap_end += len
946
976
  @marks.each do |m|
947
977
  if m.location > e
@@ -966,6 +996,9 @@ module Textbringer
966
996
  def clear
967
997
  check_read_only_flag
968
998
  @contents = +""
999
+ if binary? || !STRING_HAS_BYTE_BASED_METHODS
1000
+ @contents.force_encoding(Encoding::ASCII_8BIT)
1001
+ end
969
1002
  @point = @gap_start = @gap_end = 0
970
1003
  @marks.each do |m|
971
1004
  m.location = 0
@@ -1125,49 +1158,71 @@ module Textbringer
1125
1158
  byteindex(true, r, @point) == @point
1126
1159
  end
1127
1160
 
1128
- def byteindex(forward, re, pos)
1129
- @match_offsets = []
1130
- method = forward ? :index : :rindex
1131
- adjust_gap(0, point_max)
1132
- s = @contents[0...@gap_start]
1133
- if @binary
1134
- offset = pos
1135
- else
1136
- offset = s.byteslice(0, pos).force_encoding(Encoding::UTF_8).size
1137
- s.force_encoding(Encoding::UTF_8)
1138
- end
1139
- begin
1140
- i = s.send(method, re, offset)
1161
+ if STRING_HAS_BYTE_BASED_METHODS
1162
+ def byteindex(forward, re, pos)
1163
+ @match_offsets = []
1164
+ method = forward ? :byteindex : :byterindex
1165
+ adjust_gap(0, 0)
1166
+ s = @contents.byteslice(@gap_end..-1)
1167
+ unless binary?
1168
+ s.force_encoding(Encoding::UTF_8)
1169
+ end
1170
+ i = s.send(method, re, pos)
1141
1171
  if i
1142
1172
  m = Regexp.last_match
1143
- if m.nil?
1144
- # A bug of rindex
1145
- @match_offsets.push([pos, pos])
1146
- pos
1147
- else
1148
- b = m.pre_match.bytesize
1149
- e = b + m.to_s.bytesize
1150
- if e <= bytesize
1151
- @match_offsets.push([b, e])
1152
- match_beg = m.begin(0)
1153
- match_str = m.to_s
1154
- (1 .. m.size - 1).each do |j|
1155
- cb, ce = m.offset(j)
1156
- if cb.nil?
1157
- @match_offsets.push([nil, nil])
1158
- else
1159
- bb = b + match_str[0, cb - match_beg].bytesize
1160
- be = b + match_str[0, ce - match_beg].bytesize
1161
- @match_offsets.push([bb, be])
1173
+ (0 .. m.size - 1).each do |j|
1174
+ @match_offsets.push(m.byteoffset(j))
1175
+ end
1176
+ i
1177
+ else
1178
+ nil
1179
+ end
1180
+ end
1181
+ else
1182
+ def byteindex(forward, re, pos)
1183
+ @match_offsets = []
1184
+ method = forward ? :index : :rindex
1185
+ adjust_gap(0, 0)
1186
+ s = @contents[@gap_end..-1]
1187
+ if @binary
1188
+ offset = pos
1189
+ else
1190
+ offset = s.byteslice(0, pos).force_encoding(Encoding::UTF_8).size
1191
+ s.force_encoding(Encoding::UTF_8)
1192
+ end
1193
+ begin
1194
+ i = s.send(method, re, offset)
1195
+ if i
1196
+ m = Regexp.last_match
1197
+ if m.nil?
1198
+ # A bug of rindex
1199
+ @match_offsets.push([pos, pos])
1200
+ pos
1201
+ else
1202
+ b = m.pre_match.bytesize
1203
+ e = b + m.to_s.bytesize
1204
+ if e <= bytesize
1205
+ @match_offsets.push([b, e])
1206
+ match_beg = m.begin(0)
1207
+ match_str = m.to_s
1208
+ (1 .. m.size - 1).each do |j|
1209
+ cb, ce = m.offset(j)
1210
+ if cb.nil?
1211
+ @match_offsets.push([nil, nil])
1212
+ else
1213
+ bb = b + match_str[0, cb - match_beg].bytesize
1214
+ be = b + match_str[0, ce - match_beg].bytesize
1215
+ @match_offsets.push([bb, be])
1216
+ end
1162
1217
  end
1218
+ b
1219
+ else
1220
+ nil
1163
1221
  end
1164
- b
1165
- else
1166
- nil
1167
1222
  end
1223
+ else
1224
+ nil
1168
1225
  end
1169
- else
1170
- nil
1171
1226
  end
1172
1227
  end
1173
1228
  end
@@ -1248,7 +1303,7 @@ module Textbringer
1248
1303
  end
1249
1304
 
1250
1305
  def gap_filled_with_nul?
1251
- @contents[@gap_start...@gap_end]&.match?(/\A\0*\z/)
1306
+ @contents.byteslice(@gap_start...@gap_end)&.match?(/\A\0*\z/)
1252
1307
  end
1253
1308
 
1254
1309
  def composite_edit
@@ -1425,40 +1480,79 @@ module Textbringer
1425
1480
  else
1426
1481
  @contents = s.encode(Encoding::UTF_8)
1427
1482
  end
1428
- @contents.force_encoding(Encoding::ASCII_8BIT)
1483
+ if !STRING_HAS_BYTE_BASED_METHODS
1484
+ @contents.force_encoding(Encoding::ASCII_8BIT)
1485
+ end
1429
1486
  self.file_encoding = enc
1430
- case @contents
1431
- when /(?<!\r)\n/
1487
+ begin
1488
+ case @contents
1489
+ when /(?<!\r)\n/
1490
+ @file_format = :unix
1491
+ when /\r(?!\n)/
1492
+ @file_format = :mac
1493
+ @contents.gsub!(/\r/, "\n")
1494
+ when /\r\n/
1495
+ @file_format = :dos
1496
+ @contents.gsub!(/\r/, "")
1497
+ else
1498
+ @file_format = CONFIG[:default_file_format]
1499
+ end
1500
+ rescue ArgumentError
1432
1501
  @file_format = :unix
1433
- when /\r(?!\n)/
1434
- @file_format = :mac
1435
- @contents.gsub!(/\r/, "\n")
1436
- when /\r\n/
1437
- @file_format = :dos
1438
- @contents.gsub!(/\r/, "")
1439
- else
1440
- @file_format = CONFIG[:default_file_format]
1502
+ end
1503
+ end
1504
+
1505
+ if STRING_HAS_BYTE_BASED_METHODS
1506
+ def splice_contents(*args)
1507
+ @contents.bytesplice(*args)
1508
+ end
1509
+ else
1510
+ def splice_contents(*args)
1511
+ @contents.[]=(*args)
1441
1512
  end
1442
1513
  end
1443
1514
 
1444
1515
  def adjust_gap(min_size = 0, pos = @point)
1445
1516
  if @gap_start < pos
1446
1517
  len = user_to_gap(pos) - @gap_end
1447
- @contents[@gap_start, len] = @contents[@gap_end, len]
1448
- @gap_start += len
1449
- @gap_end += len
1518
+ s = @contents.byteslice(@gap_end, len)
1519
+ new_gap_start = @gap_start + len
1520
+ new_gap_end = @gap_end + len
1521
+ nul_filling_start = new_gap_start > @gap_end ? new_gap_start : @gap_end
1522
+ unless @binary
1523
+ # find the character boundary
1524
+ while nul_filling_start > @gap_end &&
1525
+ @contents.byteslice(nul_filling_start)&.b&.match?(/[\x80-\xbf]/n)
1526
+ nul_filling_start -= 1
1527
+ end
1528
+ end
1529
+ splice_contents(nul_filling_start...new_gap_end,
1530
+ "\0" * (new_gap_end - nul_filling_start))
1531
+ splice_contents(@gap_start...new_gap_start, s)
1532
+ @gap_start = new_gap_start
1533
+ @gap_end = new_gap_end
1450
1534
  elsif @gap_start > pos
1451
1535
  len = @gap_start - pos
1452
- @contents[@gap_end - len, len] = @contents[pos, len]
1453
- @gap_start -= len
1454
- @gap_end -= len
1536
+ s = @contents.byteslice(pos, len)
1537
+ new_gap_start = @gap_start - len
1538
+ new_gap_end = @gap_end - len
1539
+ nul_filling_end = new_gap_end < @gap_start ? new_gap_end : @gap_start
1540
+ unless @binary
1541
+ # find the character boundary
1542
+ while nul_filling_end < @gap_start &&
1543
+ @contents.byteslice(nul_filling_end)&.b&.match?(/[\x80-\xbf]/n)
1544
+ nul_filling_end += 1
1545
+ end
1546
+ end
1547
+ splice_contents(pos...nul_filling_end, "\0" * (nul_filling_end - pos))
1548
+ splice_contents(new_gap_end...@gap_end, s)
1549
+ @gap_start = new_gap_start
1550
+ @gap_end = new_gap_end
1455
1551
  end
1456
- # fill the gap with NUL to avoid invalid byte sequence in UTF-8
1457
- @contents[@gap_start...@gap_end] = "\0" * (@gap_end - @gap_start)
1458
1552
  if gap_size < min_size
1459
1553
  new_gap_size = GAP_SIZE + min_size
1460
1554
  extended_size = new_gap_size - gap_size
1461
- @contents[@gap_end, 0] = "\0" * extended_size
1555
+ splice_contents(@gap_end, 0, "\0" * extended_size)
1462
1556
  @gap_end += extended_size
1463
1557
  end
1464
1558
  end
@@ -1520,12 +1614,20 @@ module Textbringer
1520
1614
  def update_line_and_column(pos, new_pos)
1521
1615
  return if @save_point_level > 0
1522
1616
  if pos < new_pos
1523
- n = @contents[user_to_gap(pos)...user_to_gap(new_pos)].count("\n")
1617
+ n = @contents.byteslice(user_to_gap(pos)...user_to_gap(new_pos)).count("\n")
1524
1618
  if n == 0
1525
1619
  @current_column += substring(pos, new_pos).size
1526
1620
  else
1527
1621
  @current_line += n
1528
- i = @contents.rindex("\n", user_to_gap(new_pos - 1))
1622
+ if STRING_HAS_BYTE_BASED_METHODS
1623
+ begin
1624
+ i = @contents.byterindex("\n", user_to_gap(get_pos(new_pos, -1)))
1625
+ rescue RangeError
1626
+ i = nil
1627
+ end
1628
+ else
1629
+ i = @contents.rindex("\n", user_to_gap(new_pos - 1))
1630
+ end
1529
1631
  if i
1530
1632
  i += 1
1531
1633
  else
@@ -1534,12 +1636,20 @@ module Textbringer
1534
1636
  @current_column = 1 + substring(gap_to_user(i), new_pos).size
1535
1637
  end
1536
1638
  elsif pos > new_pos
1537
- n = @contents[user_to_gap(new_pos)...user_to_gap(pos)].count("\n")
1639
+ n = @contents.byteslice(user_to_gap(new_pos)...user_to_gap(pos)).count("\n")
1538
1640
  if n == 0
1539
1641
  @current_column -= substring(new_pos, pos).size
1540
1642
  else
1541
1643
  @current_line -= n
1542
- i = @contents.rindex("\n", user_to_gap(new_pos - 1))
1644
+ if STRING_HAS_BYTE_BASED_METHODS
1645
+ begin
1646
+ i = @contents.byterindex("\n", user_to_gap(get_pos(new_pos, - 1)))
1647
+ rescue RangeError
1648
+ i = nil
1649
+ end
1650
+ else
1651
+ i = @contents.rindex("\n", user_to_gap(new_pos - 1))
1652
+ end
1543
1653
  if i
1544
1654
  i += 1
1545
1655
  else
@@ -1573,8 +1683,13 @@ module Textbringer
1573
1683
  end
1574
1684
 
1575
1685
  def write_to_file(f)
1576
- [@contents[0...@gap_start], @contents[@gap_end..-1]].each do |s|
1577
- s.force_encoding(Encoding::UTF_8) unless @binary
1686
+ [
1687
+ @contents.byteslice(0...@gap_start),
1688
+ @contents.byteslice(@gap_end..-1)
1689
+ ].each do |s|
1690
+ if !STRING_HAS_BYTE_BASED_METHODS
1691
+ s.force_encoding(Encoding::UTF_8) unless @binary
1692
+ end
1578
1693
  case @file_format
1579
1694
  when :dos
1580
1695
  s.gsub!(/\n/, "\r\n")
@@ -1587,15 +1702,15 @@ module Textbringer
1587
1702
 
1588
1703
  def push_undo(action)
1589
1704
  return if @undoing || @undo_limit == 0
1705
+ if !modified?
1706
+ action.version = @version
1707
+ end
1590
1708
  if @composite_edit_level > 0
1591
1709
  @composite_edit_actions.push(action)
1592
1710
  else
1593
1711
  if @undo_stack.size >= @undo_limit
1594
1712
  @undo_stack[0, @undo_stack.size + 1 - @undo_limit] = []
1595
1713
  end
1596
- if !modified?
1597
- action.version = @version
1598
- end
1599
1714
  @undo_stack.push(action)
1600
1715
  @redo_stack.clear
1601
1716
  end
@@ -72,7 +72,11 @@ module Textbringer
72
72
 
73
73
  define_command(:save_buffer, doc: "Save the current buffer to a file.") do
74
74
  if Buffer.current.file_name.nil?
75
- Buffer.current.file_name = read_file_name("File to save in: ")
75
+ file_name = read_file_name("File to save in: ")
76
+ if File.directory?(file_name)
77
+ file_name = File.expand_path(Buffer.current.name, file_name)
78
+ end
79
+ Buffer.current.file_name = file_name
76
80
  next if Buffer.current.file_name.nil?
77
81
  end
78
82
  if Buffer.current.file_modified?
@@ -138,7 +138,18 @@ module Textbringer
138
138
  end
139
139
  re = Regexp.new(Regexp.quote(ISEARCH_STATUS[:string]), options)
140
140
  last_pos = ISEARCH_STATUS[:last_pos]
141
- offset = forward ? last_pos : last_pos - ISEARCH_STATUS[:string].bytesize
141
+ if forward
142
+ offset = last_pos
143
+ else
144
+ Buffer.current.save_excursion do
145
+ pos = last_pos - ISEARCH_STATUS[:string].bytesize
146
+ goto_char(last_pos)
147
+ while Buffer.current.point > pos
148
+ backward_char
149
+ end
150
+ offset = Buffer.current.point
151
+ end
152
+ end
142
153
  if offset >= 0 && Buffer.current.byteindex(forward, re, offset)
143
154
  if Buffer.current != Buffer.minibuffer
144
155
  message(isearch_prompt + ISEARCH_STATUS[:string], log: false)
@@ -6,7 +6,12 @@ module Textbringer
6
6
  end
7
7
 
8
8
  define_command(:exit_textbringer) do |status = 0|
9
- if Buffer.any? { |buffer| /\A\*/ !~ buffer.name && buffer.modified? }
9
+ unsaved_buffers = Buffer.filter { |buffer|
10
+ /\A\*/ !~ buffer.name && buffer.modified?
11
+ }
12
+ if !unsaved_buffers.empty?
13
+ list_buffers(unsaved_buffers)
14
+ Window.redisplay
10
15
  return unless yes_or_no?("Unsaved buffers exist; exit anyway?")
11
16
  end
12
17
  exit(status)
@@ -120,6 +125,7 @@ module Textbringer
120
125
  define_command(:complete_minibuffer) do
121
126
  minibuffer = Buffer.minibuffer
122
127
  completion_proc = minibuffer[:completion_proc]
128
+ ignore_case = minibuffer[:completion_ignore_case]
123
129
  if completion_proc
124
130
  xs = completion_proc.call(minibuffer.to_s)
125
131
  update_completions(xs)
@@ -131,7 +137,11 @@ module Textbringer
131
137
  s = y.size.downto(1).lazy.map { |i|
132
138
  y[0, i]
133
139
  }.find { |i|
134
- ys.all? { |j| j.start_with?(i) }
140
+ i = i.downcase if ignore_case
141
+ ys.all? { |j|
142
+ j = j.downcase if ignore_case
143
+ j.start_with?(i)
144
+ }
135
145
  }
136
146
  if s
137
147
  complete_minibuffer_with_string(s)
@@ -6,7 +6,7 @@ module Textbringer
6
6
  doc: "Start Textbringer server.") do
7
7
  uri = CONFIG[:server_uri] ||
8
8
  "drbunix:" + File.expand_path("server.sock", "~/.textbringer")
9
- options = CONFIG[:server_options] || { UNIXFileMode: 0600 }
9
+ options = { UNIXFileMode: 0600 }.merge(CONFIG[:server_options] || {})
10
10
  DRb.start_service(uri, Server.new, options)
11
11
  end
12
12
 
@@ -84,7 +84,7 @@ module Textbringer
84
84
  end
85
85
  end
86
86
 
87
- define_command(:list_buffers, doc: <<~EOD) do
87
+ define_command(:list_buffers, doc: <<~EOD) do |buffers = Buffer.list|
88
88
  List the existing buffers.
89
89
  EOD
90
90
  buffer = Buffer.find_or_new("*Buffer List*",
@@ -92,7 +92,7 @@ module Textbringer
92
92
  buffer.apply_mode(BufferListMode)
93
93
  buffer.read_only_edit do
94
94
  buffer.clear
95
- buffer.insert(Buffer.list.map(&:name).join("\n"))
95
+ buffer.insert(buffers.map(&:name).join("\n"))
96
96
  buffer.beginning_of_buffer
97
97
  end
98
98
  switch_to_buffer(buffer)
@@ -16,6 +16,7 @@ module Textbringer
16
16
  shell_command_switch: "-c",
17
17
  grep_command: "grep -nH -e",
18
18
  fill_column: 70,
19
+ read_file_name_completion_ignore_case: RUBY_PLATFORM.match?(/darwin/),
19
20
  default_input_method: "t_code"
20
21
  }
21
22
  end
@@ -55,7 +55,15 @@ module Textbringer
55
55
  @last_key = c
56
56
  @key_sequence << @last_key
57
57
  cmd = key_binding(@key_sequence)
58
- if cmd.is_a?(Symbol) || cmd.respond_to?(:call)
58
+ if cmd.nil?
59
+ keys = Keymap.key_sequence_string(@key_sequence)
60
+ @key_sequence.clear
61
+ @prefix_arg = nil
62
+ message("#{keys} is undefined")
63
+ Window.beep
64
+ elsif cmd.is_a?(Keymap)
65
+ # multi-stroke key binding?
66
+ else
59
67
  @this_command_keys = @key_sequence
60
68
  @key_sequence = []
61
69
  @this_command = cmd
@@ -73,13 +81,6 @@ module Textbringer
73
81
  @last_command = @this_command
74
82
  @this_command = nil
75
83
  end
76
- else
77
- if cmd.nil?
78
- keys = Keymap.key_sequence_string(@key_sequence)
79
- @key_sequence.clear
80
- @prefix_arg = nil
81
- message("#{keys} is undefined")
82
- end
83
84
  end
84
85
  Window.redisplay
85
86
  rescue Exception => e
@@ -276,7 +276,8 @@ module Textbringer
276
276
  (last_event == :on_kw && /\A(and|or)\z/.match?(last_text)) ||
277
277
  last_event == :on_period ||
278
278
  (last_event == :on_comma && event != :on_lbrace &&
279
- event != :on_lparen && event != :on_lbracket)
279
+ event != :on_lparen && event != :on_lbracket) ||
280
+ last_event == :on_label
280
281
  indentation += @buffer[:indent_level]
281
282
  end
282
283
  indentation
@@ -137,7 +137,7 @@ module Textbringer
137
137
  }
138
138
 
139
139
  def read_from_minibuffer(prompt, completion_proc: nil, default: nil,
140
- initial_value: nil,
140
+ initial_value: nil, completion_ignore_case: false,
141
141
  keymap: MINIBUFFER_LOCAL_MAP)
142
142
  if Window.echo_area.active?
143
143
  raise EditorError,
@@ -146,10 +146,12 @@ module Textbringer
146
146
  old_buffer = Buffer.current
147
147
  old_window = Window.current
148
148
  old_completion_proc = Buffer.minibuffer[:completion_proc]
149
+ old_completion_ignore_case = Buffer.minibuffer[:completion_ignore_case]
149
150
  old_current_prefix_arg = Controller.current.current_prefix_arg
150
151
  old_minibuffer_map = Buffer.minibuffer.keymap
151
152
  Buffer.minibuffer.keymap = keymap
152
153
  Buffer.minibuffer[:completion_proc] = completion_proc
154
+ Buffer.minibuffer[:completion_ignore_case] = completion_ignore_case
153
155
  Window.echo_area.active = true
154
156
  begin
155
157
  Window.current = Window.echo_area
@@ -177,6 +179,7 @@ module Textbringer
177
179
  # Just in case old_window has been deleted by resize,
178
180
  # in which case Window.current is set to the first window.
179
181
  Window.current.buffer = Buffer.current = old_buffer
182
+ Buffer.minibuffer[:completion_ignore_case] = old_completion_ignore_case
180
183
  Buffer.minibuffer[:completion_proc] = old_completion_proc
181
184
  Buffer.minibuffer.keymap = old_minibuffer_map
182
185
  Buffer.minibuffer.disable_input_method
@@ -201,8 +204,10 @@ module Textbringer
201
204
  }
202
205
  }
203
206
  initial_value = default&.sub(%r"\A#{Regexp.quote(Dir.pwd)}/", "")
207
+ ignore_case = CONFIG[:read_file_name_completion_ignore_case]
204
208
  file = read_from_minibuffer(prompt, completion_proc: f,
205
- initial_value: initial_value)
209
+ initial_value: initial_value,
210
+ completion_ignore_case: ignore_case)
206
211
  File.expand_path(file)
207
212
  end
208
213
 
@@ -223,8 +228,10 @@ module Textbringer
223
228
  end
224
229
 
225
230
  def read_encoding(prompt, **opts)
231
+ encoding_names = (Encoding.list.map(&:name) + Encoding.aliases.keys).
232
+ map(&:downcase).uniq
226
233
  f = ->(s) {
227
- complete_for_minibuffer(s.upcase, Encoding.list.map(&:name))
234
+ complete_for_minibuffer(s.downcase, encoding_names)
228
235
  }
229
236
  read_from_minibuffer(prompt, completion_proc: f, **opts)
230
237
  end
@@ -1,3 +1,3 @@
1
1
  module Textbringer
2
- VERSION = "1.0.9"
2
+ VERSION = "1.1.2"
3
3
  end
@@ -687,6 +687,8 @@ module Textbringer
687
687
  else
688
688
  "U+%04X" % c.ord
689
689
  end
690
+ rescue ArgumentError
691
+ "0x" + c.unpack("H*")[0]
690
692
  end
691
693
 
692
694
  def escape(s)
data/textbringer.gemspec CHANGED
@@ -22,15 +22,15 @@ Gem::Specification.new do |spec|
22
22
  spec.required_ruby_version = '>= 2.6'
23
23
 
24
24
  spec.add_runtime_dependency "curses", ">= 1.2.7"
25
- spec.add_runtime_dependency "unicode-display_width", "~> 1.1"
26
- spec.add_runtime_dependency "clipboard", "~> 1.1"
25
+ spec.add_runtime_dependency "unicode-display_width", ">= 1.1"
26
+ spec.add_runtime_dependency "clipboard", ">= 1.1"
27
27
  spec.add_runtime_dependency "fiddley", ">= 0.0.5"
28
28
  spec.add_runtime_dependency "editorconfig"
29
+ spec.add_runtime_dependency "warning"
29
30
 
30
31
  spec.add_development_dependency "bundler"
31
- spec.add_development_dependency "rake", "~> 12.0"
32
+ spec.add_development_dependency "rake", ">= 12.0"
32
33
  spec.add_development_dependency "test-unit"
33
34
  spec.add_development_dependency "simplecov"
34
- spec.add_development_dependency "codecov"
35
35
  spec.add_development_dependency "ripper-tags"
36
36
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: textbringer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.9
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shugo Maeda
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-26 00:00:00.000000000 Z
11
+ date: 2022-09-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: curses
@@ -28,28 +28,28 @@ dependencies:
28
28
  name: unicode-display_width
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '1.1'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.1'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: clipboard
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '1.1'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '1.1'
55
55
  - !ruby/object:Gem::Dependency
@@ -81,13 +81,13 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: bundler
84
+ name: warning
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - ">="
88
88
  - !ruby/object:Gem::Version
89
89
  version: '0'
90
- type: :development
90
+ type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
@@ -95,35 +95,35 @@ dependencies:
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
- name: rake
98
+ name: bundler
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - "~>"
101
+ - - ">="
102
102
  - !ruby/object:Gem::Version
103
- version: '12.0'
103
+ version: '0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - "~>"
108
+ - - ">="
109
109
  - !ruby/object:Gem::Version
110
- version: '12.0'
110
+ version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
- name: test-unit
112
+ name: rake
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - ">="
116
116
  - !ruby/object:Gem::Version
117
- version: '0'
117
+ version: '12.0'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
- version: '0'
124
+ version: '12.0'
125
125
  - !ruby/object:Gem::Dependency
126
- name: simplecov
126
+ name: test-unit
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
129
  - - ">="
@@ -137,7 +137,7 @@ dependencies:
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
139
  - !ruby/object:Gem::Dependency
140
- name: codecov
140
+ name: simplecov
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
143
  - - ">="
@@ -258,7 +258,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
258
258
  - !ruby/object:Gem::Version
259
259
  version: '0'
260
260
  requirements: []
261
- rubygems_version: 3.3.0.dev
261
+ rubygems_version: 3.4.0.dev
262
262
  signing_key:
263
263
  specification_version: 4
264
264
  summary: An Emacs-like text editor