textbringer 1.0.1 → 1.1.0

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ubuntu.yml +5 -9
  3. data/.github/workflows/windows.yml +4 -7
  4. data/CHANGES.md +47 -0
  5. data/README.md +0 -2
  6. data/bin/merge_mazegaki_dic +25 -0
  7. data/exe/textbringer +6 -5
  8. data/lib/textbringer.rb +4 -0
  9. data/lib/textbringer/buffer.rb +40 -3
  10. data/lib/textbringer/commands/clipboard.rb +3 -3
  11. data/lib/textbringer/commands/ctags.rb +3 -3
  12. data/lib/textbringer/commands/dabbrev.rb +1 -1
  13. data/lib/textbringer/commands/files.rb +18 -3
  14. data/lib/textbringer/commands/help.rb +25 -16
  15. data/lib/textbringer/commands/input_method.rb +18 -0
  16. data/lib/textbringer/commands/isearch.rb +23 -2
  17. data/lib/textbringer/commands/misc.rb +8 -3
  18. data/lib/textbringer/commands/windows.rb +8 -4
  19. data/lib/textbringer/config.rb +2 -1
  20. data/lib/textbringer/controller.rb +7 -1
  21. data/lib/textbringer/input_method.rb +63 -0
  22. data/lib/textbringer/input_methods/hiragana_input_method.rb +70 -0
  23. data/lib/textbringer/input_methods/t_code_input_method.rb +458 -0
  24. data/lib/textbringer/input_methods/t_code_input_method/tables.rb +64 -0
  25. data/lib/textbringer/keymap.rb +45 -26
  26. data/lib/textbringer/modes/backtrace_mode.rb +1 -1
  27. data/lib/textbringer/modes/buffer_list_mode.rb +1 -1
  28. data/lib/textbringer/modes/c_mode.rb +4 -0
  29. data/lib/textbringer/modes/completion_list_mode.rb +1 -1
  30. data/lib/textbringer/modes/help_mode.rb +1 -1
  31. data/lib/textbringer/modes/programming_mode.rb +24 -2
  32. data/lib/textbringer/modes/ruby_mode.rb +70 -12
  33. data/lib/textbringer/utils.rb +10 -5
  34. data/lib/textbringer/version.rb +1 -1
  35. data/lib/textbringer/window.rb +25 -10
  36. data/textbringer.gemspec +4 -5
  37. metadata +19 -28
  38. data/.github/workflows/ruby-head.yml +0 -30
@@ -1,6 +1,6 @@
1
1
  module Textbringer
2
2
  module Commands
3
- ISEARCH_MODE_MAP = Keymap.new
3
+ define_keymap :ISEARCH_MODE_MAP
4
4
  (?\x20..?\x7e).each do |c|
5
5
  ISEARCH_MODE_MAP.define_key(c, :isearch_printing_char)
6
6
  end
@@ -14,11 +14,14 @@ module Commands
14
14
  end
15
15
  ISEARCH_MODE_MAP.define_key(:backspace, :isearch_delete_char)
16
16
  ISEARCH_MODE_MAP.define_key(?\C-h, :isearch_delete_char)
17
+ ISEARCH_MODE_MAP.define_key(?\C-?, :isearch_delete_char)
17
18
  ISEARCH_MODE_MAP.define_key(?\C-s, :isearch_repeat_forward)
18
19
  ISEARCH_MODE_MAP.define_key(?\C-r, :isearch_repeat_backward)
19
20
  ISEARCH_MODE_MAP.define_key(?\C-w, :isearch_yank_word_or_char)
20
21
  ISEARCH_MODE_MAP.define_key(?\C-m, :isearch_exit)
21
22
  ISEARCH_MODE_MAP.define_key(?\C-g, :isearch_abort)
23
+ ISEARCH_MODE_MAP.define_key(?\C-q, :isearch_quoted_insert)
24
+ ISEARCH_MODE_MAP.define_key(?\C-\\, :isearch_toggle_input_method)
22
25
 
