textbringer 1.0.1 → 1.1.0

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