textbringer 1.0.9 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
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