23
26
  ISEARCH_STATUS = {
24
27
  forward: true,
@@ -55,6 +58,10 @@ def isearch_mode(forward, recursive_edit: false)
55
58
  end
56
59
  end
57
60
 
61
+ def isearch_mode?
62
+ Controller.current.overriding_map == ISEARCH_MODE_MAP
63
+ end
64
+
58
65
  def isearch_prompt
59
66
  if ISEARCH_STATUS[:forward]
60
67
  "I-search: "
@@ -114,6 +121,14 @@ def isearch_done
114
121
  end
115
122
  end
116
123
 
124
+ define_command(:isearch_quoted_insert, doc: <<~EOD) do
125
+ Read a character, and add it to the search string and search.
126
+ EOD
127
+ c = Controller.current.read_event
128
+ ISEARCH_STATUS[:string].concat(c)
129
+ isearch_search
130
+ end
131
+
117
132
  def isearch_search
118
133
  forward = ISEARCH_STATUS[:forward]
119
134
  options = if /\A[A-Z]/.match?(ISEARCH_STATUS[:string])
@@ -124,7 +139,7 @@ def isearch_search
124
139
  re = Regexp.new(Regexp.quote(ISEARCH_STATUS[:string]), options)
125
140
  last_pos = ISEARCH_STATUS[:last_pos]
126
141
  offset = forward ? last_pos : last_pos - ISEARCH_STATUS[:string].bytesize
127
- if Buffer.current.byteindex(forward, re, offset)
142
+ if offset >= 0 && Buffer.current.byteindex(forward, re, offset)
128
143
  if Buffer.current != Buffer.minibuffer
129
144
  message(isearch_prompt + ISEARCH_STATUS[:string], log: false)
130
145
  end
@@ -155,5 +170,11 @@ def isearch_repeat(forward)
155
170
  end
156
171
  isearch_search
157
172
  end
173
+
174
+ define_command(:isearch_toggle_input_method,
175
+ doc: "Toggle input method.") do
176
+ toggle_input_method
177
+ message(isearch_prompt + ISEARCH_STATUS[:string], log: false)
178
+ end
158
179
  end
159
180
  end
@@ -6,7 +6,12 @@ module Commands
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)
@@ -139,7 +144,7 @@ def complete_minibuffer_with_string(s)
139
144
  end
140
145
  end
141
146
 
142
- UNIVERSAL_ARGUMENT_MAP = Keymap.new
147
+ define_keymap :UNIVERSAL_ARGUMENT_MAP
143
148
  (?0..?9).each do |c|
144
149
  UNIVERSAL_ARGUMENT_MAP.define_key(c, :digit_argument)
145
150
  GLOBAL_MAP.define_key("\e#{c}", :digit_argument)
@@ -276,7 +281,7 @@ def goto_global_mark
276
281
  Window.redisplay
277
282
  signals = [:INT, :TERM, :KILL]
278
283
  begin
279
- opts = /mswin32|mingw32/ =~ RUBY_PLATFORM ? {} : {pgroup: true}
284
+ opts = /mswin|mingw/ =~ RUBY_PLATFORM ? {} : {pgroup: true}
280
285
  if CONFIG[:shell_file_name]
281
286
  cmd = [CONFIG[:shell_file_name], CONFIG[:shell_command_switch], cmd]
282
287
  end
@@ -69,9 +69,13 @@ module Commands
69
69
  define_command(:switch_to_buffer, doc: <<~EOD) do
70
70
  Display buffer in the current window.
71
71
  EOD
72
- |buffer = read_buffer("Switch to buffer: ")|
72
+ |buffer = read_buffer("Switch to buffer: "), arg = current_prefix_arg|
73
73
  if buffer.is_a?(String)
74
- buffer = Buffer[buffer]
74
+ if arg
75
+ buffer = Buffer.find_or_new(buffer)
76
+ else
77
+ buffer = Buffer[buffer]
78
+ end
75
79
  end
