textbringer 27 → 28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +3 -1
- data/lib/textbringer/commands/fill.rb +152 -59
- data/lib/textbringer/input_methods/skk_input_method.rb +710 -74
- data/lib/textbringer/mode.rb +19 -0
- data/lib/textbringer/modes/c_mode.rb +6 -0
- data/lib/textbringer/modes/programming_mode.rb +3 -6
- data/lib/textbringer/modes/ruby_mode.rb +18 -3
- data/lib/textbringer/version.rb +1 -1
- metadata +2 -2
|
@@ -5,6 +5,7 @@ require "timeout"
|
|
|
5
5
|
|
|
6
6
|
module Textbringer
|
|
7
7
|
CONFIG[:skk_dictionary_path] = File.expand_path("~/.textbringer/skk/SKK-JISYO.L")
|
|
8
|
+
CONFIG[:skk_user_dictionary_path] = File.expand_path("~/.textbringer/skk/skk-jisyo.utf8")
|
|
8
9
|
CONFIG[:skk_server_host] = nil # nil = disabled (default)
|
|
9
10
|
CONFIG[:skk_server_port] = 1178
|
|
10
11
|
|
|
@@ -123,20 +124,60 @@ module Textbringer
|
|
|
123
124
|
ascii: nil, # nil = reset to terminal default
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
# Mirrors ddskk's skk-kakutei-history: the okuri-nasi yomi confirmed in
|
|
128
|
+
# this session, most recent first, shared by all buffers, and used to
|
|
129
|
+
# complete an empty yomi (skk-comp-by-history).
|
|
130
|
+
KAKUTEI_HISTORY_LIMIT = 100 # skk-kakutei-history-limit
|
|
131
|
+
@@kakutei_history = []
|
|
132
|
+
|
|
133
|
+
def self.kakutei_history
|
|
134
|
+
@@kakutei_history
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Mirrors ddskk's skk-get-jisyo-buffer, which reads a dictionary file
|
|
138
|
+
# into a single buffer shared by every buffer using SKK: the user
|
|
139
|
+
# dictionary is loaded once per path and shared by all instances, so
|
|
140
|
+
# that saving from one buffer does not drop what another one learned.
|
|
141
|
+
@@user_dictionaries = {}
|
|
142
|
+
|
|
143
|
+
# A user dictionary candidate like (skk-ignore-dic-word "留守電"),
|
|
144
|
+
# which ddskk evaluates to hide the quoted words of the system
|
|
145
|
+
# dictionary (see purge_candidate).
|
|
146
|
+
IGNORE_DIC_WORD_RE = /\A\(skk-ignore-dic-word((?: +"[^"]*")+)\)\z/
|
|
147
|
+
|
|
126
148
|
def initialize
|
|
127
149
|
super
|
|
128
150
|
@mode = :hiragana # :hiragana | :katakana | :hankaku_katakana | :zenkaku_ascii | :ascii
|
|
129
151
|
@phase = :normal # :normal | :converting | :selecting
|
|
130
152
|
@roman_buffer = +""
|
|
153
|
+
@okuri_roman = nil # the consonant that started okurigana (dictionary fallback)
|
|
154
|
+
@okuri_start_pos = nil # buffer position of the "*" marker; nil = no okurigana
|
|
155
|
+
# @yomi/@okuri_kana are snapshots taken when entering the selecting
|
|
156
|
+
# phase (see start_selecting); during converting they are derived
|
|
157
|
+
# from the buffer via current_yomi/current_okuri_kana instead.
|
|
131
158
|
@yomi = +""
|
|
132
|
-
@okuri_roman = nil
|
|
133
159
|
@okuri_kana = nil
|
|
134
160
|
@candidates = []
|
|
135
161
|
@candidate_index = 0
|
|
162
|
+
@base_candidates = [] # the system dictionary's candidates for @lookup_key
|
|
136
163
|
@marker_pos = nil
|
|
164
|
+
@lookup_key = nil
|
|
165
|
+
# Yomi completion state: the yomi it started from, the matching
|
|
166
|
+
# user-dictionary keys, and the index of the one currently shown.
|
|
167
|
+
@comp_prefix = nil
|
|
168
|
+
@comp_candidates = nil
|
|
169
|
+
@comp_index = nil
|
|
170
|
+
@comp_message_shown = false
|
|
171
|
+
# Whether the previous key was TAB/Shift-TAB, standing in for ddskk's
|
|
172
|
+
# (eq last-command 'skk-comp-do).
|
|
173
|
+
@last_key_completed = false
|
|
137
174
|
@okuriiari = nil
|
|
138
175
|
@okurinasi = nil
|
|
176
|
+
@user_okuriiari = nil
|
|
177
|
+
@user_okurinasi = nil
|
|
139
178
|
@skk_server_socket = nil
|
|
179
|
+
@prefix_window = nil
|
|
180
|
+
@prefix_buffer = nil
|
|
140
181
|
end
|
|
141
182
|
|
|
142
183
|
def toggle
|
|
@@ -146,6 +187,7 @@ module Textbringer
|
|
|
146
187
|
else
|
|
147
188
|
reset_cursor_color
|
|
148
189
|
close_skk_server
|
|
190
|
+
close_prefix_window
|
|
149
191
|
end
|
|
150
192
|
end
|
|
151
193
|
|
|
@@ -153,6 +195,7 @@ module Textbringer
|
|
|
153
195
|
super
|
|
154
196
|
reset_cursor_color
|
|
155
197
|
close_skk_server
|
|
198
|
+
close_prefix_window
|
|
156
199
|
end
|
|
157
200
|
|
|
158
201
|
def on_activate
|
|
@@ -161,6 +204,7 @@ module Textbringer
|
|
|
161
204
|
|
|
162
205
|
def on_deactivate
|
|
163
206
|
reset_cursor_color if @enabled
|
|
207
|
+
close_prefix_window
|
|
164
208
|
end
|
|
165
209
|
|
|
166
210
|
def status
|
|
@@ -168,16 +212,114 @@ module Textbringer
|
|
|
168
212
|
end
|
|
169
213
|
|
|
170
214
|
def handle_event(event)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
215
|
+
# Hidden first, not to be drawn over what the event inserts: cut in
|
|
216
|
+
# two by it, a wide character would not be shown properly.
|
|
217
|
+
@prefix_window.hide if @prefix_window&.visible?
|
|
218
|
+
result =
|
|
219
|
+
case @phase
|
|
220
|
+
when :normal then handle_normal(event)
|
|
221
|
+
when :converting then handle_converting(event)
|
|
222
|
+
when :selecting then handle_selecting(event)
|
|
223
|
+
end
|
|
224
|
+
update_prefix_window
|
|
225
|
+
result
|
|
176
226
|
end
|
|
177
227
|
|
|
178
228
|
private
|
|
179
229
|
|
|
230
|
+
# Mirrors ddskk's skk-insert-prefix, which shows the unconfirmed
|
|
231
|
+
# romaji at point with an overlay when skk-echo is non-nil (the
|
|
232
|
+
# default). Textbringer has no overlays, so it is shown in a
|
|
233
|
+
# floating window over point instead, which leaves the buffer and
|
|
234
|
+
# its undo history untouched. During conversion the romaji is
|
|
235
|
+
# already in the buffer (see insert_roman_preview).
|
|
236
|
+
def update_prefix_window
|
|
237
|
+
if @enabled && @phase == :normal && !@roman_buffer.empty? &&
|
|
238
|
+
!isearch_mode?
|
|
239
|
+
show_prefix_window
|
|
240
|
+
else
|
|
241
|
+
close_prefix_window
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def show_prefix_window
|
|
246
|
+
window = Window.current
|
|
247
|
+
width = prefix_window_width(window.buffer)
|
|
248
|
+
y, x = prefix_window_position(window, width)
|
|
249
|
+
@prefix_buffer ||= Buffer.new(name: " *skk-prefix*", undo_limit: 0)
|
|
250
|
+
@prefix_buffer.replace(
|
|
251
|
+
@roman_buffer + " " * (width - Buffer.display_width(@roman_buffer))
|
|
252
|
+
)
|
|
253
|
+
if @prefix_window.nil? || @prefix_window.deleted?
|
|
254
|
+
# Like ddskk's skk-prefix-*-face; defined here, after the colors
|
|
255
|
+
# are set up, unless the user has defined it.
|
|
256
|
+
Face.define(:skk_prefix, underline: true) unless Face[:skk_prefix]
|
|
257
|
+
@prefix_window = FloatingWindow.new(1, width, y, x,
|
|
258
|
+
buffer: @prefix_buffer,
|
|
259
|
+
face: :skk_prefix)
|
|
260
|
+
else
|
|
261
|
+
@prefix_window.resize(1, width) if @prefix_window.columns != width
|
|
262
|
+
@prefix_window.move_to(y: y, x: x)
|
|
263
|
+
end
|
|
264
|
+
@prefix_window.show
|
|
265
|
+
Window.redisplay
|
|
266
|
+
# Put the cursor after the romaji, as after characters typed; the
|
|
267
|
+
# next redisplay puts it back at point.
|
|
268
|
+
cursor_x = [x - window.x + Buffer.display_width(@roman_buffer),
|
|
269
|
+
window.columns - 1].min
|
|
270
|
+
window.window.setpos(y - window.y, cursor_x)
|
|
271
|
+
window.window.noutrefresh
|
|
272
|
+
Window.update
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Wide enough to cover the characters after point as a whole: a wide
|
|
276
|
+
# character cut in two by the window would not be shown properly
|
|
277
|
+
# again when the window is gone.
|
|
278
|
+
def prefix_window_width(buffer)
|
|
279
|
+
width = Buffer.display_width(@roman_buffer)
|
|
280
|
+
covered = 0
|
|
281
|
+
buffer.save_excursion do
|
|
282
|
+
while covered < width && !buffer.end_of_buffer? &&
|
|
283
|
+
buffer.char_after != "\n"
|
|
284
|
+
covered += Buffer.display_width(buffer.char_after)
|
|
285
|
+
buffer.forward_char
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
[width, covered].max
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# A window sticking out of the screen is not drawn at all, so romaji
|
|
292
|
+
# that does not fit in the rest of the line goes to the beginning of
|
|
293
|
+
# the next line, where the kana will go. The echo area has no next
|
|
294
|
+
# line, so it is moved to the left there instead.
|
|
295
|
+
def prefix_window_position(window, width)
|
|
296
|
+
y = window.cursor.y
|
|
297
|
+
x = window.cursor.x
|
|
298
|
+
if x + width > window.columns
|
|
299
|
+
if window.echo_area?
|
|
300
|
+
x = [window.columns - width, 0].max
|
|
301
|
+
else
|
|
302
|
+
y += 1
|
|
303
|
+
x = 0
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
[window.y + y, window.x + x]
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def close_prefix_window
|
|
310
|
+
return if @prefix_window.nil?
|
|
311
|
+
@prefix_window.close
|
|
312
|
+
@prefix_window = nil
|
|
313
|
+
end
|
|
314
|
+
|
|
180
315
|
def handle_normal(event)
|
|
316
|
+
# Like ddskk's skk-delete-backward-char, backspace erases the
|
|
317
|
+
# unconfirmed romaji as a whole, leaving the character before it.
|
|
318
|
+
if backspace_event?(event) && !@roman_buffer.empty?
|
|
319
|
+
@roman_buffer = +""
|
|
320
|
+
return nil
|
|
321
|
+
end
|
|
322
|
+
|
|
181
323
|
unless event.is_a?(String)
|
|
182
324
|
@roman_buffer = +""
|
|
183
325
|
return event
|
|
@@ -257,7 +399,39 @@ module Textbringer
|
|
|
257
399
|
end
|
|
258
400
|
|
|
259
401
|
def handle_converting(event)
|
|
402
|
+
# Like ddskk, any key clears the message a completion left behind.
|
|
403
|
+
if @comp_message_shown
|
|
404
|
+
Window.echo_area.clear_message
|
|
405
|
+
@comp_message_shown = false
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# Like ddskk's skk-comp-do setting this-command, only TAB/Shift-TAB
|
|
409
|
+
# keep the flag up for the next key; any other key drops it.
|
|
410
|
+
after_completion = @last_key_completed
|
|
411
|
+
@last_key_completed = false
|
|
412
|
+
|
|
413
|
+
if backspace_event?(event)
|
|
414
|
+
backspace_converting
|
|
415
|
+
return nil
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
# Like skk-insert, "." and "," (skk-next-completion-char and
|
|
419
|
+
# skk-previous-completion-char) move through the candidates only
|
|
420
|
+
# right after a completion, and are typed as "。" and "、" otherwise.
|
|
421
|
+
if event == "\t" || (after_completion && event == ".")
|
|
422
|
+
complete_yomi(after_completion)
|
|
423
|
+
@last_key_completed = true
|
|
424
|
+
return nil
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
if event == :btab || (after_completion && event == ",")
|
|
428
|
+
complete_yomi_backward(after_completion)
|
|
429
|
+
@last_key_completed = true
|
|
430
|
+
return nil
|
|
431
|
+
end
|
|
432
|
+
|
|
260
433
|
unless event.is_a?(String)
|
|
434
|
+
discard_roman_preview
|
|
261
435
|
commit_converting
|
|
262
436
|
return event
|
|
263
437
|
end
|
|
@@ -265,22 +439,34 @@ module Textbringer
|
|
|
265
439
|
# Control characters not handled below: commit conversion and pass through
|
|
266
440
|
if event.bytesize == 1 && (event.ord < 0x20 || event.ord == 0x7f) &&
|
|
267
441
|
event != "\C-g" && event != "\C-j"
|
|
442
|
+
discard_roman_preview
|
|
268
443
|
commit_converting
|
|
269
444
|
return event
|
|
270
445
|
end
|
|
271
446
|
|
|
272
447
|
case event
|
|
273
448
|
when "\C-g"
|
|
274
|
-
|
|
449
|
+
discard_roman_preview
|
|
450
|
+
if after_completion && @comp_prefix
|
|
451
|
+
cancel_completion
|
|
452
|
+
else
|
|
453
|
+
cancel_converting
|
|
454
|
+
end
|
|
275
455
|
nil
|
|
276
456
|
when "\C-j"
|
|
457
|
+
discard_roman_preview
|
|
277
458
|
commit_converting
|
|
278
459
|
nil
|
|
279
460
|
when " "
|
|
461
|
+
discard_roman_preview
|
|
280
462
|
start_selecting
|
|
281
463
|
nil
|
|
464
|
+
when "q"
|
|
465
|
+
toggle_yomi_katakana
|
|
466
|
+
nil
|
|
282
467
|
when /\A[A-Z]\z/
|
|
283
|
-
if @
|
|
468
|
+
if @okuri_start_pos.nil?
|
|
469
|
+
discard_roman_preview
|
|
284
470
|
start_okurigana(event.downcase)
|
|
285
471
|
else
|
|
286
472
|
process_converting_romaji(event.downcase)
|
|
@@ -307,6 +493,9 @@ module Textbringer
|
|
|
307
493
|
when "x"
|
|
308
494
|
prev_candidate
|
|
309
495
|
nil
|
|
496
|
+
when "X"
|
|
497
|
+
purge_candidate
|
|
498
|
+
nil
|
|
310
499
|
when "\r", "\n"
|
|
311
500
|
confirm_selecting
|
|
312
501
|
nil
|
|
@@ -394,36 +583,39 @@ module Textbringer
|
|
|
394
583
|
nil
|
|
395
584
|
end
|
|
396
585
|
|
|
586
|
+
# Mirrors ddskk: the headword and okurigana being composed are just
|
|
587
|
+
# ordinary buffer text between markers ("▽"/"*"), so a confirmed kana
|
|
588
|
+
# is always simply inserted here -- there is no need to decide whether
|
|
589
|
+
# it belongs to the yomi or the okurigana, unlike a design that tracks
|
|
590
|
+
# them in separate string variables.
|
|
397
591
|
def process_converting_romaji(event)
|
|
592
|
+
# Remove the preview of the previously buffered, unconfirmed romaji
|
|
593
|
+
delete_roman_preview
|
|
594
|
+
|
|
398
595
|
# Special "n" handling: flush "ん" before appending if next char won't extend "n"
|
|
399
596
|
if @roman_buffer == "n" && !%w[n y a i u e o].include?(event)
|
|
400
597
|
@roman_buffer = +""
|
|
401
|
-
|
|
598
|
+
insert_kana(converting_kana_for_mode("ん"))
|
|
402
599
|
end
|
|
403
600
|
|
|
404
601
|
@roman_buffer << event
|
|
405
602
|
|
|
406
|
-
|
|
407
|
-
|
|
603
|
+
# ddskk's romaji rules pair each kana with its katakana form and pick
|
|
604
|
+
# one by the current mode (skk-katakana), so a katakana-mode headword
|
|
605
|
+
# is katakana from the first kana onward, not converted afterward.
|
|
606
|
+
table = converting_table
|
|
607
|
+
prefixes = HIRAGANA_PREFIXES
|
|
408
608
|
|
|
409
609
|
kana = table[@roman_buffer]
|
|
410
610
|
if kana
|
|
411
611
|
@roman_buffer = +""
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
@okuri_kana = (@okuri_kana || "") + kana
|
|
415
|
-
with_target_buffer do |buffer|
|
|
416
|
-
buffer.insert(kana)
|
|
417
|
-
end
|
|
418
|
-
Window.redisplay
|
|
419
|
-
start_selecting
|
|
420
|
-
else
|
|
421
|
-
append_yomi_kana(kana)
|
|
422
|
-
end
|
|
612
|
+
insert_kana(kana)
|
|
613
|
+
start_selecting if @okuri_start_pos
|
|
423
614
|
return
|
|
424
615
|
end
|
|
425
616
|
|
|
426
617
|
if prefixes.include?(@roman_buffer)
|
|
618
|
+
insert_roman_preview(@roman_buffer)
|
|
427
619
|
return
|
|
428
620
|
end
|
|
429
621
|
|
|
@@ -432,8 +624,9 @@ module Textbringer
|
|
|
432
624
|
first = @roman_buffer[0]
|
|
433
625
|
rest = @roman_buffer[1..]
|
|
434
626
|
if first == rest[0] && first =~ /[bcdfghjklmnpqrstvwxyz]/
|
|
435
|
-
|
|
627
|
+
insert_kana(converting_kana_for_mode("っ"))
|
|
436
628
|
@roman_buffer = +rest # Keep the second consonant buffered
|
|
629
|
+
insert_roman_preview(@roman_buffer)
|
|
437
630
|
return
|
|
438
631
|
end
|
|
439
632
|
end
|
|
@@ -442,7 +635,7 @@ module Textbringer
|
|
|
442
635
|
if @roman_buffer.size == 1
|
|
443
636
|
char = @roman_buffer
|
|
444
637
|
@roman_buffer = +""
|
|
445
|
-
|
|
638
|
+
insert_kana(char)
|
|
446
639
|
return
|
|
447
640
|
end
|
|
448
641
|
|
|
@@ -450,31 +643,17 @@ module Textbringer
|
|
|
450
643
|
first_char = @roman_buffer[0]
|
|
451
644
|
last_char = @roman_buffer[-1]
|
|
452
645
|
@roman_buffer = +""
|
|
453
|
-
|
|
454
|
-
# Vowel starts okurigana: accumulate kana and continue buffering the rest
|
|
455
|
-
@okuri_kana = (@okuri_kana || "") + kana
|
|
456
|
-
with_target_buffer { |b| b.insert(kana) }
|
|
457
|
-
Window.redisplay
|
|
458
|
-
else
|
|
459
|
-
append_yomi_kana(first_char)
|
|
460
|
-
end
|
|
646
|
+
insert_kana(first_char)
|
|
461
647
|
process_converting_romaji(last_char)
|
|
462
648
|
end
|
|
463
649
|
|
|
464
|
-
def append_yomi_kana(kana)
|
|
465
|
-
@yomi << kana
|
|
466
|
-
with_target_buffer do |buffer|
|
|
467
|
-
buffer.insert(kana)
|
|
468
|
-
end
|
|
469
|
-
Window.redisplay
|
|
470
|
-
end
|
|
471
|
-
|
|
472
650
|
def start_converting(first_char)
|
|
473
651
|
@phase = :converting
|
|
474
|
-
@yomi = +""
|
|
475
652
|
@okuri_roman = nil
|
|
476
|
-
@
|
|
653
|
+
@okuri_start_pos = nil
|
|
477
654
|
@roman_buffer = +""
|
|
655
|
+
reset_completion
|
|
656
|
+
@last_key_completed = false
|
|
478
657
|
with_target_buffer do |buffer|
|
|
479
658
|
@marker_pos = buffer.point
|
|
480
659
|
buffer.insert("▽")
|
|
@@ -487,36 +666,128 @@ module Textbringer
|
|
|
487
666
|
|
|
488
667
|
def start_okurigana(c)
|
|
489
668
|
@okuri_roman = c.dup
|
|
490
|
-
|
|
669
|
+
with_target_buffer do |buffer|
|
|
670
|
+
@okuri_start_pos = buffer.point
|
|
671
|
+
buffer.insert("*")
|
|
672
|
+
end
|
|
673
|
+
Window.redisplay
|
|
674
|
+
kana = converting_table[c]
|
|
491
675
|
if kana
|
|
492
|
-
# Vowel okurigana: insert the kana immediately
|
|
676
|
+
# Vowel okurigana: insert the kana immediately.
|
|
493
677
|
# (A vowel is never a prefix of a longer romaji sequence, so it's always complete.)
|
|
494
|
-
@okuri_kana = kana
|
|
495
678
|
@roman_buffer = +""
|
|
496
|
-
|
|
497
|
-
Window.redisplay
|
|
679
|
+
insert_kana(kana)
|
|
498
680
|
start_selecting
|
|
499
681
|
else
|
|
500
682
|
@roman_buffer = c.dup
|
|
683
|
+
insert_roman_preview(@roman_buffer)
|
|
684
|
+
end
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
def insert_roman_preview(s)
|
|
688
|
+
with_target_buffer do |buffer|
|
|
689
|
+
buffer.insert(s)
|
|
690
|
+
end
|
|
691
|
+
Window.redisplay
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
def delete_roman_preview
|
|
695
|
+
return if @roman_buffer.empty?
|
|
696
|
+
with_target_buffer do |buffer|
|
|
697
|
+
buffer.delete_region(buffer.point - @roman_buffer.bytesize, buffer.point)
|
|
501
698
|
end
|
|
502
699
|
end
|
|
503
700
|
|
|
701
|
+
def discard_roman_preview
|
|
702
|
+
delete_roman_preview
|
|
703
|
+
@roman_buffer = +""
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
def backspace_event?(event)
|
|
707
|
+
event == :backspace ||
|
|
708
|
+
(event.is_a?(String) && (event == "\C-h" || event.ord == 0x7f))
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
# Mirrors ddskk, where the headword being composed is ordinary
|
|
712
|
+
# buffer text, so backspace just deletes its last character one
|
|
713
|
+
# step at a time: unconfirmed romaji first, then okurigana, then
|
|
714
|
+
# the confirmed yomi, cancelling the conversion once everything is
|
|
715
|
+
# gone.
|
|
716
|
+
def backspace_converting
|
|
717
|
+
unless @roman_buffer.empty?
|
|
718
|
+
delete_roman_preview
|
|
719
|
+
@roman_buffer = @roman_buffer[0..-2]
|
|
720
|
+
if @roman_buffer.empty?
|
|
721
|
+
# Only undo entering okurigana entirely (removing the "*" marker)
|
|
722
|
+
# if no okurigana kana has been confirmed yet -- e.g. after a
|
|
723
|
+
# geminate consonant already confirmed "っ", erasing the preview
|
|
724
|
+
# of the following consonant must not discard that "っ" along
|
|
725
|
+
# with the okurigana marker.
|
|
726
|
+
if @okuri_start_pos &&
|
|
727
|
+
with_target_buffer(&:point) == @okuri_start_pos + "*".bytesize
|
|
728
|
+
undo_okurigana_start
|
|
729
|
+
end
|
|
730
|
+
Window.redisplay
|
|
731
|
+
else
|
|
732
|
+
insert_roman_preview(@roman_buffer)
|
|
733
|
+
end
|
|
734
|
+
return
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
point = with_target_buffer(&:point)
|
|
738
|
+
boundary = @okuri_start_pos ? @okuri_start_pos + "*".bytesize :
|
|
739
|
+
@marker_pos + "▽".bytesize
|
|
740
|
+
|
|
741
|
+
if point == boundary
|
|
742
|
+
if @okuri_start_pos
|
|
743
|
+
undo_okurigana_start
|
|
744
|
+
Window.redisplay
|
|
745
|
+
else
|
|
746
|
+
cancel_converting
|
|
747
|
+
end
|
|
748
|
+
return
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
with_target_buffer do |buffer|
|
|
752
|
+
last = buffer.char_before(buffer.point)
|
|
753
|
+
buffer.delete_region(buffer.point - last.bytesize, buffer.point)
|
|
754
|
+
end
|
|
755
|
+
Window.redisplay
|
|
756
|
+
end
|
|
757
|
+
|
|
758
|
+
# Undoes entering okurigana (removes the "*" marker), as if the
|
|
759
|
+
# uppercase key that started it had never been pressed.
|
|
760
|
+
def undo_okurigana_start
|
|
761
|
+
with_target_buffer do |buffer|
|
|
762
|
+
buffer.delete_region(@okuri_start_pos, @okuri_start_pos + "*".bytesize)
|
|
763
|
+
end
|
|
764
|
+
@okuri_roman = nil
|
|
765
|
+
@okuri_start_pos = nil
|
|
766
|
+
end
|
|
767
|
+
|
|
504
768
|
def cancel_converting
|
|
505
769
|
with_target_buffer do |buffer|
|
|
506
770
|
buffer.delete_region(@marker_pos, buffer.point)
|
|
507
771
|
end
|
|
508
772
|
@phase = :normal
|
|
509
|
-
@yomi = +""
|
|
510
773
|
@roman_buffer = +""
|
|
511
774
|
@okuri_roman = nil
|
|
512
|
-
@
|
|
775
|
+
@okuri_start_pos = nil
|
|
513
776
|
@marker_pos = nil
|
|
777
|
+
Window.echo_area.clear_message
|
|
514
778
|
Window.redisplay
|
|
515
779
|
update_cursor_color
|
|
516
780
|
end
|
|
517
781
|
|
|
518
782
|
def commit_converting
|
|
783
|
+
# skk-kakutei records a yomi confirmed as-is in the ▽ mode.
|
|
784
|
+
if @okuri_start_pos.nil? && starts_with_hiragana?(current_yomi)
|
|
785
|
+
record_kakutei_history(current_yomi)
|
|
786
|
+
end
|
|
519
787
|
with_target_buffer do |buffer|
|
|
788
|
+
if @okuri_start_pos
|
|
789
|
+
buffer.delete_region(@okuri_start_pos, @okuri_start_pos + "*".bytesize)
|
|
790
|
+
end
|
|
520
791
|
# Remove the ▽ marker (3 bytes for ▽ in UTF-8)
|
|
521
792
|
marker_end = @marker_pos + "▽".bytesize
|
|
522
793
|
buffer.delete_region(@marker_pos, marker_end)
|
|
@@ -524,29 +795,203 @@ module Textbringer
|
|
|
524
795
|
@phase = :normal
|
|
525
796
|
@roman_buffer = +""
|
|
526
797
|
@okuri_roman = nil
|
|
527
|
-
@
|
|
798
|
+
@okuri_start_pos = nil
|
|
528
799
|
@marker_pos = nil
|
|
529
800
|
Window.redisplay
|
|
530
801
|
update_cursor_color
|
|
531
802
|
end
|
|
532
803
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
804
|
+
# Reads the yomi/okurigana straight out of the buffer, the way ddskk's
|
|
805
|
+
# skk-set-okurigana slices skk-henkan-key out of the region between its
|
|
806
|
+
# markers, instead of accumulating them in separate variables while
|
|
807
|
+
# converting.
|
|
808
|
+
def current_yomi
|
|
809
|
+
with_target_buffer do |buffer|
|
|
810
|
+
buffer.substring(@marker_pos + "▽".bytesize, @okuri_start_pos || buffer.point)
|
|
811
|
+
end
|
|
812
|
+
end
|
|
813
|
+
|
|
814
|
+
def current_okuri_kana
|
|
815
|
+
return nil unless @okuri_start_pos
|
|
816
|
+
with_target_buffer do |buffer|
|
|
817
|
+
buffer.substring(@okuri_start_pos + "*".bytesize, buffer.point)
|
|
818
|
+
end
|
|
819
|
+
end
|
|
820
|
+
|
|
821
|
+
# Mirrors ddskk's skk-toggle-characters in the "on" (▽) branch: it
|
|
822
|
+
# converts the yomi to the opposite kana and commits, refusing (as
|
|
823
|
+
# ddskk's skk-error does) while unconfirmed romaji remains. Since
|
|
824
|
+
# okurigana always leaves unconfirmed romaji buffered until its first
|
|
825
|
+
# kana is confirmed (at which point converting phase is left via
|
|
826
|
+
# start_selecting), this naturally also covers -- by refusing -- the
|
|
827
|
+
# only okurigana case reachable here.
|
|
828
|
+
def toggle_yomi_katakana
|
|
829
|
+
unless @roman_buffer.empty?
|
|
830
|
+
message("There remains a kana prefix")
|
|
831
|
+
Window.redisplay
|
|
832
|
+
return
|
|
833
|
+
end
|
|
834
|
+
|
|
835
|
+
yomi = current_yomi
|
|
836
|
+
return if yomi.empty?
|
|
837
|
+
|
|
838
|
+
# skk-henkan-skk-region-by-func records the yomi before toggling it
|
|
839
|
+
# (and commit_converting records it after, like its skk-kakutei).
|
|
840
|
+
record_kakutei_history(yomi) if starts_with_hiragana?(yomi)
|
|
841
|
+
converted = katakana?(yomi[0]) ? katakana_to_hiragana(yomi) : hiragana_to_katakana(yomi)
|
|
842
|
+
with_target_buffer do |buffer|
|
|
843
|
+
buffer.delete_region(@marker_pos + "▽".bytesize, buffer.point)
|
|
844
|
+
buffer.insert(converted)
|
|
845
|
+
end
|
|
846
|
+
commit_converting
|
|
847
|
+
end
|
|
848
|
+
|
|
849
|
+
# Mirrors ddskk's skk-comp (SKK manual section 6.4): completes the yomi
|
|
850
|
+
# from the user dictionary's okuri-nasi keys only, most recently
|
|
851
|
+
# confirmed first. Like skk-insert's
|
|
852
|
+
# (skk-comp (not (eq last-command 'skk-comp-do))), TAB right after
|
|
853
|
+
# TAB/Shift-TAB advances the completion, and otherwise starts a new one.
|
|
854
|
+
def complete_yomi(after_completion)
|
|
855
|
+
return unless @okuri_start_pos.nil?
|
|
856
|
+
|
|
857
|
+
# Like skk-comp-do's (skk-kana-cleanup 'force): unconfirmed romaji
|
|
858
|
+
# that is a kana by itself ("n") is confirmed, and anything else is
|
|
859
|
+
# dropped. Typing the romaji already broke any run of completions.
|
|
860
|
+
unless @roman_buffer.empty?
|
|
861
|
+
pending = @roman_buffer
|
|
862
|
+
discard_roman_preview
|
|
863
|
+
insert_kana(converting_kana_for_mode("ん")) if pending == "n"
|
|
864
|
+
end
|
|
865
|
+
|
|
866
|
+
if after_completion && @comp_candidates
|
|
867
|
+
advance_completion(1, first: false)
|
|
536
868
|
else
|
|
537
|
-
@
|
|
869
|
+
@comp_prefix = current_yomi
|
|
870
|
+
ensure_user_dictionary_loaded
|
|
871
|
+
# Dictionary keys are always hiragana; like skk-comp-do, search with
|
|
872
|
+
# the yomi in hiragana and show the candidates back in katakana.
|
|
873
|
+
key = @mode == :katakana ? katakana_to_hiragana(@comp_prefix) : @comp_prefix
|
|
874
|
+
candidates =
|
|
875
|
+
if key.empty?
|
|
876
|
+
# skk-comp-from-jisyo gives nothing for an empty yomi; only
|
|
877
|
+
# skk-comp-by-history completes it, from the kakutei history.
|
|
878
|
+
@@kakutei_history.uniq
|
|
879
|
+
else
|
|
880
|
+
# Like skk-comp-search-current-buffer, skip the yomi's own
|
|
881
|
+
# entry: completing to what is already there would change
|
|
882
|
+
# nothing.
|
|
883
|
+
@user_okurinasi.keys.select { |k| k.start_with?(key) && k != key }
|
|
884
|
+
end
|
|
885
|
+
@comp_candidates =
|
|
886
|
+
@mode == :katakana ? candidates.map { |k| hiragana_to_katakana(k) } : candidates
|
|
887
|
+
@comp_index = -1
|
|
888
|
+
advance_completion(1, first: true)
|
|
538
889
|
end
|
|
890
|
+
end
|
|
539
891
|
|
|
540
|
-
|
|
541
|
-
|
|
892
|
+
def complete_yomi_backward(after_completion)
|
|
893
|
+
if after_completion && @comp_candidates
|
|
894
|
+
advance_completion(-1, first: false)
|
|
895
|
+
else
|
|
896
|
+
# Drop any completion from before the yomi was edited, so a TAB
|
|
897
|
+
# right after this starts afresh instead of resuming it.
|
|
898
|
+
reset_completion
|
|
899
|
+
Window.beep
|
|
900
|
+
show_completion_message(
|
|
901
|
+
"No more previous completions for \"#{current_yomi}\""
|
|
902
|
+
)
|
|
903
|
+
end
|
|
904
|
+
end
|
|
905
|
+
|
|
906
|
+
# Mirrors ddskk's skk-henkan-off-by-quit taking the skk-comp-do branch:
|
|
907
|
+
# C-g right after a completion, even a failed one, only restores the
|
|
908
|
+
# yomi it started from.
|
|
909
|
+
def cancel_completion
|
|
910
|
+
with_target_buffer do |buffer|
|
|
911
|
+
buffer.delete_region(@marker_pos + "▽".bytesize, buffer.point)
|
|
912
|
+
buffer.insert(@comp_prefix)
|
|
913
|
+
end
|
|
914
|
+
reset_completion
|
|
915
|
+
Window.redisplay
|
|
916
|
+
end
|
|
917
|
+
|
|
918
|
+
def show_completion_message(text)
|
|
919
|
+
message(text)
|
|
920
|
+
@comp_message_shown = true
|
|
921
|
+
Window.redisplay
|
|
922
|
+
end
|
|
923
|
+
|
|
924
|
+
# Like ddskk's skk-comp-circulate (nil by default), stops at either end
|
|
925
|
+
# instead of wrapping around. FIRST is skk-comp-do's argument of the
|
|
926
|
+
# same name, which picks "No completions" over "No more completions".
|
|
927
|
+
def advance_completion(direction, first:)
|
|
928
|
+
new_index = @comp_index + direction
|
|
929
|
+
if new_index < 0 || new_index >= @comp_candidates.size
|
|
930
|
+
Window.beep
|
|
931
|
+
show_completion_message(
|
|
932
|
+
if direction < 0
|
|
933
|
+
"No more previous completions for \"#{@comp_prefix}\""
|
|
934
|
+
elsif @comp_prefix.empty?
|
|
935
|
+
"No more words in history"
|
|
936
|
+
elsif first
|
|
937
|
+
"No completions for \"#{@comp_prefix}\""
|
|
938
|
+
else
|
|
939
|
+
"No more completions for \"#{@comp_prefix}\""
|
|
940
|
+
end
|
|
941
|
+
)
|
|
942
|
+
return
|
|
943
|
+
end
|
|
944
|
+
@comp_index = new_index
|
|
945
|
+
with_target_buffer do |buffer|
|
|
946
|
+
buffer.delete_region(@marker_pos + "▽".bytesize, buffer.point)
|
|
947
|
+
buffer.insert(@comp_candidates[@comp_index])
|
|
948
|
+
end
|
|
949
|
+
Window.redisplay
|
|
950
|
+
end
|
|
951
|
+
|
|
952
|
+
def reset_completion
|
|
953
|
+
@comp_prefix = nil
|
|
954
|
+
@comp_candidates = nil
|
|
955
|
+
@comp_index = nil
|
|
956
|
+
end
|
|
957
|
+
|
|
958
|
+
def start_selecting
|
|
959
|
+
reset_completion
|
|
960
|
+
|
|
961
|
+
# Snapshot the yomi/okurigana now: once selecting replaces the
|
|
962
|
+
# buffer text with a "▼" candidate, the markers no longer delimit them.
|
|
963
|
+
@yomi = current_yomi
|
|
964
|
+
@okuri_kana = current_okuri_kana
|
|
965
|
+
|
|
966
|
+
# Dictionary keys are always hiragana, even when composing in
|
|
967
|
+
# katakana mode (mirrors ddskk's skk-katakana-to-hiragana call in
|
|
968
|
+
# skk-set-okurigana).
|
|
969
|
+
yomi_key = @mode == :katakana ? katakana_to_hiragana(@yomi) : @yomi
|
|
970
|
+
@lookup_key = @okuri_roman ? (yomi_key + @okuri_roman) : yomi_key
|
|
971
|
+
|
|
972
|
+
ensure_user_dictionary_loaded
|
|
973
|
+
user_dict = @okuri_roman ? @user_okuriiari : @user_okurinasi
|
|
974
|
+
user_candidates = user_dict[@lookup_key] || []
|
|
975
|
+
|
|
976
|
+
base_candidates = if CONFIG[:skk_server_host]
|
|
977
|
+
skk_server_lookup(@lookup_key) || []
|
|
542
978
|
else
|
|
543
979
|
ensure_dictionary_loaded
|
|
544
980
|
dict = @okuri_roman ? @okuriiari : @okurinasi
|
|
545
|
-
dict[lookup_key]
|
|
981
|
+
dict[@lookup_key] || []
|
|
546
982
|
end
|
|
547
983
|
|
|
548
|
-
|
|
549
|
-
|
|
984
|
+
@base_candidates = base_candidates
|
|
985
|
+
# User dictionary candidates are ordered by recency (see
|
|
986
|
+
# learn_candidate) and take priority, mirroring ddskk's
|
|
987
|
+
# skk-search-prog-list ordering the private dictionary before the
|
|
988
|
+
# system one. Words an skk-ignore-dic-word candidate lists are left
|
|
989
|
+
# out of the system dictionary's candidates.
|
|
990
|
+
user_words = user_candidates.grep_v(IGNORE_DIC_WORD_RE)
|
|
991
|
+
candidates = (user_words + (base_candidates - ignored_words(user_candidates))).uniq
|
|
992
|
+
|
|
993
|
+
if candidates.empty?
|
|
994
|
+
register_new_word
|
|
550
995
|
return
|
|
551
996
|
end
|
|
552
997
|
|
|
@@ -562,13 +1007,26 @@ module Textbringer
|
|
|
562
1007
|
update_cursor_color
|
|
563
1008
|
end
|
|
564
1009
|
|
|
1010
|
+
# Like ddskk, SPC past the last candidate does not wrap around:
|
|
1011
|
+
# skk-henkan-1 finds no more candidates, so skk-henkan goes on to
|
|
1012
|
+
# skk-henkan-in-minibuff and asks for a new word.
|
|
565
1013
|
def next_candidate
|
|
566
|
-
|
|
1014
|
+
if @candidate_index + 1 >= @candidates.size
|
|
1015
|
+
register_new_word
|
|
1016
|
+
return
|
|
1017
|
+
end
|
|
1018
|
+
@candidate_index += 1
|
|
567
1019
|
replace_candidate
|
|
568
1020
|
end
|
|
569
1021
|
|
|
1022
|
+
# Like skk-previous-candidate with skk-henkan-count 0, "x" on the first
|
|
1023
|
+
# candidate goes back to the yomi instead of wrapping around.
|
|
570
1024
|
def prev_candidate
|
|
571
|
-
|
|
1025
|
+
if @candidate_index == 0
|
|
1026
|
+
cancel_selecting
|
|
1027
|
+
return
|
|
1028
|
+
end
|
|
1029
|
+
@candidate_index -= 1
|
|
572
1030
|
replace_candidate
|
|
573
1031
|
end
|
|
574
1032
|
|
|
@@ -581,15 +1039,50 @@ module Textbringer
|
|
|
581
1039
|
end
|
|
582
1040
|
|
|
583
1041
|
def confirm_selecting
|
|
584
|
-
|
|
1042
|
+
confirm_word(@candidates[@candidate_index])
|
|
1043
|
+
end
|
|
1044
|
+
|
|
1045
|
+
# Shared by confirm_selecting and register_new_word: insert the final
|
|
1046
|
+
# word, learn it into the user dictionary, and leave converting/
|
|
1047
|
+
# selecting entirely.
|
|
1048
|
+
def confirm_word(word)
|
|
1049
|
+
with_target_buffer do |buffer|
|
|
1050
|
+
buffer.delete_region(@marker_pos, buffer.point)
|
|
1051
|
+
buffer.insert(word + (@okuri_kana || ""))
|
|
1052
|
+
end
|
|
1053
|
+
learn_candidate(word)
|
|
1054
|
+
leave_conversion
|
|
1055
|
+
end
|
|
1056
|
+
|
|
1057
|
+
# Mirrors ddskk's skk-purge-from-jisyo: once confirmed with yes-or-no-p,
|
|
1058
|
+
# remove the current candidate from the user dictionary and delete the
|
|
1059
|
+
# ▼ text from the buffer without inserting anything.
|
|
1060
|
+
def purge_candidate
|
|
1061
|
+
word = @candidates[@candidate_index]
|
|
1062
|
+
purge =
|
|
1063
|
+
begin
|
|
1064
|
+
yes_or_no?("Really purge \"#{@lookup_key} /#{word}/ \"?")
|
|
1065
|
+
rescue Quit
|
|
1066
|
+
false
|
|
1067
|
+
end
|
|
1068
|
+
unless purge
|
|
1069
|
+
Window.redisplay
|
|
1070
|
+
return
|
|
1071
|
+
end
|
|
1072
|
+
|
|
1073
|
+
purge_from_user_dictionary(word)
|
|
585
1074
|
with_target_buffer do |buffer|
|
|
586
1075
|
buffer.delete_region(@marker_pos, buffer.point)
|
|
587
|
-
buffer.insert(candidate + (@okuri_kana || ""))
|
|
588
1076
|
end
|
|
1077
|
+
leave_conversion
|
|
1078
|
+
end
|
|
1079
|
+
|
|
1080
|
+
def leave_conversion
|
|
589
1081
|
@phase = :normal
|
|
590
1082
|
@yomi = +""
|
|
591
1083
|
@roman_buffer = +""
|
|
592
1084
|
@okuri_roman = nil
|
|
1085
|
+
@okuri_start_pos = nil
|
|
593
1086
|
@okuri_kana = nil
|
|
594
1087
|
@candidates = []
|
|
595
1088
|
@candidate_index = 0
|
|
@@ -598,6 +1091,44 @@ module Textbringer
|
|
|
598
1091
|
update_cursor_color
|
|
599
1092
|
end
|
|
600
1093
|
|
|
1094
|
+
# Mirrors ddskk's skk-henkan-in-minibuff: when no candidate is found,
|
|
1095
|
+
# or SPC runs past the last one, read a new word for skk-henkan-key from the minibuffer and register
|
|
1096
|
+
# it. Unlike ddskk, the minibuffer here is not switched into SKK
|
|
1097
|
+
# itself, so entering kanji requires switching input method by hand
|
|
1098
|
+
# (e.g. C-\); nested registration (ddskk lets an unresolved word typed
|
|
1099
|
+
# inside the registration prompt itself trigger another registration)
|
|
1100
|
+
# is left for a follow-up.
|
|
1101
|
+
def register_new_word
|
|
1102
|
+
# EchoArea#redisplay only ever draws @prompt when @message is nil --
|
|
1103
|
+
# otherwise it draws @message instead. A "No conversion: ..." message
|
|
1104
|
+
# left over from a previous registration attempt would otherwise
|
|
1105
|
+
# blank out this new prompt on screen even though @prompt itself
|
|
1106
|
+
# gets set correctly underneath it.
|
|
1107
|
+
Window.echo_area.clear_message
|
|
1108
|
+
prompt = "SKK register #{@yomi}#{@okuri_roman ? "*#{@okuri_roman}" : ""}: "
|
|
1109
|
+
new_word =
|
|
1110
|
+
begin
|
|
1111
|
+
read_from_minibuffer(prompt)
|
|
1112
|
+
rescue Quit
|
|
1113
|
+
+""
|
|
1114
|
+
end
|
|
1115
|
+
|
|
1116
|
+
if new_word.empty?
|
|
1117
|
+
# Registering after the candidates ran out leaves the last one
|
|
1118
|
+
# shown, as skk-henkan-in-minibuff puts it back when given an empty
|
|
1119
|
+
# string; with no candidates at all there is nothing to go back to.
|
|
1120
|
+
message("No conversion: #{@yomi}") unless @phase == :selecting
|
|
1121
|
+
Window.redisplay
|
|
1122
|
+
return
|
|
1123
|
+
end
|
|
1124
|
+
|
|
1125
|
+
confirm_word(new_word)
|
|
1126
|
+
end
|
|
1127
|
+
|
|
1128
|
+
# Mirrors ddskk's skk-previous-candidate: cancelling out of the
|
|
1129
|
+
# candidate list drops the okurigana distinction entirely (the "*"
|
|
1130
|
+
# marker is not restored), so the okurigana text rejoins the yomi as
|
|
1131
|
+
# plain, un-marked headword text.
|
|
601
1132
|
def cancel_selecting
|
|
602
1133
|
with_target_buffer do |buffer|
|
|
603
1134
|
buffer.delete_region(@marker_pos, buffer.point)
|
|
@@ -605,6 +1136,8 @@ module Textbringer
|
|
|
605
1136
|
end
|
|
606
1137
|
@phase = :converting
|
|
607
1138
|
@roman_buffer = +""
|
|
1139
|
+
@okuri_roman = nil
|
|
1140
|
+
@okuri_start_pos = nil
|
|
608
1141
|
@candidates = []
|
|
609
1142
|
@candidate_index = 0
|
|
610
1143
|
Window.redisplay
|
|
@@ -614,12 +1147,27 @@ module Textbringer
|
|
|
614
1147
|
def ensure_dictionary_loaded
|
|
615
1148
|
return if @okuriiari
|
|
616
1149
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
1150
|
+
@okuriiari, @okurinasi =
|
|
1151
|
+
parse_skk_dictionary(CONFIG[:skk_dictionary_path], encoding: "EUC-JP:UTF-8")
|
|
1152
|
+
end
|
|
1153
|
+
|
|
1154
|
+
def ensure_user_dictionary_loaded
|
|
1155
|
+
path = CONFIG[:skk_user_dictionary_path]
|
|
1156
|
+
@user_okuriiari, @user_okurinasi =
|
|
1157
|
+
@@user_dictionaries[path] ||=
|
|
1158
|
+
if path && File.exist?(path)
|
|
1159
|
+
parse_skk_dictionary(path, encoding: "UTF-8")
|
|
1160
|
+
else
|
|
1161
|
+
[{}, {}]
|
|
1162
|
+
end
|
|
1163
|
+
end
|
|
1164
|
+
|
|
1165
|
+
def parse_skk_dictionary(path, encoding:)
|
|
1166
|
+
okuriiari = {}
|
|
1167
|
+
okurinasi = {}
|
|
620
1168
|
section = :okuriiari
|
|
621
1169
|
|
|
622
|
-
File.foreach(path, encoding:
|
|
1170
|
+
File.foreach(path, encoding: encoding) do |line|
|
|
623
1171
|
line.chomp!
|
|
624
1172
|
if line == ";; okuri-nasi entries."
|
|
625
1173
|
section = :okurinasi
|
|
@@ -634,9 +1182,82 @@ module Textbringer
|
|
|
634
1182
|
next if candidates.empty?
|
|
635
1183
|
|
|
636
1184
|
if section == :okuriiari
|
|
637
|
-
|
|
1185
|
+
okuriiari[key] = candidates
|
|
638
1186
|
else
|
|
639
|
-
|
|
1187
|
+
okurinasi[key] = candidates
|
|
1188
|
+
end
|
|
1189
|
+
end
|
|
1190
|
+
|
|
1191
|
+
[okuriiari, okurinasi]
|
|
1192
|
+
end
|
|
1193
|
+
|
|
1194
|
+
# Mirrors ddskk's skk-update-jisyo-1: move the confirmed candidate to
|
|
1195
|
+
# the front of its entry, and the entry itself to the front of the
|
|
1196
|
+
# dictionary (completion walks the keys in that order), then persist
|
|
1197
|
+
# immediately, since this SKK implementation has no explicit save
|
|
1198
|
+
# command or autosave-count setting yet.
|
|
1199
|
+
def learn_candidate(candidate)
|
|
1200
|
+
dict = @okuri_roman ? @user_okuriiari : @user_okurinasi
|
|
1201
|
+
entries = dict.delete(@lookup_key) || []
|
|
1202
|
+
candidates = [candidate] + entries.reject { |c| c == candidate }
|
|
1203
|
+
dict.replace({ @lookup_key => candidates }.merge(dict))
|
|
1204
|
+
save_user_dictionary
|
|
1205
|
+
# skk-update-jisyo records only okuri-nasi conversions in the history.
|
|
1206
|
+
record_kakutei_history(@lookup_key) unless @okuri_roman
|
|
1207
|
+
end
|
|
1208
|
+
|
|
1209
|
+
# Mirrors the purge branch of ddskk's skk-update-jisyo-1: a word the
|
|
1210
|
+
# system dictionary (or SKK server) also has is quoted with
|
|
1211
|
+
# skk-ignore-dic-word, folded into one candidate as
|
|
1212
|
+
# skk-compose-ignore-word does, so that later conversions hide it;
|
|
1213
|
+
# any other word is just deleted. Unlike ddskk's skk-update-jisyo,
|
|
1214
|
+
# the yomi is not recorded in the kakutei history.
|
|
1215
|
+
def purge_from_user_dictionary(word)
|
|
1216
|
+
dict = @okuri_roman ? @user_okuriiari : @user_okurinasi
|
|
1217
|
+
entries = (dict.delete(@lookup_key) || []) - [word]
|
|
1218
|
+
if @base_candidates.include?(word)
|
|
1219
|
+
ignored = ignored_words(entries) + [word]
|
|
1220
|
+
quoted = ignored.map { |w| "\"#{w}\"" }.join(" ")
|
|
1221
|
+
entries = ["(skk-ignore-dic-word #{quoted})"] + entries.grep_v(IGNORE_DIC_WORD_RE)
|
|
1222
|
+
end
|
|
1223
|
+
# skk-update-jisyo-1 writes nothing back for an entry left empty.
|
|
1224
|
+
dict.replace({ @lookup_key => entries }.merge(dict)) unless entries.empty?
|
|
1225
|
+
save_user_dictionary
|
|
1226
|
+
end
|
|
1227
|
+
|
|
1228
|
+
def ignored_words(candidates)
|
|
1229
|
+
candidates.flat_map { |c|
|
|
1230
|
+
m = IGNORE_DIC_WORD_RE.match(c)
|
|
1231
|
+
m ? m[1].scan(/"([^"]*)"/).flatten : []
|
|
1232
|
+
}
|
|
1233
|
+
end
|
|
1234
|
+
|
|
1235
|
+
# Like skk-update-kakutei-history, keeping only the newest
|
|
1236
|
+
# KAKUTEI_HISTORY_LIMIT entries.
|
|
1237
|
+
def record_kakutei_history(yomi)
|
|
1238
|
+
@@kakutei_history.unshift(yomi)
|
|
1239
|
+
@@kakutei_history.slice!(KAKUTEI_HISTORY_LIMIT..)
|
|
1240
|
+
end
|
|
1241
|
+
|
|
1242
|
+
# The check skk-kakutei and skk-henkan-skk-region-by-func make with
|
|
1243
|
+
# skk-what-char-type at the ▽ point before recording a yomi.
|
|
1244
|
+
def starts_with_hiragana?(yomi)
|
|
1245
|
+
yomi.match?(/\A[ぁ-ん]/)
|
|
1246
|
+
end
|
|
1247
|
+
|
|
1248
|
+
def save_user_dictionary
|
|
1249
|
+
path = CONFIG[:skk_user_dictionary_path]
|
|
1250
|
+
return unless path
|
|
1251
|
+
|
|
1252
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
1253
|
+
File.open(path, "w:UTF-8") do |f|
|
|
1254
|
+
f.puts ";; okuri-ari entries."
|
|
1255
|
+
@user_okuriiari.each do |key, candidates|
|
|
1256
|
+
f.puts "#{key} /#{candidates.join('/')}/"
|
|
1257
|
+
end
|
|
1258
|
+
f.puts ";; okuri-nasi entries."
|
|
1259
|
+
@user_okurinasi.each do |key, candidates|
|
|
1260
|
+
f.puts "#{key} /#{candidates.join('/')}/"
|
|
640
1261
|
end
|
|
641
1262
|
end
|
|
642
1263
|
end
|
|
@@ -660,6 +1281,16 @@ module Textbringer
|
|
|
660
1281
|
}.join
|
|
661
1282
|
end
|
|
662
1283
|
|
|
1284
|
+
def katakana_to_hiragana(kana)
|
|
1285
|
+
kana.chars.map { |c|
|
|
1286
|
+
c.ord.between?(0x30A1, 0x30F6) ? (c.ord - 0x60).chr("UTF-8") : c
|
|
1287
|
+
}.join
|
|
1288
|
+
end
|
|
1289
|
+
|
|
1290
|
+
def katakana?(char)
|
|
1291
|
+
char.ord.between?(0x30A1, 0x30F6)
|
|
1292
|
+
end
|
|
1293
|
+
|
|
663
1294
|
def hiragana_to_hankaku_katakana(kana)
|
|
664
1295
|
kana.chars.map { |c|
|
|
665
1296
|
case c.ord
|
|
@@ -766,13 +1397,18 @@ module Textbringer
|
|
|
766
1397
|
end
|
|
767
1398
|
end
|
|
768
1399
|
|
|
769
|
-
#
|
|
770
|
-
|
|
771
|
-
|
|
1400
|
+
# Unlike current_table/current_prefixes (used by process_romaji for
|
|
1401
|
+
# ordinary input), converting only switches to katakana, not hankaku
|
|
1402
|
+
# katakana: half-width katakana isn't hiragana-convertible for the
|
|
1403
|
+
# dictionary lookup key the way full-width katakana is (see
|
|
1404
|
+
# start_selecting), and ddskk's half-width kana input is a separate
|
|
1405
|
+
# mode with its own conversion path, not part of skk-katakana.
|
|
1406
|
+
def converting_table
|
|
1407
|
+
@mode == :katakana ? KATAKANA_TABLE : HIRAGANA_TABLE
|
|
772
1408
|
end
|
|
773
1409
|
|
|
774
|
-
def
|
|
775
|
-
|
|
1410
|
+
def converting_kana_for_mode(hiragana_kana)
|
|
1411
|
+
@mode == :katakana ? hiragana_to_katakana(hiragana_kana) : hiragana_kana
|
|
776
1412
|
end
|
|
777
1413
|
|
|
778
1414
|
def update_cursor_color
|