textbringer 17 → 19

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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/exe/txtb +1 -1
  3. data/lib/textbringer/buffer.rb +37 -3
  4. data/lib/textbringer/commands/buffers.rb +4 -2
  5. data/lib/textbringer/commands/clipboard.rb +21 -6
  6. data/lib/textbringer/commands/completion.rb +133 -0
  7. data/lib/textbringer/commands/ctags.rb +1 -1
  8. data/lib/textbringer/commands/files.rb +11 -1
  9. data/lib/textbringer/commands/help.rb +1 -1
  10. data/lib/textbringer/commands/isearch.rb +4 -10
  11. data/lib/textbringer/commands/ispell.rb +0 -2
  12. data/lib/textbringer/commands/lsp.rb +389 -0
  13. data/lib/textbringer/commands/misc.rb +2 -1
  14. data/lib/textbringer/commands.rb +7 -3
  15. data/lib/textbringer/completion_popup.rb +188 -0
  16. data/lib/textbringer/faces/basic.rb +3 -1
  17. data/lib/textbringer/faces/completion.rb +4 -0
  18. data/lib/textbringer/floating_window.rb +327 -0
  19. data/lib/textbringer/input_methods/skk_input_method.rb +751 -0
  20. data/lib/textbringer/lsp/client.rb +568 -0
  21. data/lib/textbringer/lsp/server_registry.rb +138 -0
  22. data/lib/textbringer/mode.rb +3 -1
  23. data/lib/textbringer/modes/programming_mode.rb +17 -8
  24. data/lib/textbringer/modes/transient_mark_mode.rb +9 -2
  25. data/lib/textbringer/utils.rb +14 -10
  26. data/lib/textbringer/version.rb +1 -1
  27. data/lib/textbringer/window.rb +116 -19
  28. data/lib/textbringer.rb +7 -0
  29. data/sig/lib/textbringer/buffer.rbs +483 -0
  30. data/sig/lib/textbringer/color.rbs +9 -0
  31. data/sig/lib/textbringer/commands/buffers.rbs +93 -0
  32. data/sig/lib/textbringer/commands/clipboard.rbs +17 -0
  33. data/sig/lib/textbringer/commands/completion.rbs +20 -0
  34. data/sig/lib/textbringer/commands/ctags.rbs +11 -0
  35. data/sig/lib/textbringer/commands/dabbrev.rbs +4 -0
  36. data/sig/lib/textbringer/commands/files.rbs +29 -0
  37. data/sig/lib/textbringer/commands/fill.rbs +5 -0
  38. data/sig/lib/textbringer/commands/help.rbs +28 -0
  39. data/sig/lib/textbringer/commands/input_method.rbs +6 -0
  40. data/sig/lib/textbringer/commands/isearch.rbs +38 -0
  41. data/sig/lib/textbringer/commands/ispell.rbs +39 -0
  42. data/sig/lib/textbringer/commands/keyboard_macro.rbs +25 -0
  43. data/sig/lib/textbringer/commands/lsp.rbs +8 -0
  44. data/sig/lib/textbringer/commands/misc.rbs +74 -0
  45. data/sig/lib/textbringer/commands/rectangle.rbs +19 -0
  46. data/sig/lib/textbringer/commands/register.rbs +31 -0
  47. data/sig/lib/textbringer/commands/replace.rbs +17 -0
  48. data/sig/lib/textbringer/commands/server.rbs +31 -0
  49. data/sig/lib/textbringer/commands/ucs_normalize.rbs +9 -0
  50. data/sig/lib/textbringer/commands/windows.rbs +45 -0
  51. data/sig/lib/textbringer/commands.rbs +21 -0
  52. data/sig/lib/textbringer/completion_popup.rbs +40 -0
  53. data/sig/lib/textbringer/controller.rbs +58 -0
  54. data/sig/lib/textbringer/default_output.rbs +7 -0
  55. data/sig/lib/textbringer/errors.rbs +3 -0
  56. data/sig/lib/textbringer/face.rbs +19 -0
  57. data/sig/lib/textbringer/floating_window.rbs +42 -0
  58. data/sig/lib/textbringer/global_minor_mode.rbs +7 -0
  59. data/sig/lib/textbringer/input_method.rbs +28 -0
  60. data/sig/lib/textbringer/input_methods/hangul_input_method.rbs +12 -0
  61. data/sig/lib/textbringer/input_methods/hiragana_input_method.rbs +12 -0
  62. data/sig/lib/textbringer/input_methods/t_code_input_method.rbs +49 -0
  63. data/sig/lib/textbringer/keymap.rbs +33 -0
  64. data/sig/lib/textbringer/lsp/client.rbs +21 -0
  65. data/sig/lib/textbringer/lsp/server_registry.rbs +23 -0
  66. data/sig/lib/textbringer/minor_mode.rbs +12 -0
  67. data/sig/lib/textbringer/mode.rbs +70 -0
  68. data/sig/lib/textbringer/modes/backtrace_mode.rbs +8 -0
  69. data/sig/lib/textbringer/modes/buffer_list_mode.rbs +5 -0
  70. data/sig/lib/textbringer/modes/c_mode.rbs +21 -0
  71. data/sig/lib/textbringer/modes/completion_list_mode.rbs +5 -0
  72. data/sig/lib/textbringer/modes/fundamental_mode.rbs +3 -0
  73. data/sig/lib/textbringer/modes/help_mode.rbs +7 -0
  74. data/sig/lib/textbringer/modes/overwrite_mode.rbs +15 -0
  75. data/sig/lib/textbringer/modes/programming_mode.rbs +14 -0
  76. data/sig/lib/textbringer/modes/ruby_mode.rbs +57 -0
  77. data/sig/lib/textbringer/plugin.rbs +3 -0
  78. data/sig/lib/textbringer/ring.rbs +36 -0
  79. data/sig/lib/textbringer/utils.rbs +95 -0
  80. data/sig/lib/textbringer/window.rbs +183 -0
  81. data/textbringer.gemspec +1 -0
  82. metadata +76 -2
