textbringer 1.0.3 → 1.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a50dd7957ac593c31d5f1f0102d2240b277f419cd7ea431af422afe53cb3746b
4
- data.tar.gz: 7c4ad8bca8b8ec2ca21bea0a855872f4de39f7e18e22c64e072a4723fbf8a029
3
+ metadata.gz: 8b83487edc5f542afe9444b4393375aedd1ea82b4893997b17c6c4a33a3a4d76
4
+ data.tar.gz: 60b6f47109aad0c55ed9832b97a8a54a65ad7cf7cac95341a20b2f01ba23234d
5
5
  SHA512:
6
- metadata.gz: 96b9ca053d2efeef296ff47db39aa1ef8d345456a5bd3a762025a4928ffb63dc8a3f63cdab5c60e6280a1cf47d56eedfa7734ddd0a23d0bd368fece0e58a96a3
7
- data.tar.gz: 1cbe3d4cc148f6d837913c5080199bff92417a584fd6b9a452d2f414e1b9e3a09259ce16cc7bfd5a05c2987ac2bc4a55f1d417d42314c59155100d5d0c98764c
6
+ metadata.gz: 17399f8a6297f0b77cd03cdac6c24c64b6e6da82dc596b6e5bdfa7ba807cdcb8c9e32d12e376b219941ee14a2ef4a60e63bab9e2a07723a107b5e01b259eb034
7
+ data.tar.gz: 27b5e0e6011257b5beafc563b439944a72c7018cd058060bde55618369a55ec2c6596840f0de661a075f361485f5f27e76d908c6b04360f1342986a48b78797d
@@ -3,15 +3,14 @@ name: ubuntu
3
3
  on: [push]
4
4
 
5
5
  jobs:
6
- build:
7
- runs-on: ubuntu-latest
6
+ test:
8
7
  strategy:
9
8
  matrix:
10
- ruby: [ '2.6.x', '2.5.x', '2.4.x' ]
9
+ ruby: [ head, 3.1, 3.0, 2.7, 2.6 ]
10
+ runs-on: ubuntu-latest
11
11
  steps:
12
- - uses: actions/checkout@master
13
- - name: Set up Ruby
14
- uses: actions/setup-ruby@v1
12
+ - uses: actions/checkout@v2
13
+ - uses: ruby/setup-ruby@v1
15
14
  with:
16
15
  ruby-version: ${{ matrix.ruby }}
17
16
  - name: Install dependencies
@@ -21,6 +20,3 @@ jobs:
21
20
  bundle install
22
21
  - name: Run test
23
22
  run: xvfb-run bundle exec rake test
24
- env:
25
- UPLOAD_TO_CODECOV: 1
26
- CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}}
@@ -7,16 +7,13 @@ jobs:
7
7
  runs-on: windows-latest
8
8
  strategy:
9
9
  matrix:
10
- ruby: [ '2.6.x', '2.5.x', '2.4.x' ]
10
+ ruby: [ 'mingw', 'mswin', '3.0', '2.7', '2.6' ]
11
11
  steps:
12
- - uses: actions/checkout@master
12
+ - uses: actions/checkout@v2
13
13
  - name: Set up Ruby
14
- uses: actions/setup-ruby@v1
14
+ uses: ruby/setup-ruby@v1
15
15
  with:
16
16
  ruby-version: ${{ matrix.ruby }}
17
- - name: Set up Bundler
18
- run: gem install bundler --no-document
19
- - name: Install dependencies
20
- run: bundle install
17
+ bundler-cache: true
21
18
  - name: Run test
22
19
  run: bundle exec rake test
data/CHANGES.md CHANGED
@@ -1,3 +1,41 @@
1
+ ## 1.1.1
2
+
3
+ * Bug fixes.
4
+
5
+ ## 1.1.0
6
+
7
+ * Show unsaved buffers on exit
8
+ * Bug fixes.
9
+
10
+ ## 1.0.9
11
+
12
+ * Remove mazegaki.dic and bushu.rev
13
+
14
+ ## 1.0.8
15
+
16
+ * Updated mazegaki.dic
17
+ * Add licenses of dictionary data to LEGAL.txt
18
+
19
+ ## 1.0.7
20
+
21
+ * Support endless method definitions in ruby-mode.
22
+ * Updated mazegaki.dic.
23
+
24
+ ## 1.0.6
25
+
26
+ * Add the Hiragana input method.
27
+ * Add make_directory.
28
+ * Bug fixes.
29
+
30
+ ## 1.0.5
31
+
32
+ * Support the Japanese input method T-Code.
33
+
34
+ ## 1.0.4
35
+
36
+ * Support Ruby 3.0.
37
+ * Do not record backtrace of Quit (C-g).
38
+
1
39
  ## 1.0.3
