textbringer 1.0.3 → 1.1.1

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