76
80
  if buffer
77
81
  Window.current.buffer = Buffer.current = buffer
@@ -80,7 +84,7 @@ module Commands
80
84
  end
81
85
  end
82
86
 
83
- define_command(:list_buffers, doc: <<~EOD) do
87
+ define_command(:list_buffers, doc: <<~EOD) do |buffers = Buffer.list|
84
88
  List the existing buffers.
85
89
  EOD
86
90
  buffer = Buffer.find_or_new("*Buffer List*",
@@ -88,7 +92,7 @@ module Commands
88
92
  buffer.apply_mode(BufferListMode)
89
93
  buffer.read_only_edit do
90
94
  buffer.clear
91
- buffer.insert(Buffer.list.map(&:name).join("\n"))
95
+ buffer.insert(buffers.map(&:name).join("\n"))
92
96
  buffer.beginning_of_buffer
93
97
  end
94
98
  switch_to_buffer(buffer)
@@ -15,6 +15,7 @@ module Textbringer
15
15
  shell_file_name: ENV["SHELL"],
16
16
  shell_command_switch: "-c",
17
17
  grep_command: "grep -nH -e",
18
- fill_column: 70
18
+ fill_column: 70,
19
+ default_input_method: "t_code"
19
20
  }
20
21
  end
@@ -264,7 +264,13 @@ def read_event_with_keyboard_macro(read_event_method)
264
264
  end
265
265
 
266
266
  def call_read_event_method(read_event_method)
267
- Window.current.send(read_event_method)
267
+ Window.current.send(read_event_method)&.then { |event|
268
+ if @key_sequence.empty?
269
+ Buffer.current.filter_event(event)
270
+ else
271
+ event
272
+ end
273
+ }
268
274
  end
269
275
  end
270
276
  end