2
40
 
3
41
  * Fix indentation bugs.
data/README.md CHANGED
@@ -4,8 +4,6 @@
4
4
  [![ubuntu](https://github.com/shugo/textbringer/workflows/ubuntu/badge.svg)](https://github.com/shugo/textbringer/actions?query=workflow%3Aubuntu)
5
5
  [![windows](https://github.com/shugo/textbringer/workflows/windows/badge.svg)](https://github.com/shugo/textbringer/actions?query=workflow%3Awindows)
6
6
  [![macos](https://github.com/shugo/textbringer/workflows/macos/badge.svg)](https://github.com/shugo/textbringer/actions?query=workflow%3Amacos)
7
- [![ruby-head](https://github.com/shugo/textbringer/workflows/ruby-head/badge.svg)](https://github.com/shugo/textbringer/actions?query=workflow%3Aruby-head)
8
- [![codecov](https://codecov.io/gh/shugo/textbringer/branch/master/graph/badge.svg)](https://codecov.io/gh/shugo/textbringer)
9
7
 
10
8
  Textbringer is a member of a demon race that takes on the form of an Emacs-like
11
9
  text editor.
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Usage: merge_mazegaki_dic /path/to/mazegaki.dic /path/to/skkdic/SKK-JISYO.* > ~/.textbringer/tcode/mazegaki.dic
4
+
5
+ MAZEGAKI_DIC = Hash.new([])
6
+
7
+ ARGF.each_line do |line|
8
+ next if /^\p{ascii}/.match?(line)
9
+ x, y = line.split
10
+ key = x.sub(/\A(\p{hiragana}+)[a-z>]\z/, "\\1—")
11
+ values = y.split("/").map { |i|
12
+ i.sub(/;.*/, "")
13
+ }.reject { |i|
14
+ i.empty? || i == key
15
+ }
16
+ MAZEGAKI_DIC[key] |= values
17
+ end
18
+
19
+ MAZEGAKI_DIC.sort_by { |key,|
20
+ key
21
+ }.each do |key, values|
22
+ puts "#{key} /#{values.join('/')}/"
23
+ end
24
+
25
+
data/exe/textbringer CHANGED
@@ -1,20 +1,20 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ $VERBOSE = nil
4
+
3
5
  require "textbringer"
4
6
 
5
7
  include Textbringer
6
8
  include Commands
7
9
 
8
- def load_user_config
9
- config_file = File.expand_path("~/.textbringer.rb")
10
+ def load_user_config(path)
11
+ config_file = File.expand_path(path)
10
12
  begin
11
13
  load(config_file)
12
14
  rescue LoadError
13
15
  end
14
16
  end
15
17
 
16
- $VERBOSE = nil
17
-
18
18
  unless STDIN.tty?
19
19
  STDERR.puts("textbringer: standard input is not a tty")
20
20
  exit 1
@@ -24,8 +24,9 @@ Controller.current = Controller.new
24
24
  begin
25
25
  Window.start do
26
26
  begin
27
+ load_user_config("~/.textbringer/init.rb")
27
28
  Plugin.load_plugins
28
- load_user_config
29
+ load_user_config("~/.textbringer.rb")
29
30
  ruby_mode
30
31
  if ARGV.size > 0
31
32
  ARGV.each do |arg|
@@ -11,6 +11,7 @@ module Textbringer
11
11
  attr_accessor :mode, :keymap
12
12
  attr_reader :name, :file_name, :file_encoding, :file_format, :point, :marks
13
13
  attr_reader :current_line, :current_column, :visible_mark
14
+ attr_reader :input_method
14
15
 
15
16
  GAP_SIZE = 256
16
17
  UNDO_LIMIT = 1000
@@ -240,6 +241,7 @@ module Textbringer
240
241
  @visible_mark = nil
241
242
  @read_only = read_only
242
243
  @callbacks = {}
244
+ @input_method = nil
243
245
  end
244
246
 
245
247
  def inspect
@@ -410,6 +412,7 @@ module Textbringer
410
412
  f.flock(File::LOCK_EX)
411
413
  write_to_file(f)
412
414
  f.flush
415
+ f.fsync
413
416
  end
414
417
  @file_mtime = File.mtime(file_name)
415
418
  rescue Errno::EISDIR
@@ -1384,6 +1387,35 @@ module Textbringer
1384
1387
  end
1385
1388
  end
1386
1389
 
1390
+ def toggle_input_method(name)
1391
+ if name.nil?
1392
+ @input_method ||= InputMethod.find(CONFIG[:default_input_method])
1393
+ else
1394
+ @input_method = InputMethod.find(name)
1395
+ end
1396
+ @input_method.toggle
1397
+ end
1398
+
1399
+ def disable_input_method
1400
+ @input_method&.disable
1401
+ end
1402
+
1403
+ def filter_event(event)
1404
+ if @input_method
1405
+ @input_method.filter_event(event)
1406
+ else
1407
+ event
1408
+ end
1409
+ end
1410
+
1411
+ def input_method_status
1412
+ if @input_method&.enabled?
1413
+ @input_method.status
1414
+ else
1415
+ "--"
1416
+ end
1417
+ end
1418
+
1387
1419
  private
1388
1420
 
1389
1421
  def set_contents(s, enc)
@@ -1555,15 +1587,15 @@ module Textbringer
1555
1587
 
1556
1588
  def push_undo(action)
1557
1589
  return if @undoing || @undo_limit == 0
1590
+ if !modified?
1591
+ action.version = @version
1592
+ end
1558
1593
  if @composite_edit_level > 0
1559
1594
  @composite_edit_actions.push(action)
1560
1595
  else
1561
1596
  if @undo_stack.size >= @undo_limit
1562
1597
  @undo_stack[0, @undo_stack.size + 1 - @undo_limit] = []
1563
1598
  end
1564
- if !modified?
1565
- action.version = @version
1566
- end
1567
1599
  @undo_stack.push(action)
1568
1600
  @redo_stack.clear
1569
1601
  end
@@ -1649,6 +1681,7 @@ module Textbringer
1649
1681
  def initialize(buffer, location, string)
1650
1682
  super(buffer, location)
1651
1683
  @string = string
1684
+ @copied = false
1652
1685
  end
1653
1686
 
1654
1687
  def undo
@@ -1662,6 +1695,10 @@ module Textbringer
1662
1695
  end
1663
1696
 
1664
1697
  def merge(s)
1698
+ unless @copied
1699
+ @string = @string.dup
1700
+ @copied = true
1701
+ end
1665
1702
  @string.concat(s)
1666
1703
  end
1667
1704
  end
@@ -1,9 +1,10 @@
1
1
  require "editorconfig"
2
+ require "fileutils"
2
3
 
3
4
  module Textbringer
4
5
  module Commands
5
6
  define_command(:find_file, doc: "Open or create a file.") do
6
- |file_name = read_file_name("Find file: ")|
7
+ |file_name = read_file_name("Find file: ", default: (Buffer.current.file_name ? File.dirname(Buffer.current.file_name) : Dir.pwd) + "/")|
7
8
  config = EditorConfig.load_file(file_name)
8
9
  buffer = Buffer.find_file(file_name)
9
10
  if buffer.new_file?
@@ -86,12 +87,13 @@ module Textbringer
86
87
 
87
88
  define_command(:write_file,
88
89
  doc: "Save the current buffer as the specified file.") do
89
- |file_name = read_file_name("Write file: ")|
90
+ |file_name = read_file_name("Write file: ",
91
+ default: Buffer.current.file_name)|
90
92
  if File.directory?(file_name)
91
93
  file_name = File.expand_path(Buffer.current.name, file_name)
92
94
  end
93
95
  if File.exist?(file_name)
94
- unless y_or_n?("File `#{file_name}' exists; overwrite?")
96
+ unless y_or_n?("File exists; overwrite?")
95
97
  message("Cancelled")
96
98
  next
97
99
  end
@@ -130,5 +132,12 @@ module Textbringer
130
132
  default: Buffer.current.file_name)|
131
133
  find_file(file_name)
132
134
  end
135
+
136
+ define_command(:make_directory, doc: "Create a new directory.") do
137
+ |dir_name = read_file_name("Make directory: ",
138
+ default: Buffer.current.file_name &&
139
+ File.dirname(Buffer.current.file_name))|
140
+ FileUtils.mkdir_p(dir_name)
141
+ end
133
142
  end
134
143
  end
@@ -21,27 +21,36 @@ module Textbringer
21
21
  end
22
22
  private :show_help
23
23
 
24
+ def keymap_bindings(keymap)
25
+ s = format("%-16s %s\n", "Key", "Binding")
26
+ s << format("%-16s %s\n", "---", "-------")
27
+ s << "\n"
28
+ keymap.each do |key_sequence, command|
29
+ if command != :self_insert
30
+ s << format("%-16s [%s]\n",
31
+ Keymap.key_sequence_string(key_sequence),
32
+ command)
33
+ end
34
+ end
35
+ s
36
+ end
37
+
24
38
  define_command(:describe_bindings,
25
39
  doc: "Display the key bindings.") do
26
40
  show_help do |help|
27
- s = format("%-16s %s\n", "Key", "Binding")
28
- s << format("%-16s %s\n", "---", "-------")
29
- s << "\n"
30
- bindings = {}
31
- [
32
- GLOBAL_MAP,
33
- Buffer.current.keymap,
34
- Controller.current.overriding_map
35
- ].each do |map|
36
- map&.each do |key_sequence, command|
37
- bindings[key_sequence] = command
38
- end
41
+ s = ""
42
+ if Controller.current.overriding_map
43
+ s << "Overriding Bindings:\n"
44
+ s << keymap_bindings(Controller.current.overriding_map)
45
+ s << "\n"
39
46
  end
40
- bindings.each do |key_sequence, command|
41
- s << format("%-16s [%s]\n",
42
- Keymap.key_sequence_string(key_sequence),
43
- command)
47
+ if Buffer.current.keymap
48
+ s << "Current Buffer Bindings:\n"
49
+ s << keymap_bindings(Buffer.current.keymap)
50
+ s << "\n"
44
51
  end
52
+ s << "Global Bindings:\n"
53
+ s << keymap_bindings(GLOBAL_MAP)
45
54
  help.insert(s)
46
55
  end
47
56
  push_help_command([:describe_bindings])
@@ -0,0 +1,18 @@
1
+ module Textbringer
2
+ module Commands
3
+ define_command(:toggle_input_method,
4
+ doc: "Toggel input method.") do |name = nil|
5
+ if name.nil? && current_prefix_arg
6
+ name = read_input_method_name("Input method: ")
7
+ end
8
+ Buffer.current.toggle_input_method(name)
9
+ end
10
+
11
+ def read_input_method_name(prompt, default: CONFIG[:default_input_method])
12
+ f = ->(s) {
13
+ complete_for_minibuffer(s.tr("-", "_"), InputMethod.list)
14
+ }
15
+ read_from_minibuffer(prompt, completion_proc: f, default: default)
16
+ end
17
+ end
18
+ end
@@ -14,12 +14,14 @@ module Textbringer
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)
22
23
  ISEARCH_MODE_MAP.define_key(?\C-q, :isearch_quoted_insert)
24
+ ISEARCH_MODE_MAP.define_key(?\C-\\, :isearch_toggle_input_method)
23
25
 
24
26
  ISEARCH_STATUS = {
25
27
  forward: true,
@@ -56,6 +58,10 @@ module Textbringer
56
58
  end
57
59
  end
58
60
 
61
+ def isearch_mode?
62
+ Controller.current.overriding_map == ISEARCH_MODE_MAP
63
+ end
64
+
59
65
  def isearch_prompt
60
66
  if ISEARCH_STATUS[:forward]
61
67
  "I-search: "
@@ -133,7 +139,7 @@ module Textbringer
133
139
  re = Regexp.new(Regexp.quote(ISEARCH_STATUS[:string]), options)
134
140
  last_pos = ISEARCH_STATUS[:last_pos]
135
141
  offset = forward ? last_pos : last_pos - ISEARCH_STATUS[:string].bytesize
136
- if Buffer.current.byteindex(forward, re, offset)
142
+ if offset >= 0 && Buffer.current.byteindex(forward, re, offset)
137
143
  if Buffer.current != Buffer.minibuffer
138
144
  message(isearch_prompt + ISEARCH_STATUS[:string], log: false)
139
145
  end
@@ -164,5 +170,11 @@ module Textbringer
164
170
  end
165
171
  isearch_search
166
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
167
179
  end
168
180
  end
@@ -6,7 +6,12 @@ module Textbringer
6
6
  end
7
7
 
8
8
  define_command(:exit_textbringer) do |status = 0|
9
- if Buffer.any? { |buffer| /\A\*/ !~ buffer.name && buffer.modified? }
9
+ unsaved_buffers = Buffer.filter { |buffer|
10
+ /\A\*/ !~ buffer.name && buffer.modified?
11
+ }
12
+ if !unsaved_buffers.empty?
13
+ list_buffers(unsaved_buffers)
14
+ Window.redisplay
10
15
  return unless yes_or_no?("Unsaved buffers exist; exit anyway?")
11
16
  end
12
17
  exit(status)
@@ -276,7 +281,7 @@ module Textbringer
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
@@ -6,7 +6,7 @@ module Textbringer
6
6
  doc: "Start Textbringer server.") do
7
7
  uri = CONFIG[:server_uri] ||
8
8
  "drbunix:" + File.expand_path("server.sock", "~/.textbringer")
9
- options = CONFIG[:server_options] || { UNIXFileMode: 0600 }
9
+ options = { UNIXFileMode: 0600 }.merge(CONFIG[:server_options] || {})
10
10
  DRb.start_service(uri, Server.new, options)
11
11
  end
12
12
 
@@ -69,9 +69,13 @@ module Textbringer
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 Textbringer
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 Textbringer
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 @@ module Textbringer
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