@@ -0,0 +1,751 @@
1
+ module Textbringer
2
+ class SkkInputMethod < InputMethod
3
+ HIRAGANA_TABLE = {
4
+ "a" => "あ", "i" => "い", "u" => "う", "e" => "え", "o" => "お",
5
+ "ka" => "か", "ki" => "き", "ku" => "く", "ke" => "け", "ko" => "こ",
6
+ "ga" => "が", "gi" => "ぎ", "gu" => "ぐ", "ge" => "げ", "go" => "ご",
7
+ "sa" => "さ", "si" => "し", "su" => "す", "se" => "せ", "so" => "そ",
8
+ "za" => "ざ", "zi" => "じ", "zu" => "ず", "ze" => "ぜ", "zo" => "ぞ",
9
+ "sha" => "しゃ", "shi" => "し", "shu" => "しゅ", "she" => "しぇ", "sho" => "しょ",
10
+ "ja" => "じゃ", "ji" => "じ", "ju" => "じゅ", "je" => "じぇ", "jo" => "じょ",
11
+ "ta" => "た", "ti" => "ち", "tu" => "つ", "te" => "て", "to" => "と",
12
+ "da" => "だ", "di" => "ぢ", "du" => "づ", "de" => "で", "do" => "ど",
13
+ "cha" => "ちゃ", "chi" => "ち", "chu" => "ちゅ", "che" => "ちぇ", "cho" => "ちょ",
14
+ "na" => "な", "ni" => "に", "nu" => "ぬ", "ne" => "ね", "no" => "の",
15
+ "ha" => "は", "hi" => "ひ", "hu" => "ふ", "he" => "へ", "ho" => "ほ",
16
+ "ba" => "ば", "bi" => "び", "bu" => "ぶ", "be" => "べ", "bo" => "ぼ",
17
+ "pa" => "ぱ", "pi" => "ぴ", "pu" => "ぷ", "pe" => "ぺ", "po" => "ぽ",
18
+ "ma" => "ま", "mi" => "み", "mu" => "む", "me" => "め", "mo" => "も",
19
+ "ya" => "や", "yi" => "い", "yu" => "ゆ", "ye" => "いぇ", "yo" => "よ",
20
+ "ra" => "ら", "ri" => "り", "ru" => "る", "re" => "れ", "ro" => "ろ",
21
+ "wa" => "わ", "wi" => "ゐ", "wu" => "う", "we" => "ゑ", "wo" => "を",
22
+ "nn" => "ん",
23
+ "kya" => "きゃ", "kyi" => "きぃ", "kyu" => "きゅ", "kye" => "きぇ", "kyo" => "きょ",
24
+ "gya" => "ぎゃ", "gyi" => "ぎぃ", "gyu" => "ぎゅ", "gye" => "ぎぇ", "gyo" => "ぎょ",
25
+ "sya" => "しゃ", "syi" => "しぃ", "syu" => "しゅ", "sye" => "しぇ", "syo" => "しょ",
26
+ "zya" => "じゃ", "zyi" => "じぃ", "zyu" => "じゅ", "zye" => "じぇ", "zyo" => "じょ",
27
+ "tya" => "ちゃ", "tyi" => "ちぃ", "tyu" => "ちゅ", "tye" => "ちぇ", "tyo" => "ちょ",
28
+ "dya" => "ぢゃ", "dyi" => "ぢぃ", "dyu" => "ぢゅ", "dye" => "ぢぇ", "dyo" => "ぢょ",
29
+ "nya" => "にゃ", "nyi" => "にぃ", "nyu" => "にゅ", "nye" => "にぇ", "nyo" => "にょ",
30
+ "hya" => "ひゃ", "hyi" => "ひぃ", "hyu" => "ひゅ", "hye" => "ひぇ", "hyo" => "ひょ",
31
+ "bya" => "びゃ", "byi" => "びぃ", "byu" => "びゅ", "bye" => "びぇ", "byo" => "びょ",
32
+ "pya" => "ぴゃ", "pyi" => "ぴぃ", "pyu" => "ぴゅ", "pye" => "ぴぇ", "pyo" => "ぴょ",
33
+ "mya" => "みゃ", "myi" => "みぃ", "myu" => "みゅ", "mye" => "みぇ", "myo" => "みょ",
34
+ "rya" => "りゃ", "ryi" => "りぃ", "ryu" => "りゅ", "rye" => "りぇ", "ryo" => "りょ",
35
+ "fa" => "ふぁ", "fi" => "ふぃ", "fu" => "ふ", "fe" => "ふぇ", "fo" => "ふぉ",
36
+ "va" => "ヴぁ", "vi" => "ヴぃ", "vu" => "ヴ", "ve" => "ヴぇ", "vo" => "ヴぉ",
37
+ "tsa" => "つぁ", "tsi" => "つぃ", "tse" => "つぇ", "tso" => "つぉ",
38
+ "la" => "ぁ", "li" => "ぃ", "lu" => "ぅ", "le" => "ぇ", "lo" => "ぉ",
39
+ "lya" => "ゃ", "lyu" => "ゅ", "lyo" => "ょ",
40
+ "lka" => "ヵ", "lke" => "ヶ",
41
+ "xtu" => "っ", "xtsu" => "っ",
42
+ "xya" => "ゃ", "xyu" => "ゅ", "xyo" => "ょ",
43
+ "xa" => "ぁ", "xi" => "ぃ", "xu" => "ぅ", "xe" => "ぇ", "xo" => "ぉ",
44
+ "," => "、", "." => "。",
45
+ }
46
+
47
+ HIRAGANA_PREFIXES = HIRAGANA_TABLE.keys.flat_map { |s|
48
+ (s.size - 1).times.map { |i| s[0, i + 1] }
49
+ }.uniq
50
+
51
+ KATAKANA_TABLE = HIRAGANA_TABLE.transform_values { |v|
52
+ v.chars.map { |c|
53
+ c.ord.between?(0x3041, 0x3096) ? (c.ord + 0x60).chr("UTF-8") : c
54
+ }.join
55
+ }
56
+
57
+ HANKAKU_KATAKANA_TABLE = {
58
+ "a" => "ア", "i" => "イ", "u" => "ウ", "e" => "エ", "o" => "オ",
59
+ "ka" => "カ", "ki" => "キ", "ku" => "ク", "ke" => "ケ", "ko" => "コ",
60
+ "ga" => "ガ", "gi" => "ギ", "gu" => "グ", "ge" => "ゲ", "go" => "ゴ",
61
+ "sa" => "サ", "si" => "シ", "su" => "ス", "se" => "セ", "so" => "ソ",
62
+ "za" => "ザ", "zi" => "ジ", "zu" => "ズ", "ze" => "ゼ", "zo" => "ゾ",
63
+ "sha" => "シャ", "shi" => "シ", "shu" => "シュ", "she" => "シェ", "sho" => "ショ",
64
+ "ja" => "ジャ", "ji" => "ジ", "ju" => "ジュ", "je" => "ジェ", "jo" => "ジョ",
65
+ "ta" => "タ", "ti" => "チ", "tu" => "ツ", "te" => "テ", "to" => "ト",
66
+ "da" => "ダ", "di" => "ヂ", "du" => "ヅ", "de" => "デ", "do" => "ド",
67
+ "cha" => "チャ", "chi" => "チ", "chu" => "チュ", "che" => "チェ", "cho" => "チョ",
68
+ "na" => "ナ", "ni" => "ニ", "nu" => "ヌ", "ne" => "ネ", "no" => "ノ",
69
+ "ha" => "ハ", "hi" => "ヒ", "hu" => "フ", "he" => "ヘ", "ho" => "ホ",
70
+ "ba" => "バ", "bi" => "ビ", "bu" => "ブ", "be" => "ベ", "bo" => "ボ",
71
+ "pa" => "パ", "pi" => "ピ", "pu" => "プ", "pe" => "ペ", "po" => "ポ",
72
+ "ma" => "マ", "mi" => "ミ", "mu" => "ム", "me" => "メ", "mo" => "モ",
73
+ "ya" => "ヤ", "yu" => "ユ", "yo" => "ヨ",
74
+ "ra" => "ラ", "ri" => "リ", "ru" => "ル", "re" => "レ", "ro" => "ロ",
75
+ "wa" => "ワ", "wo" => "ヲ",
76
+ "nn" => "ン",
77
+ "kya" => "キャ", "kyu" => "キュ", "kyo" => "キョ",
78
+ "gya" => "ギャ", "gyu" => "ギュ", "gyo" => "ギョ",
79
+ "sya" => "シャ", "syu" => "シュ", "syo" => "ショ",
80
+ "zya" => "ジャ", "zyu" => "ジュ", "zyo" => "ジョ",
81
+ "tya" => "チャ", "tyu" => "チュ", "tyo" => "チョ",
82
+ "dya" => "ヂャ", "dyu" => "ヂュ", "dyo" => "ヂョ",
83
+ "nya" => "ニャ", "nyu" => "ニュ", "nyo" => "ニョ",
84
+ "hya" => "ヒャ", "hyu" => "ヒュ", "hyo" => "ヒョ",
85
+ "bya" => "ビャ", "byu" => "ビュ", "byo" => "ビョ",
86
+ "pya" => "ピャ", "pyu" => "ピュ", "pyo" => "ピョ",
87
+ "mya" => "ミャ", "myu" => "ミュ", "myo" => "ミョ",
88
+ "rya" => "リャ", "ryu" => "リュ", "ryo" => "リョ",
89
+ "fa" => "ファ", "fi" => "フィ", "fu" => "フ", "fe" => "フェ", "fo" => "フォ",
90
+ "," => "、", "." => "。",
91
+ }
92
+
93
+ HANKAKU_KATAKANA_PREFIXES = HANKAKU_KATAKANA_TABLE.keys.flat_map { |s|
94
+ (s.size - 1).times.map { |i| s[0, i + 1] }
95
+ }.uniq
96
+
97
+ DICTIONARY_PATH = File.expand_path("~/.textbringer/skk/SKK-JISYO.L")
98
+
99
+ DEFAULT_CURSOR_COLORS = {
100
+ hiragana: "pink",
101
+ katakana: "green",
102
+ hankaku_katakana: "blue",
103
+ zenkaku_ascii: "yellow",
104
+ ascii: nil, # nil = reset to terminal default
105
+ }
106
+
107
+ def initialize
108
+ super
109
+ @mode = :hiragana # :hiragana | :katakana | :hankaku_katakana | :zenkaku_ascii | :ascii
110
+ @phase = :normal # :normal | :converting | :selecting
111
+ @roman_buffer = +""
112
+ @yomi = +""
113
+ @okuri_roman = nil
114
+ @okuri_kana = nil
115
+ @candidates = []
116
+ @candidate_index = 0
117
+ @marker_pos = nil
118
+ @okuriiari = nil
119
+ @okurinasi = nil
120
+ end
121
+
122
+ def toggle
123
+ super
124
+ if @enabled
125
+ update_cursor_color
126
+ else
127
+ reset_cursor_color
128
+ end
129
+ end
130
+
131
+ def disable
132
+ super
133
+ reset_cursor_color
134
+ end
135
+
136
+ def status
137
+ case @phase
138
+ when :converting then "▽"
139
+ when :selecting then "▼"
140
+ else
141
+ { hiragana: "あ", katakana: "ア", hankaku_katakana: "ア",
142
+ zenkaku_ascii: "A", ascii: "A" }[@mode]
143
+ end
144
+ end
145
+
146
+ def handle_event(event)
147
+ case @phase
148
+ when :normal then handle_normal(event)
149
+ when :converting then handle_converting(event)
150
+ when :selecting then handle_selecting(event)
151
+ end
152
+ end
153
+
154
+ private
155
+
156
+ def handle_normal(event)
157
+ unless event.is_a?(String)
158
+ @roman_buffer = +""
159
+ return event
160
+ end
161
+
162
+ case event
163
+ when "\C-j"
164
+ @roman_buffer = +""
165
+ @mode = :hiragana
166
+ Window.redisplay
167
+ update_cursor_color
168
+ nil
169
+ when "\C-q"
170
+ if [:hiragana, :katakana].include?(@mode)
171
+ @roman_buffer = +""
172
+ @mode = :hankaku_katakana
173
+ Window.redisplay
174
+ update_cursor_color
175
+ elsif @mode == :hankaku_katakana
176
+ @roman_buffer = +""
177
+ @mode = :hiragana
178
+ Window.redisplay
179
+ update_cursor_color
180
+ else
181
+ return process_romaji(event)
182
+ end
183
+ nil
184
+ when "q"
185
+ if @mode == :hiragana
186
+ @roman_buffer = +""
187
+ @mode = :katakana
188
+ Window.redisplay
189
+ update_cursor_color
190
+ nil
191
+ elsif @mode == :katakana || @mode == :hankaku_katakana
192
+ @roman_buffer = +""
193
+ @mode = :hiragana
194
+ Window.redisplay
195
+ update_cursor_color
196
+ nil
197
+ else
198
+ process_romaji(event)
199
+ end
200
+ when "l"
201
+ if [:hiragana, :katakana, :hankaku_katakana].include?(@mode)
202
+ @roman_buffer = +""
203
+ @mode = :ascii
204
+ Window.redisplay
205
+ update_cursor_color
206
+ nil
207
+ else
208
+ process_romaji(event)
209
+ end
210
+ when "L"
211
+ if [:hiragana, :katakana, :hankaku_katakana].include?(@mode)
212
+ @roman_buffer = +""
213
+ @mode = :zenkaku_ascii
214
+ Window.redisplay
215
+ update_cursor_color
216
+ nil
217
+ else
218
+ process_romaji(event)
219
+ end
220
+ when /\A[A-Z]\z/
221
+ if [:hiragana, :katakana, :hankaku_katakana].include?(@mode)
222
+ start_converting(event.downcase)
223
+ else
224
+ process_romaji(event)
225
+ end
226
+ when /\A[\x00-\x09\x0b-\x1f\x7f]\z/
227
+ # Control characters other than C-j and C-q pass through unchanged
228
+ @roman_buffer = +""
229
+ event
230
+ else
231
+ process_romaji(event)
232
+ end
233
+ end
234
+
235
+ def handle_converting(event)
236
+ unless event.is_a?(String)
237
+ commit_converting
238
+ return event
239
+ end
240
+
241
+ # Control characters not handled below: commit conversion and pass through
242
+ if event.bytesize == 1 && (event.ord < 0x20 || event.ord == 0x7f) &&
243
+ event != "\C-g" && event != "\C-j"
244
+ commit_converting
245
+ return event
246
+ end
247
+
248
+ case event
249
+ when "\C-g"
250
+ cancel_converting
251
+ nil
252
+ when "\C-j"
253
+ commit_converting
254
+ nil
255
+ when " "
256
+ start_selecting
257
+ nil
258
+ when /\A[A-Z]\z/
259
+ if @okuri_roman.nil?
260
+ start_okurigana(event.downcase)
261
+ else
262
+ process_converting_romaji(event.downcase)
263
+ end
264
+ nil
265
+ else
266
+ process_converting_romaji(event)
267
+ end
268
+ end
269
+
270
+ def handle_selecting(event)
271
+ unless event.is_a?(String)
272
+ confirm_selecting
273
+ return event
274
+ end
275
+
276
+ case event
277
+ when "\C-g"
278
+ cancel_selecting
279
+ nil
280
+ when " "
281
+ next_candidate
282
+ nil
283
+ when "x"
284
+ prev_candidate
285
+ nil
286
+ when "\C-m", "\r", "\n"
287
+ confirm_selecting
288
+ nil
289
+ else
290
+ confirm_selecting
291
+ handle_event(event)
292
+ end
293
+ end
294
+
295
+ def process_romaji(event)
296
+ case @mode
297
+ when :ascii
298
+ return event
299
+ when :zenkaku_ascii
300
+ if event.ord.between?(0x21, 0x7E)
301
+ return (event.ord + 0xFEE0).chr("UTF-8")
302
+ else
303
+ return event
304
+ end
305
+ end
306
+
307
+ # Special "n" handling: flush "ん" before appending if next char won't extend "n"
308
+ if @roman_buffer == "n" && !%w[n y a i u e o].include?(event)
309
+ kana = kana_for_mode("ん")
310
+ @roman_buffer = +""
311
+ insert_kana(kana)
312
+ end
313
+
314
+ @roman_buffer << event
315
+
316
+ table = current_table
317
+ prefixes = current_prefixes
318
+
319
+ kana = table[@roman_buffer]
320
+ if kana
321
+ @roman_buffer = +""
322
+ insert_kana(kana_for_mode(kana))
323
+ return nil
324
+ end
325
+
326
+ if prefixes.include?(@roman_buffer)
327
+ return nil
328
+ end
329
+
330
+ # Double consonant: e.g. "kk" → っ + keep second "k"
331
+ if @roman_buffer.size >= 2
332
+ first = @roman_buffer[0]
333
+ rest = @roman_buffer[1..]
334
+ if first == rest[0] && first =~ /[bcdfghjklmnpqrstvwxyz]/
335
+ geminate = kana_for_mode("っ")
336
+ @roman_buffer = +rest
337
+ insert_kana(geminate)
338
+ # Now check if rest completes a kana
339
+ kana2 = table[@roman_buffer]
340
+ if kana2
341
+ @roman_buffer = +""
342
+ insert_kana(kana_for_mode(kana2))
343
+ end
344
+ return nil
345
+ end
346
+ end
347
+
348
+ # No match: if single char, return it to let the controller handle it
349
+ # (consistent with HiraganaInputMethod#flush for unrecognized chars)
350
+ if @roman_buffer.size == 1
351
+ char = @roman_buffer
352
+ @roman_buffer = +""
353
+ return char
354
+ end
355
+
356
+ # Multi-char: flush first char as-is, retry with last char
357
+ first_char = @roman_buffer[0]
358
+ last_char = @roman_buffer[-1]
359
+ @roman_buffer = +""
360
+ with_target_buffer { |b| b.insert(first_char) }
361
+ Window.redisplay
362
+ process_romaji(last_char)
363
+ end
364
+
365
+ def insert_kana(kana)
366
+ with_target_buffer do |buffer|
367
+ buffer.insert(kana)
368
+ end
369
+ Window.redisplay
370
+ nil
371
+ end
372
+
373
+ def process_converting_romaji(event)
374
+ # Special "n" handling: flush "ん" before appending if next char won't extend "n"
375
+ if @roman_buffer == "n" && !%w[n y a i u e o].include?(event)
376
+ @roman_buffer = +""
377
+ append_yomi_kana("ん")
378
+ end
379
+
380
+ @roman_buffer << event
381
+
382
+ table = hiragana_table_for_converting
383
+ prefixes = hiragana_prefixes_for_converting
384
+
385
+ kana = table[@roman_buffer]
386
+ if kana
387
+ @roman_buffer = +""
388
+ if @okuri_roman
389
+ # Completing okurigana
390
+ @okuri_kana = kana
391
+ with_target_buffer do |buffer|
392
+ buffer.insert(kana)
393
+ end
394
+ Window.redisplay
395
+ start_selecting
396
+ else
397
+ append_yomi_kana(kana)
398
+ end
399
+ return
400
+ end
401
+
402
+ if prefixes.include?(@roman_buffer)
403
+ return
404
+ end
405
+
406
+ # Double consonant handling
407
+ if @roman_buffer.size >= 2
408
+ first = @roman_buffer[0]
409
+ rest = @roman_buffer[1..]
410
+ if first == rest[0] && first =~ /[bcdfghjklmnpqrstvwxyz]/
411
+ append_yomi_kana("っ")
412
+ @roman_buffer = +rest # Keep the second consonant buffered
413
+ return
414
+ end
415
+ end
416
+
417
+ # No match: if single char, insert as-is
418
+ if @roman_buffer.size == 1
419
+ char = @roman_buffer
420
+ @roman_buffer = +""
421
+ append_yomi_kana(char)
422
+ return
423
+ end
424
+
425
+ # Multi-char: flush first char as-is, retry with last char
426
+ first_char = @roman_buffer[0]
427
+ last_char = @roman_buffer[-1]
428
+ @roman_buffer = +""
429
+ append_yomi_kana(first_char)
430
+ process_converting_romaji(last_char)
431
+ end
432
+
433
+ def append_yomi_kana(kana)
434
+ @yomi << kana
435
+ with_target_buffer do |buffer|
436
+ buffer.insert(kana)
437
+ end
438
+ Window.redisplay
439
+ end
440
+
441
+ def start_converting(first_char)
442
+ @phase = :converting
443
+ @yomi = +""
444
+ @okuri_roman = nil
445
+ @okuri_kana = nil
446
+ @roman_buffer = +""
447
+ with_target_buffer do |buffer|
448
+ @marker_pos = buffer.point
449
+ buffer.insert("▽")
450
+ end
451
+ Window.redisplay
452
+ update_cursor_color
453
+ process_converting_romaji(first_char)
454
+ nil
455
+ end
456
+
457
+ def start_okurigana(consonant)
458
+ @okuri_roman = consonant.dup
459
+ @roman_buffer = consonant.dup
460
+ end
461
+
462
+ def cancel_converting
463
+ with_target_buffer do |buffer|
464
+ buffer.delete_region(@marker_pos, buffer.point)
465
+ end
466
+ @phase = :normal
467
+ @yomi = +""
468
+ @roman_buffer = +""
469
+ @okuri_roman = nil
470
+ @okuri_kana = nil
471
+ @marker_pos = nil
472
+ Window.redisplay
473
+ update_cursor_color
474
+ end
475
+
476
+ def commit_converting
477
+ with_target_buffer do |buffer|
478
+ # Remove the ▽ marker (3 bytes for ▽ in UTF-8)
479
+ marker_end = @marker_pos + "▽".bytesize
480
+ buffer.delete_region(@marker_pos, marker_end)
481
+ end
482
+ @phase = :normal
483
+ @roman_buffer = +""
484
+ @okuri_roman = nil
485
+ @okuri_kana = nil
486
+ @marker_pos = nil
487
+ Window.redisplay
488
+ update_cursor_color
489
+ end
490
+
491
+ def start_selecting
492
+ ensure_dictionary_loaded
493
+
494
+ lookup_key = if @okuri_roman
495
+ @yomi + @okuri_roman
496
+ else
497
+ @yomi
498
+ end
499
+
500
+ dict = @okuri_roman ? @okuriiari : @okurinasi
501
+ candidates = dict[lookup_key]
502
+
503
+ if candidates.nil? || candidates.empty?
504
+ message("No conversion: #{@yomi}")
505
+ return
506
+ end
507
+
508
+ @candidates = candidates
509
+ @candidate_index = 0
510
+ @phase = :selecting
511
+
512
+ with_target_buffer do |buffer|
513
+ buffer.delete_region(@marker_pos, buffer.point)
514
+ buffer.insert("▼" + @candidates[0] + (@okuri_kana || ""))
515
+ end
516
+ Window.redisplay
517
+ update_cursor_color
518
+ end
519
+
520
+ def next_candidate
521
+ @candidate_index = (@candidate_index + 1) % @candidates.size
522
+ replace_candidate
523
+ end
524
+
525
+ def prev_candidate
526
+ @candidate_index = (@candidate_index - 1 + @candidates.size) % @candidates.size
527
+ replace_candidate
528
+ end
529
+
530
+ def replace_candidate
531
+ with_target_buffer do |buffer|
532
+ buffer.delete_region(@marker_pos, buffer.point)
533
+ buffer.insert("▼" + @candidates[@candidate_index] + (@okuri_kana || ""))
534
+ end
535
+ Window.redisplay
536
+ end
537
+
538
+ def confirm_selecting
539
+ candidate = @candidates[@candidate_index]
540
+ with_target_buffer do |buffer|
541
+ buffer.delete_region(@marker_pos, buffer.point)
542
+ buffer.insert(candidate + (@okuri_kana || ""))
543
+ end
544
+ @phase = :normal
545
+ @yomi = +""
546
+ @roman_buffer = +""
547
+ @okuri_roman = nil
548
+ @okuri_kana = nil
549
+ @candidates = []
550
+ @candidate_index = 0
551
+ @marker_pos = nil
552
+ Window.redisplay
553
+ update_cursor_color
554
+ end
555
+
556
+ def cancel_selecting
557
+ with_target_buffer do |buffer|
558
+ buffer.delete_region(@marker_pos, buffer.point)
559
+ buffer.insert("▽" + @yomi + (@okuri_kana || ""))
560
+ end
561
+ @phase = :converting
562
+ @roman_buffer = +""
563
+ @candidates = []
564
+ @candidate_index = 0
565
+ Window.redisplay
566
+ update_cursor_color
567
+ end
568
+
569
+ def ensure_dictionary_loaded
570
+ return if @okuriiari
571
+
572
+ path = CONFIG[:skk_dictionary] || DICTIONARY_PATH
573
+ @okuriiari = {}
574
+ @okurinasi = {}
575
+ section = :okuriiari
576
+
577
+ File.foreach(path, encoding: "EUC-JP:UTF-8") do |line|
578
+ line.chomp!
579
+ if line == ";; okuri-nasi entries."
580
+ section = :okurinasi
581
+ next
582
+ end
583
+ next if line.start_with?(";") || line.empty?
584
+
585
+ key, rest = line.split(" /", 2)
586
+ next unless key && rest
587
+
588
+ candidates = rest.split("/").map { |c| c.split(";").first&.strip }.compact.reject(&:empty?)
589
+ next if candidates.empty?
590
+
591
+ if section == :okuriiari
592
+ @okuriiari[key] = candidates
593
+ else
594
+ @okurinasi[key] = candidates
595
+ end
596
+ end
597
+ end
598
+
599
+ def kana_for_mode(hiragana_kana)
600
+ case @mode
601
+ when :hiragana
602
+ hiragana_kana
603
+ when :katakana
604
+ hiragana_to_katakana(hiragana_kana)
605
+ when :hankaku_katakana
606
+ hiragana_to_hankaku_katakana(hiragana_kana)
607
+ else
608
+ hiragana_kana
609
+ end
610
+ end
611
+
612
+ def hiragana_to_katakana(kana)
613
+ kana.chars.map { |c|
614
+ c.ord.between?(0x3041, 0x3096) ? (c.ord + 0x60).chr("UTF-8") : c
615
+ }.join
616
+ end
617
+
618
+ def hiragana_to_hankaku_katakana(kana)
619
+ kana.chars.map { |c|
620
+ case c.ord
621
+ when 0x3041 then "ァ"
622
+ when 0x3042 then "ア"
623
+ when 0x3043 then "ィ"
624
+ when 0x3044 then "イ"
625
+ when 0x3045 then "ゥ"
626
+ when 0x3046 then "ウ"
627
+ when 0x3047 then "ェ"
628
+ when 0x3048 then "エ"
629
+ when 0x3049 then "ォ"
630
+ when 0x304a then "オ"
631
+ when 0x304b then "カ"
632
+ when 0x304c then "ガ"
633
+ when 0x304d then "キ"
634
+ when 0x304e then "ギ"
635
+ when 0x304f then "ク"
636
+ when 0x3050 then "グ"
637
+ when 0x3051 then "ケ"
638
+ when 0x3052 then "ゲ"
639
+ when 0x3053 then "コ"
640
+ when 0x3054 then "ゴ"
641
+ when 0x3055 then "サ"
642
+ when 0x3056 then "ザ"
643
+ when 0x3057 then "シ"
644
+ when 0x3058 then "ジ"
645
+ when 0x3059 then "ス"
646
+ when 0x305a then "ズ"
647
+ when 0x305b then "セ"
648
+ when 0x305c then "ゼ"
649
+ when 0x305d then "ソ"
650
+ when 0x305e then "ゾ"
651
+ when 0x305f then "タ"
652
+ when 0x3060 then "ダ"
653
+ when 0x3061 then "チ"
654
+ when 0x3062 then "ヂ"
655
+ when 0x3063 then "ッ"
656
+ when 0x3064 then "ツ"
657
+ when 0x3065 then "ヅ"
658
+ when 0x3066 then "テ"
659
+ when 0x3067 then "デ"
660
+ when 0x3068 then "ト"
661
+ when 0x3069 then "ド"
662
+ when 0x306a then "ナ"
663
+ when 0x306b then "ニ"
664
+ when 0x306c then "ヌ"
665
+ when 0x306d then "ネ"
666
+ when 0x306e then "ノ"
667
+ when 0x306f then "ハ"
668
+ when 0x3070 then "バ"
669
+ when 0x3071 then "パ"
670
+ when 0x3072 then "ヒ"
671
+ when 0x3073 then "ビ"
672
+ when 0x3074 then "ピ"
673
+ when 0x3075 then "フ"
674
+ when 0x3076 then "ブ"
675
+ when 0x3077 then "プ"
676
+ when 0x3078 then "ヘ"
677
+ when 0x3079 then "ベ"
678
+ when 0x307a then "ペ"
679
+ when 0x307b then "ホ"
680
+ when 0x307c then "ボ"
681
+ when 0x307d then "ポ"
682
+ when 0x307e then "マ"
683
+ when 0x307f then "ミ"
684
+ when 0x3080 then "ム"
685
+ when 0x3081 then "メ"
686
+ when 0x3082 then "モ"
687
+ when 0x3083 then "ャ"
688
+ when 0x3084 then "ヤ"
689
+ when 0x3085 then "ュ"
690
+ when 0x3086 then "ユ"
691
+ when 0x3087 then "ョ"
692
+ when 0x3088 then "ヨ"
693
+ when 0x3089 then "ラ"
694
+ when 0x308a then "リ"
695
+ when 0x308b then "ル"
696
+ when 0x308c then "レ"
697
+ when 0x308d then "ロ"
698
+ when 0x308e then "ワ"
699
+ when 0x308f then "ワ"
700
+ when 0x3090 then "イ"
701
+ when 0x3091 then "エ"
702
+ when 0x3092 then "ヲ"
703
+ when 0x3093 then "ン"
704
+ else c
705
+ end
706
+ }.join
707
+ end
708
+
709
+ def current_table
710
+ case @mode
711
+ when :katakana then KATAKANA_TABLE
712
+ when :hankaku_katakana then HANKAKU_KATAKANA_TABLE
713
+ else HIRAGANA_TABLE
714
+ end
715
+ end
716
+
717
+ def current_prefixes
718
+ case @mode
719
+ when :hankaku_katakana then HANKAKU_KATAKANA_PREFIXES
720
+ else HIRAGANA_PREFIXES
721
+ end
722
+ end
723
+
724
+ # During converting phase, always use hiragana for yomi tracking
725
+ def hiragana_table_for_converting
726
+ HIRAGANA_TABLE
727
+ end
728
+
729
+ def hiragana_prefixes_for_converting
730
+ HIRAGANA_PREFIXES
731
+ end
732
+
733
+ def update_cursor_color
734
+ return unless STDOUT.tty?
735
+ colors = CONFIG[:skk_cursor_colors] || DEFAULT_CURSOR_COLORS
736
+ color = colors[@mode]
737
+ if color
738
+ STDOUT.write("\e]12;#{color}\a")
739
+ else
740
+ reset_cursor_color
741
+ end
742
+ STDOUT.flush
743
+ end
744
+
745
+ def reset_cursor_color
746
+ return unless STDOUT.tty?
747
+ STDOUT.write("\e]112\a")
748
+ STDOUT.flush
749
+ end
750
+ end
751
+ end