@@ -0,0 +1,63 @@
1
+ module Textbringer
2
+ class InputMethod
3
+ extend Commands
4
+ include Commands
5
+
6
+ @@list = []
7
+
8
+ def self.inherited(subclass)
9
+ name = subclass.name.sub(/Textbringer::/, "").sub(/InputMethod/, "").
10
+ gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').
11
+ downcase
12
+ @@list.push(name)
13
+ end
14
+
15
+ def self.list
16
+ @@list
17
+ end
18
+
19
+ def self.find(name)
20
+ class_name = name.split(/_/).map(&:capitalize).join + "InputMethod"
21
+ Textbringer.const_get(class_name).new
22
+ rescue NameError
23
+ raise EditorError, "No such input method: #{name}"
24
+ end
25
+
26
+ def initialize
27
+ @enabled = false
28
+ @skip_next_event = false
29
+ end
30
+
31
+ def toggle
32
+ @enabled = !@enabled
33
+ end
34
+
35
+ def disable
36
+ @enabled = false
37
+ end
38
+
39
+ def enabled?
40
+ @enabled
41
+ end
42
+
43
+ def filter_event(event)
44
+ if @enabled
45
+ if event == "\e"
46
+ @skip_next_event = true
47
+ event
48
+ elsif @skip_next_event
49
+ @skip_next_event = false
50
+ event
51
+ else
52
+ handle_event(event)
53
+ end
54
+ else
55
+ event
56
+ end
57
+ end
58
+
59
+ def handle_event(event)
60
+ raise EditorError, "subclass must override InputMethod#handle_event"
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Textbringer
4
+ class HiraganaInputMethod < InputMethod
5
+ HIRAGANA_TABLE = {
6
+ "a" => "あ", "i" => "い", "u" => "う", "e" => "え", "o" => "お",
7
+ "ka" => "か", "ki" => "き", "ku" => "く", "ke" => "け", "ko" => "こ",
8
+ "ga" => "が", "gi" => "ぎ", "gu" => "ぐ", "ge" => "げ", "go" => "ご",
9
+ "sa" => "さ", "si" => "し", "su" => "す", "se" => "せ", "so" => "そ",
10
+ "za" => "ざ", "zi" => "じ", "zu" => "ず", "ze" => "ぜ", "zo" => "ぞ",
11
+ "sha" => "しゃ", "shi" => "し", "shu" => "しゅ", "she" => "しぇ", "sho" => "しょ",
12
+ "ja" => "じゃ", "ji" => "じ", "ju" => "じゅ", "je" => "じぇ", "jo" => "じょ",
13
+ "ta" => "た", "ti" => "ち", "tu" => "つ", "te" => "て", "to" => "と",
14
+ "da" => "だ", "di" => "ぢ", "du" => "づ", "de" => "で", "do" => "ど",
15
+ "cha" => "ちゃ", "chi" => "ち", "chu" => "ちゅ", "che" => "ちぇ", "cho" => "ちょ",
16
+ "na" => "な", "ni" => "に", "nu" => "ぬ", "ne" => "ね", "no" => "の",
17
+ "ha" => "は", "hi" => "ひ", "hu" => "ふ", "he" => "へ", "ho" => "ほ",
18
+ "ba" => "ば", "bi" => "び", "bu" => "ぶ", "be" => "べ", "bo" => "ぼ",
19
+ "pa" => "ぱ", "pi" => "ぴ", "pu" => "ぷ", "pe" => "ぺ", "po" => "ぽ",
20
+ "ma" => "ま", "mi" => "み", "mu" => "む", "me" => "め", "mo" => "も",
21
+ "ya" => "や", "yi" => "い", "yu" => "ゆ", "ye" => "いぇ", "yo" => "よ",
22
+ "ra" => "ら", "ri" => "り", "ru" => "る", "re" => "れ", "ro" => "ろ",
23
+ "wa" => "わ", "wi" => "ゐ", "wu" => "う", "we" => "ゑ", "wo" => "を",
24
+ "nn" => "ん"
25
+ }
26
+ HIRAGANA_PREFIXES = HIRAGANA_TABLE.keys.flat_map { |s|
27
+ (s.size - 1).times.map { |i| s[0, i + 1] }
28
+ }.uniq
29
+
30
+ def initialize
31
+ super
32
+ @input_buffer = +""
33
+ end
34
+
35
+ def status
36
+ "あ"
37
+ end
38
+
39
+ def handle_event(event)
40
+ if !event.is_a?(String)
41
+ if !@input_buffer.empty?
42
+ @input_buffer = +""
43
+ end
44
+ return event
45
+ end
46
+ @input_buffer << event
47
+ s = HIRAGANA_TABLE[@input_buffer]
48
+ if s
49
+ return flush(s)
50
+ end
51
+ if HIRAGANA_PREFIXES.include?(@input_buffer)
52
+ return nil
53
+ end
54
+ flush(@input_buffer)
55
+ end
56
+
57
+ def flush(s)
58
+ if !@input_buffer.empty?
59
+ @input_buffer = +""
60
+ end
61
+ if s.size == 1
62
+ s
63
+ else
64
+ Buffer.current.insert(s)
65
+ Window.redisplay
66
+ nil
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,458 @@
1
+ module Textbringer
2
+ class TCodeInputMethod < InputMethod
3
+ require_relative "t_code_input_method/tables"
4
+
5
+ data_dir = File.expand_path("t_code_input_method", __dir__)
6
+ BUSHU_PATH = File.expand_path("bushu.rev", data_dir)
7
+ BUSHU_DIC = {} unless defined?(BUSHU_DIC)
8
+ MAZEGAKI_PATH = File.expand_path("mazegaki.dic", data_dir)
9
+ MAZEGAKI_DIC = {} unless defined?(MAZEGAKI_DIC)
10
+ MAZEGAKI_MAX_WORD_LEN = 12 # じょうほうしょりがっかい
11
+ MAZEGAKI_MAX_SUFFIX_LEN = 4
12
+
13
+ def initialize
14
+ super
15
+ @prev_key_index = nil
16
+ @mazegaki_start_pos = nil
17
+ @mazegaki_candidates = nil
18
+ @delete_help_window = false
19
+ @help_window = nil
20
+ @prev_buffer = nil
21
+ setup_dictionaries
22
+ end
23
+
24
+ def setup_dictionaries
25
+ data_dir = CONFIG[:t_code_data_dir] ||
26
+ File.expand_path("~/.textbringer/tcode")
27
+ bushu_path = File.join(data_dir, "bushu.rev")
28
+ mazegaki_path = File.join(data_dir, "mazegaki.dic")
29
+ if BUSHU_DIC.empty?
30
+ File.open(bushu_path) do |f|
31
+ f.each_line do |line|
32
+ x, *xs = line.chomp.chars
33
+ BUSHU_DIC[xs.sort.join] = x
34
+ end
35
+ end
36
+ end
37
+ if MAZEGAKI_DIC.empty?
38
+ File.open(mazegaki_path) do |f|
39
+ f.each_line do |line|
40
+ x, y = line.split
41
+ MAZEGAKI_DIC[x] = y
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def status
48
+ "漢"
49
+ end
50
+
51
+ def handle_event(event)
52
+ key_index = KEY_TABLE[event]
53
+ if @mazegaki_start_pos
54
+ if process_mazegaki_conversion(event, key_index)
55
+ return nil
56
+ end
57
+ end
58
+ if key_index.nil?
59
+ @prev_key_index = nil
60
+ return event
61
+ end
62
+ if @prev_key_index.nil?
63
+ @prev_key_index = key_index
64
+ nil
65
+ else
66
+ c = KANJI_TABLE[key_index][@prev_key_index]
67
+ @prev_key_index = nil
68
+ case c
69
+ when ?■
70
+ nil
71
+ when ?◆
72
+ bushu_compose
73
+ when ?◇
74
+ start_mazegaki_conversion(false)
75
+ when ?◈
76
+ start_mazegaki_conversion(true)
77
+ when ?⑤
78
+ show_stroke
79
+ else
80
+ c
81
+ end
82
+ end
83
+ end
84
+
85
+ def with_target_buffer(&block)
86
+ if isearch_mode?
87
+ @isearch_buffer ||= Buffer.new
88
+ if @isearch_buffer.to_s != ISEARCH_STATUS[:string]
89
+ @isearch_buffer.replace(ISEARCH_STATUS[:string])
90
+ end
91
+ block.call(@isearch_buffer)
92
+ ISEARCH_STATUS[:string] = @isearch_buffer.to_s
93
+ if Buffer.current != Buffer.minibuffer
94
+ message(isearch_prompt + ISEARCH_STATUS[:string], log: false)
95
+ Window.redisplay
96
+ end
97
+ else
98
+ block.call(Buffer.current)
99
+ end
100
+ end
101
+
102
+ def bushu_compose
103
+ with_target_buffer do |buffer|
104
+ pos = buffer.point
105
+ s = 2.times.map {
106
+ buffer.backward_char
107
+ buffer.char_after
108
+ }.sort.join
109
+ c = BUSHU_DIC[s]
110
+ if c
111
+ buffer.replace(c, start: buffer.point, end: pos)
112
+ else
113
+ buffer.goto_char(pos)
114
+ end
115
+ end
116
+ isearch_search if isearch_mode?
117
+ Window.redisplay
118
+ nil
119
+ end
120
+
121
+ def start_mazegaki_conversion(with_inflection = false)
122
+ @mazegaki_convert_with_inflection = with_inflection
123
+ pos, yomi = find_mazegaki_start_pos(with_inflection)
124
+ if pos.nil?
125
+ raise EditorError, "No mazegaki conversion candidate"
126
+ end
127
+ mazegaki_convert(pos, yomi)
128
+ end
129
+
130
+ def mazegaki_convert(pos, yomi)
131
+ with_target_buffer do |buffer|
132
+ candidates = mazegaki_lookup_candidates(yomi)
133
+ if candidates
134
+ @mazegaki_yomi = yomi
135
+ @mazegaki_suffix = buffer.substring(pos + yomi.bytesize,
136
+ buffer.point)
137
+ case candidates.size
138
+ when 1
139
+ buffer.composite_edit do
140
+ buffer.delete_region(pos, buffer.point)
141
+ buffer.insert("△" + candidates[0] + @mazegaki_suffix)
142
+ end
143
+ when 2
144
+ buffer.composite_edit do
145
+ buffer.delete_region(pos, buffer.point)
146
+ buffer.insert("△{" + candidates.join(",") + "}" +
147
+ @mazegaki_suffix)
148
+ end
149
+ else
150
+ buffer.save_excursion do
151
+ buffer.goto_char(pos)
152
+ buffer.insert("△")
153
+ end
154
+ end
155
+ @mazegaki_start_pos = pos
156
+ @mazegaki_candidates = candidates
157
+ @mazegaki_candidates_page = 0
158
+ if candidates.size > 2
159
+ show_mazegaki_candidates
160
+ end
161
+ end
162
+ Window.redisplay
163
+ nil
164
+ end
165
+ end
166
+
167
+ def mazegaki_lookup_yomi(s, with_inflectin)
168
+ if !with_inflectin
169
+ return MAZEGAKI_DIC.key?(s) ? s : nil
170
+ end
171
+ yomi = s.sub(/\p{hiragana}\z/, "")
172
+ (MAZEGAKI_MAX_SUFFIX_LEN + 1).times do
173
+ return yomi if MAZEGAKI_DIC.key?(yomi + "—")
174
+ break if !yomi.sub!(/\p{hiragana}\z/, "")
175
+ end
176
+ nil
177
+ end
178
+
179
+ def mazegaki_lookup_candidates(yomi)
180
+ if @mazegaki_convert_with_inflection
181
+ s = yomi + "—"
182
+ else
183
+ s = yomi
184
+ end
185
+ c = MAZEGAKI_DIC[s]
186
+ return nil if c.nil?
187
+ candidates = c.split("/").map { |i|
188
+ i.sub(/;.*/, "")
189
+ }.reject(&:empty?)
190
+ return nil if candidates.empty?
191
+ candidates
192
+ end
193
+
194
+ def find_mazegaki_start_pos(with_inflection)
195
+ with_target_buffer do |buffer|
196
+ buffer.save_excursion do
197
+ pos = buffer.point
198
+ start_pos = nil
199
+ yomi = nil
200
+ MAZEGAKI_MAX_WORD_LEN.times do
201
+ break if buffer.beginning_of_buffer?
202
+ buffer.backward_char
203
+ s = buffer.substring(buffer.point, pos)
204
+ y = mazegaki_lookup_yomi(s, with_inflection)
205
+ if y
206
+ start_pos = buffer.point
207
+ yomi = y
208
+ end
209
+ end
210
+ return start_pos, yomi
211
+ end
212
+ end
213
+ end
214
+
215
+ def process_mazegaki_conversion(event, key_index)
216
+ case event
217
+ when " "
218
+ mazegaki_next_page
219
+ return true
220
+ when "<"
221
+ mazegaki_relimit_left
222
+ return true
223
+ when ">"
224
+ mazegaki_relimit_right
225
+ return true
226
+ end
227
+ begin
228
+ if @mazegaki_candidates.size == 1
229
+ if event == "\C-m"
230
+ mazegaki_finish(@mazegaki_candidates[0])
231
+ return true
232
+ elsif key_index
233
+ mazegaki_finish(@mazegaki_candidates[0])
234
+ return false
235
+ end
236
+ elsif key_index
237
+ mazegaki_limit = MAZEGAKI_STROKE_PRIORITY_LIST.size
238
+ i = MAZEGAKI_STROKE_PRIORITY_LIST.index(key_index)
239
+ if i
240
+ offset = @mazegaki_candidates_page * mazegaki_limit + i
241
+ c = @mazegaki_candidates[offset]
242
+ if c
243
+ mazegaki_finish(c)
244
+ return true
245
+ end
246
+ end
247
+ end
248
+ mazegaki_reset
249
+ true
250
+ ensure
251
+ @mazegaki_start_pos = nil
252
+ @mazegaki_candidates = nil
253
+ Window.redisplay
254
+ end
255
+ end
256
+
257
+ def mazegaki_reset
258
+ with_target_buffer do |buffer|
259
+ buffer.undo
260
+ pos = @mazegaki_start_pos +
261
+ @mazegaki_yomi.bytesize + @mazegaki_suffix.bytesize
262
+ buffer.goto_char(pos)
263
+ hide_help_window
264
+ end
265
+ end
266
+
267
+ def mazegaki_finish(s)
268
+ mazegaki_reset
269
+ with_target_buffer do |buffer|
270
+ buffer.composite_edit do
271
+ buffer.delete_region(@mazegaki_start_pos, buffer.point)
272
+ buffer.insert(s + @mazegaki_suffix)
273
+ end
274
+ end
275
+ isearch_search if isearch_mode?
276
+ end
277
+
278
+ def hide_help_window
279
+ if @delete_help_window
280
+ Window.delete_window(@help_window)
281
+ elsif @prev_buffer
282
+ @help_window.buffer = @prev_buffer
283
+ end
284
+ @delete_help_window = false
285
+ @help_window = nil
286
+ @prev_buffer = nil
287
+ end
288
+
289
+ def mazegaki_next_page
290
+ if @mazegaki_candidates.size <= mazegaki_limit
291
+ return
292
+ end
293
+ @mazegaki_candidates_page += 1
294
+ if @mazegaki_candidates_page * mazegaki_limit >
295
+ @mazegaki_candidates.size
296
+ @mazegaki_candidates_page = 0
297
+ end
298
+ show_mazegaki_candidates
299
+ Window.redisplay
300
+ end
301
+
302
+ def mazegaki_relimit_left
303
+ with_target_buffer do |buffer|
304
+ yomi = nil
305
+ start_pos = nil
306
+ mazegaki_reset
307
+ buffer.save_excursion do
308
+ pos = buffer.point
309
+ buffer.goto_char(@mazegaki_start_pos)
310
+ s = buffer.substring(buffer.point, pos)
311
+ (MAZEGAKI_MAX_WORD_LEN - s.size).times do
312
+ break if buffer.beginning_of_buffer?
313
+ buffer.backward_char
314
+ s = buffer.substring(buffer.point, pos)
315
+ yomi = mazegaki_lookup_yomi(s, @mazegaki_convert_with_inflection)
316
+ if yomi
317
+ start_pos = buffer.point
318
+ break
319
+ end
320
+ end
321
+ if start_pos.nil?
322
+ message("Can't relimit left")
323
+ start_pos = @mazegaki_start_pos
324
+ yomi = @mazegaki_yomi
325
+ end
326
+ end
327
+ mazegaki_convert(start_pos, yomi)
328
+ Window.redisplay
329
+ end
330
+ end
331
+
332
+ def mazegaki_relimit_right
333
+ with_target_buffer do |buffer|
334
+ start_pos = nil
335
+ yomi = nil
336
+ mazegaki_reset
337
+ buffer.save_excursion do
338
+ pos = buffer.point
339
+ buffer.goto_char(@mazegaki_start_pos)
340
+ if @mazegaki_convert_with_inflection && @mazegaki_yomi &&
341
+ (yomi = mazegaki_lookup_yomi(@mazegaki_yomi, true))
342
+ start_pos = @mazegaki_start_pos
343
+ else
344
+ loop do
345
+ break if buffer.point >= pos
346
+ buffer.forward_char
347
+ s = buffer.substring(buffer.point, pos)
348
+ yomi = mazegaki_lookup_yomi(s, @mazegaki_convert_with_inflection)
349
+ if yomi
350
+ start_pos = buffer.point
351
+ break
352
+ end
353
+ end
354
+ end
355
+ end
356
+ if start_pos.nil?
357
+ if !@mazegaki_convert_with_inflection
358
+ start_pos, yomi = find_mazegaki_start_pos(true)
359
+ if start_pos
360
+ @mazegaki_convert_with_inflection = true
361
+ end
362
+ end
363
+ if start_pos.nil?
364
+ message("Can't relimit right")
365
+ start_pos = @mazegaki_start_pos
366
+ yomi = @mazegaki_yomi
367
+ end
368
+ end
369
+ mazegaki_convert(start_pos, yomi)
370
+ Window.redisplay
371
+ end
372
+ end
373
+
374
+ def show_mazegaki_candidates
375
+ offset = @mazegaki_candidates_page * mazegaki_limit
376
+ candidates = @mazegaki_candidates[offset, mazegaki_limit]
377
+ xs = Array.new(40, "-")
378
+ candidates.each_with_index do |s, i|
379
+ xs[MAZEGAKI_STROKE_PRIORITY_LIST[i]] = s
380
+ end
381
+ max_width = candidates.map { |s|
382
+ Buffer.display_width(s)
383
+ }.max
384
+ page = @mazegaki_candidates_page + 1
385
+ page_count =
386
+ (@mazegaki_candidates.size.to_f / mazegaki_limit).ceil
387
+ message = xs.map.with_index { |s, i|
388
+ space = " " * (max_width - Buffer.display_width(s))
389
+ if i % 10 < 5
390
+ s + space
391
+ else
392
+ space + s
393
+ end
394
+ }.each_slice(10).map.with_index { |ys, i|
395
+ if i == 0
396
+ " " + ys[0, 4].join(" ") + " " + ys[4, 2].join(" ") + " " +
397
+ ys[6, 4].join(" ")
398
+ else
399
+ "[" + ys[0, 4].join(" ") + "] " + ys[4, 2].join(" ") + " [" +
400
+ ys[6, 4].join(" ") + "]"
401
+ end
402
+ }.join("\n") + " (#{page}/#{page_count})"
403
+ show_help(message)
404
+ end
405
+
406
+ def mazegaki_limit
407
+ MAZEGAKI_STROKE_PRIORITY_LIST.size
408
+ end
409
+
410
+ def show_stroke
411
+ c = Buffer.current.char_after
412
+ x, y = KANJI_TABLE.find.with_index { |row, i|
413
+ j = row.index(c)
414
+ if j
415
+ break [j, i]
416
+ else
417
+ false
418
+ end
419
+ }
420
+ if x.nil?
421
+ raise EditorError, "Stroke not found"
422
+ end
423
+ s = " " * 10 + "・・・・  ・・・・" * 3
424
+ s[x] = "1"
425
+ s[y] = "2"
426
+ message = s.gsub(/.{10}/, "\\&\n").gsub(/ /, " ")
427
+ show_help(message)
428
+ Window.redisplay
429
+ nil
430
+ end
431
+
432
+ def show_help(message)
433
+ buffer = Buffer.find_or_new("*T-Code Help*",
434
+ undo_limit: 0, read_only: true)
435
+ buffer.read_only_edit do
436
+ buffer.clear
437
+ buffer.insert(message)
438
+ buffer.beginning_of_buffer
439
+ end
440
+ if Window.list.size == 1
441
+ Window.list.first.split(message.lines.size + 1)
442
+ @delete_help_window = true
443
+ end
444
+ if Window.current.echo_area?
445
+ window = Window.list.last
446
+ else
447
+ windows = Window.list
448
+ i = (windows.index(Window.current) + 1) % windows.size
449
+ window = windows[i]
450
+ end
451
+ @help_window = window
452
+ if window.buffer != buffer
453
+ @prev_buffer = window.buffer
454
+ window.buffer = buffer
455
+ end
456
+ end
457
+ end
458
+ end