ruvim 0.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 (66) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/test.yml +15 -0
  3. data/README.md +135 -0
  4. data/Rakefile +36 -0
  5. data/docs/binding.md +125 -0
  6. data/docs/command.md +306 -0
  7. data/docs/config.md +155 -0
  8. data/docs/done.md +112 -0
  9. data/docs/plugin.md +559 -0
  10. data/docs/spec.md +655 -0
  11. data/docs/todo.md +63 -0
  12. data/docs/tutorial.md +490 -0
  13. data/docs/vim_diff.md +179 -0
  14. data/exe/ruvim +6 -0
  15. data/lib/ruvim/app.rb +1600 -0
  16. data/lib/ruvim/buffer.rb +421 -0
  17. data/lib/ruvim/cli.rb +264 -0
  18. data/lib/ruvim/clipboard.rb +73 -0
  19. data/lib/ruvim/command_invocation.rb +14 -0
  20. data/lib/ruvim/command_line.rb +63 -0
  21. data/lib/ruvim/command_registry.rb +38 -0
  22. data/lib/ruvim/config_dsl.rb +134 -0
  23. data/lib/ruvim/config_loader.rb +68 -0
  24. data/lib/ruvim/context.rb +26 -0
  25. data/lib/ruvim/dispatcher.rb +120 -0
  26. data/lib/ruvim/display_width.rb +110 -0
  27. data/lib/ruvim/editor.rb +1025 -0
  28. data/lib/ruvim/ex_command_registry.rb +80 -0
  29. data/lib/ruvim/global_commands.rb +1889 -0
  30. data/lib/ruvim/highlighter.rb +52 -0
  31. data/lib/ruvim/input.rb +66 -0
  32. data/lib/ruvim/keymap_manager.rb +96 -0
  33. data/lib/ruvim/screen.rb +452 -0
  34. data/lib/ruvim/terminal.rb +30 -0
  35. data/lib/ruvim/text_metrics.rb +96 -0
  36. data/lib/ruvim/version.rb +5 -0
  37. data/lib/ruvim/window.rb +71 -0
  38. data/lib/ruvim.rb +30 -0
  39. data/sig/ruvim.rbs +4 -0
  40. data/test/app_completion_test.rb +39 -0
  41. data/test/app_dot_repeat_test.rb +54 -0
  42. data/test/app_motion_test.rb +73 -0
  43. data/test/app_register_test.rb +47 -0
  44. data/test/app_scenario_test.rb +77 -0
  45. data/test/app_startup_test.rb +199 -0
  46. data/test/app_text_object_test.rb +54 -0
  47. data/test/app_unicode_behavior_test.rb +66 -0
  48. data/test/buffer_test.rb +72 -0
  49. data/test/cli_test.rb +165 -0
  50. data/test/config_dsl_test.rb +78 -0
  51. data/test/dispatcher_test.rb +124 -0
  52. data/test/editor_mark_test.rb +69 -0
  53. data/test/editor_register_test.rb +64 -0
  54. data/test/fixtures/render_basic_snapshot.txt +8 -0
  55. data/test/fixtures/render_basic_snapshot_nonumber.txt +8 -0
  56. data/test/fixtures/render_unicode_scrolled_snapshot.txt +7 -0
  57. data/test/highlighter_test.rb +16 -0
  58. data/test/input_screen_integration_test.rb +69 -0
  59. data/test/keymap_manager_test.rb +48 -0
  60. data/test/render_snapshot_test.rb +70 -0
  61. data/test/screen_test.rb +123 -0
  62. data/test/search_option_test.rb +39 -0
  63. data/test/test_helper.rb +15 -0
  64. data/test/text_metrics_test.rb +42 -0
  65. data/test/window_test.rb +21 -0
  66. metadata +106 -0
@@ -0,0 +1,73 @@
1
+ require "open3"
2
+
3
+ module RuVim
4
+ module Clipboard
5
+ module_function
6
+
7
+ def available?
8
+ !backend.nil?
9
+ end
10
+
11
+ def read
12
+ cmd = backend&.dig(:read)
13
+ return nil unless cmd
14
+
15
+ out, status = Open3.capture2(*cmd)
16
+ return nil unless status.success?
17
+
18
+ out
19
+ rescue StandardError
20
+ nil
21
+ end
22
+
23
+ def write(text)
24
+ cmd = backend&.dig(:write)
25
+ return false unless cmd
26
+
27
+ Open3.popen2(*cmd) do |stdin, _stdout, wait|
28
+ stdin.write(text.to_s)
29
+ stdin.close
30
+ return wait.value.success?
31
+ end
32
+ rescue StandardError
33
+ false
34
+ end
35
+
36
+ def reset_backend!
37
+ @backend = nil
38
+ end
39
+
40
+ def backend
41
+ @backend ||= detect_backend
42
+ end
43
+
44
+ def detect_backend
45
+ return pbcopy_backend if command_available?("pbcopy") && command_available?("pbpaste")
46
+ return wayland_backend if command_available?("wl-copy") && command_available?("wl-paste")
47
+ return xclip_backend if command_available?("xclip")
48
+ return xsel_backend if command_available?("xsel")
49
+
50
+ nil
51
+ end
52
+
53
+ def pbcopy_backend
54
+ { write: %w[pbcopy], read: %w[pbpaste] }
55
+ end
56
+
57
+ def wayland_backend
58
+ { write: %w[wl-copy], read: %w[wl-paste -n] }
59
+ end
60
+
61
+ def xclip_backend
62
+ { write: %w[xclip -selection clipboard -in], read: %w[xclip -selection clipboard -out] }
63
+ end
64
+
65
+ def xsel_backend
66
+ { write: %w[xsel --clipboard --input], read: %w[xsel --clipboard --output] }
67
+ end
68
+
69
+ def command_available?(name)
70
+ system("which", name, out: File::NULL, err: File::NULL)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,14 @@
1
+ module RuVim
2
+ class CommandInvocation
3
+ attr_accessor :id, :argv, :kwargs, :count, :bang, :raw_keys
4
+
5
+ def initialize(id:, argv: nil, kwargs: nil, count: nil, bang: nil, raw_keys: nil)
6
+ @id = id
7
+ @argv = argv || []
8
+ @kwargs = kwargs || {}
9
+ @count = count || 1
10
+ @bang = !!bang
11
+ @raw_keys = raw_keys
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,63 @@
1
+ module RuVim
2
+ class CommandLine
3
+ attr_reader :prefix, :text, :cursor
4
+
5
+ def initialize
6
+ reset(prefix: ":")
7
+ end
8
+
9
+ def reset(prefix: ":")
10
+ @prefix = prefix
11
+ @text = +""
12
+ @cursor = 0
13
+ end
14
+
15
+ def clear
16
+ @text.clear
17
+ @cursor = 0
18
+ end
19
+
20
+ def replace_text(str)
21
+ @text = str.to_s.dup
22
+ @cursor = @text.length
23
+ end
24
+
25
+ def replace_span(start_idx, end_idx, replacement, cursor_at: :end)
26
+ s = [[start_idx.to_i, 0].max, @text.length].min
27
+ e = [[end_idx.to_i, s].max, @text.length].min
28
+ rep = replacement.to_s
29
+ @text = @text[0...s].to_s + rep + @text[e..].to_s
30
+ @cursor =
31
+ case cursor_at
32
+ when :start then s
33
+ when Integer then [[cursor_at, 0].max, @text.length].min
34
+ else
35
+ s + rep.length
36
+ end
37
+ end
38
+
39
+ def insert(str)
40
+ @text.insert(@cursor, str)
41
+ @cursor += str.length
42
+ end
43
+
44
+ def backspace
45
+ return if @cursor.zero?
46
+
47
+ @text.slice!(@cursor - 1)
48
+ @cursor -= 1
49
+ end
50
+
51
+ def move_left
52
+ @cursor -= 1 if @cursor.positive?
53
+ end
54
+
55
+ def move_right
56
+ @cursor += 1 if @cursor < @text.length
57
+ end
58
+
59
+ def content
60
+ "#{@prefix}#{@text}"
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,38 @@
1
+ module RuVim
2
+ class CommandRegistry
3
+ CommandSpec = Struct.new(
4
+ :id,
5
+ :call,
6
+ :desc,
7
+ :source,
8
+ keyword_init: true
9
+ )
10
+
11
+ include Singleton
12
+
13
+ def initialize
14
+ @specs = {}
15
+ end
16
+
17
+ def register(id, call:, desc: "", source: :builtin)
18
+ key = id.to_s
19
+ @specs[key] = CommandSpec.new(id: key, call:, desc:, source:)
20
+ end
21
+
22
+ def fetch(id)
23
+ @specs.fetch(id.to_s)
24
+ end
25
+
26
+ def registered?(id)
27
+ @specs.key?(id.to_s)
28
+ end
29
+
30
+ def all
31
+ @specs.values.sort_by(&:id)
32
+ end
33
+
34
+ def clear!
35
+ @specs.clear
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,134 @@
1
+ module RuVim
2
+ class ConfigDSL < BasicObject
3
+ def initialize(command_registry:, ex_registry:, keymaps:, command_host:, editor: nil, filetype: nil)
4
+ @command_registry = command_registry
5
+ @ex_registry = ex_registry
6
+ @keymaps = keymaps
7
+ @command_host = command_host
8
+ @editor = editor
9
+ @filetype = filetype&.to_s
10
+ @inline_map_command_seq = 0
11
+ end
12
+
13
+ def nmap(seq, command_id = nil, desc: "user keymap", **opts, &block)
14
+ command_id = inline_map_command_id(:normal, seq, desc:, &block) if block
15
+ raise ::ArgumentError, "command_id or block required" if command_id.nil?
16
+
17
+ if @filetype && !@filetype.empty?
18
+ @keymaps.bind_filetype(@filetype, seq, command_id.to_s, mode: :normal, **opts)
19
+ else
20
+ @keymaps.bind(:normal, seq, command_id.to_s, **opts)
21
+ end
22
+ end
23
+
24
+ def imap(seq, command_id = nil, desc: "user keymap", **opts, &block)
25
+ command_id = inline_map_command_id(:insert, seq, desc:, &block) if block
26
+ raise ::ArgumentError, "command_id or block required" if command_id.nil?
27
+
28
+ if @filetype && !@filetype.empty?
29
+ @keymaps.bind_filetype(@filetype, seq, command_id.to_s, mode: :insert, **opts)
30
+ else
31
+ @keymaps.bind(:insert, seq, command_id.to_s, **opts)
32
+ end
33
+ end
34
+
35
+ def map_global(seq, command_id = nil, mode: :normal, desc: "user keymap", **opts, &block)
36
+ command_id = inline_map_command_id(mode || :global, seq, desc:, &block) if block
37
+ raise ::ArgumentError, "command_id or block required" if command_id.nil?
38
+
39
+ if mode
40
+ @keymaps.bind(mode.to_sym, seq, command_id.to_s, **opts)
41
+ else
42
+ @keymaps.bind_global(seq, command_id.to_s, **opts)
43
+ end
44
+ end
45
+
46
+ def command(id, desc: "user command", &block)
47
+ raise ::ArgumentError, "block required" unless block
48
+
49
+ @command_registry.register(id.to_s, call: block, desc:, source: :user)
50
+ end
51
+
52
+ def ex_command(name, desc: "user ex", aliases: [], nargs: :any, bang: false, &block)
53
+ raise ::ArgumentError, "block required" unless block
54
+
55
+ @ex_registry.unregister(name.to_s) if @ex_registry.registered?(name.to_s)
56
+ @ex_registry.register(name.to_s, call: block, desc:, aliases:, nargs:, bang:, source: :user)
57
+ end
58
+
59
+ # Convenience: define an Ex command that forwards to an existing internal command ID.
60
+ def ex_command_call(name, command_id, desc: "user ex", aliases: [], nargs: :any, bang: false)
61
+ ex_command(name, desc:, aliases:, nargs:, bang:) do |ctx, argv:, kwargs:, bang:, count:|
62
+ spec = @command_registry.fetch(command_id.to_s)
63
+ @command_host.call(spec.call, ctx, argv:, kwargs:, bang:, count:)
64
+ end
65
+ end
66
+
67
+ def set(option_expr)
68
+ apply_option_expr(option_expr, scope: :auto)
69
+ end
70
+
71
+ def setlocal(option_expr)
72
+ apply_option_expr(option_expr, scope: :local)
73
+ end
74
+
75
+ def setglobal(option_expr)
76
+ apply_option_expr(option_expr, scope: :global)
77
+ end
78
+
79
+ private
80
+
81
+ def inline_map_command_id(mode, seq, desc:, &block)
82
+ raise ::ArgumentError, "block required" unless block
83
+
84
+ @inline_map_command_seq += 1
85
+ id = "user.keymap.#{normalize_mode_name(mode)}.#{sanitize_seq_label(seq)}.#{@inline_map_command_seq}"
86
+ command(id, desc:, &block)
87
+ id
88
+ end
89
+
90
+ def normalize_mode_name(mode)
91
+ (mode || :global).to_s
92
+ end
93
+
94
+ def sanitize_seq_label(seq)
95
+ raw =
96
+ case seq
97
+ when ::Array
98
+ seq.map(&:to_s).join("_")
99
+ else
100
+ seq.to_s
101
+ end
102
+ s = raw.gsub(/[^A-Za-z0-9]+/, "_").gsub(/\A_+|_+\z/, "")
103
+ s.empty? ? "anonymous" : s
104
+ end
105
+
106
+ def apply_option_expr(expr, scope:)
107
+ raise ::ArgumentError, "editor context required for option DSL" unless @editor
108
+
109
+ token = expr.to_s.strip
110
+ raise ::ArgumentError, "empty option expression" if token.empty?
111
+
112
+ if token.start_with?("no")
113
+ name = token[2..]
114
+ @editor.set_option(name, false, scope: resolve_scope(name, scope))
115
+ return
116
+ end
117
+
118
+ if token.include?("=")
119
+ name, raw = token.split("=", 2)
120
+ @editor.set_option(name, raw, scope: resolve_scope(name, scope))
121
+ return
122
+ end
123
+
124
+ @editor.set_option(token, true, scope: resolve_scope(token, scope))
125
+ end
126
+
127
+ def resolve_scope(name, scope)
128
+ return :auto if scope == :auto
129
+ return :global if scope == :global
130
+
131
+ @editor.option_default_scope(name) == :buffer ? :buffer : :window
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,68 @@
1
+ module RuVim
2
+ class ConfigLoader
3
+ def initialize(command_registry:, ex_registry:, keymaps:, command_host:)
4
+ @command_registry = command_registry
5
+ @ex_registry = ex_registry
6
+ @keymaps = keymaps
7
+ @command_host = command_host
8
+ end
9
+
10
+ def load_default!
11
+ path = xdg_config_path
12
+ return nil unless File.file?(path)
13
+
14
+ load_file(path)
15
+ end
16
+
17
+ def load_file(path, editor: nil, filetype: nil)
18
+ dsl = ConfigDSL.new(
19
+ command_registry: @command_registry,
20
+ ex_registry: @ex_registry,
21
+ keymaps: @keymaps,
22
+ command_host: @command_host,
23
+ editor: editor,
24
+ filetype: filetype
25
+ )
26
+ code = File.read(path)
27
+ dsl.instance_eval(code, path, 1)
28
+ path
29
+ end
30
+
31
+ def load_ftplugin!(editor, buffer)
32
+ filetype = buffer.options["filetype"].to_s
33
+ return nil if filetype.empty?
34
+ return nil if buffer.options["__ftplugin_loaded__"] == filetype
35
+
36
+ path = ftplugin_path_for(filetype)
37
+ return nil unless path && File.file?(path)
38
+
39
+ load_file(path, editor:, filetype:)
40
+ buffer.options["__ftplugin_loaded__"] = filetype
41
+ path
42
+ end
43
+
44
+ private
45
+
46
+ def xdg_config_path
47
+ base = ::ENV["XDG_CONFIG_HOME"]
48
+ if base && !base.empty?
49
+ File.join(base, "ruvim", "init.rb")
50
+ else
51
+ File.expand_path("~/.config/ruvim/init.rb")
52
+ end
53
+ end
54
+
55
+ def ftplugin_path_for(filetype)
56
+ xdg_ftplugin_path(filetype)
57
+ end
58
+
59
+ def xdg_ftplugin_path(filetype)
60
+ base = ::ENV["XDG_CONFIG_HOME"]
61
+ if base && !base.empty?
62
+ File.join(base, "ruvim", "ftplugin", "#{filetype}.rb")
63
+ else
64
+ File.expand_path("~/.config/ruvim/ftplugin/#{filetype}.rb")
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,26 @@
1
+ module RuVim
2
+ class Context
3
+ attr_reader :editor, :invocation
4
+
5
+ def initialize(editor:, invocation: nil)
6
+ @editor = editor
7
+ @invocation = invocation
8
+ end
9
+
10
+ def window
11
+ editor.current_window
12
+ end
13
+
14
+ def buffer
15
+ editor.current_buffer
16
+ end
17
+
18
+ def count
19
+ invocation&.count || 1
20
+ end
21
+
22
+ def bang?
23
+ invocation&.bang || false
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,120 @@
1
+ require "shellwords"
2
+
3
+ module RuVim
4
+ class Dispatcher
5
+ ExCall = Struct.new(:name, :argv, :bang, keyword_init: true)
6
+
7
+ def initialize(command_registry: CommandRegistry.instance, ex_registry: ExCommandRegistry.instance, command_host: GlobalCommands.instance)
8
+ @command_registry = command_registry
9
+ @ex_registry = ex_registry
10
+ @command_host = command_host
11
+ end
12
+
13
+ def dispatch(editor, invocation)
14
+ spec = @command_registry.fetch(invocation.id)
15
+ ctx = Context.new(editor:, invocation:)
16
+ @command_host.call(spec.call, ctx, argv: invocation.argv, kwargs: invocation.kwargs, bang: invocation.bang, count: invocation.count)
17
+ rescue StandardError => e
18
+ editor.echo_error("Error: #{e.message}")
19
+ end
20
+
21
+ def dispatch_ex(editor, line)
22
+ if (sub = parse_global_substitute(line))
23
+ invocation = CommandInvocation.new(id: "__substitute__", kwargs: sub)
24
+ ctx = Context.new(editor:, invocation:)
25
+ @command_host.ex_substitute(ctx, **sub)
26
+ editor.enter_normal_mode
27
+ return
28
+ end
29
+
30
+ parsed = parse_ex(line)
31
+ return if parsed.nil?
32
+
33
+ spec = @ex_registry.fetch(parsed.name)
34
+ validate_ex_args!(spec, parsed.argv, parsed.bang)
35
+ invocation = CommandInvocation.new(id: spec.name, argv: parsed.argv, bang: parsed.bang)
36
+ ctx = Context.new(editor:, invocation:)
37
+ @command_host.call(spec.call, ctx, argv: parsed.argv, bang: parsed.bang, count: 1, kwargs: {})
38
+ rescue StandardError => e
39
+ editor.echo_error("Error: #{e.message}")
40
+ ensure
41
+ editor.enter_normal_mode
42
+ end
43
+
44
+ def parse_ex(line)
45
+ raw = line.to_s.strip
46
+ return nil if raw.empty?
47
+
48
+ tokens = Shellwords.shellsplit(raw)
49
+ return nil if tokens.empty?
50
+
51
+ head = tokens.shift
52
+ bang = head.end_with?("!")
53
+ name = bang ? head[0...-1] : head
54
+ ExCall.new(name:, argv: tokens, bang:)
55
+ rescue ArgumentError => e
56
+ raise RuVim::CommandError, "Parse error: #{e.message}"
57
+ end
58
+
59
+ def parse_global_substitute(line)
60
+ raw = line.to_s.strip
61
+ return nil unless raw.start_with?("%s")
62
+ return nil if raw.length < 4
63
+
64
+ delim = raw[2]
65
+ return nil if delim.nil? || delim =~ /\s/
66
+ i = 3
67
+ pat, i = parse_delimited_segment(raw, i, delim)
68
+ return nil unless pat
69
+ rep, i = parse_delimited_segment(raw, i, delim)
70
+ return nil unless rep
71
+ flags = raw[i..].to_s
72
+ {
73
+ pattern: pat,
74
+ replacement: rep,
75
+ global: flags.include?("g")
76
+ }
77
+ rescue StandardError
78
+ nil
79
+ end
80
+
81
+ private
82
+
83
+ def parse_delimited_segment(str, idx, delim)
84
+ out = +""
85
+ i = idx
86
+ while i < str.length
87
+ ch = str[i]
88
+ if ch == "\\"
89
+ nxt = str[i + 1]
90
+ return nil unless nxt
91
+ out << "\\"
92
+ out << nxt
93
+ i += 2
94
+ next
95
+ end
96
+ if ch == delim
97
+ return [out, i + 1]
98
+ end
99
+ out << ch
100
+ i += 1
101
+ end
102
+ nil
103
+ end
104
+
105
+ def validate_ex_args!(spec, argv, bang)
106
+ case spec.nargs
107
+ when 0
108
+ raise RuVim::CommandError, "#{spec.name} takes no arguments" unless argv.empty?
109
+ when 1
110
+ raise RuVim::CommandError, "#{spec.name} requires one argument" unless argv.length == 1
111
+ when :maybe_one
112
+ raise RuVim::CommandError, "#{spec.name} takes at most one argument" unless argv.length <= 1
113
+ end
114
+
115
+ if bang && !spec.bang
116
+ raise RuVim::CommandError, "#{spec.name} does not accept !"
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,110 @@
1
+ module RuVim
2
+ module DisplayWidth
3
+ module_function
4
+
5
+ def cell_width(ch, col: 0, tabstop: 2)
6
+ return 1 if ch.nil? || ch.empty?
7
+
8
+ if ch == "\t"
9
+ width = tabstop - (col % tabstop)
10
+ return width.zero? ? tabstop : width
11
+ end
12
+
13
+ code = ch.ord
14
+ return 0 if code.zero?
15
+ return 0 if combining_mark?(code)
16
+ return 0 if zero_width_codepoint?(code)
17
+ return ambiguous_width if ambiguous_codepoint?(code)
18
+ return 2 if emoji_codepoint?(code)
19
+ return 2 if wide_codepoint?(code)
20
+
21
+ 1
22
+ end
23
+
24
+ def display_width(str, tabstop: 2, start_col: 0)
25
+ col = start_col
26
+ str.to_s.each_char { |ch| col += cell_width(ch, col:, tabstop:) }
27
+ col - start_col
28
+ end
29
+
30
+ def expand_tabs(str, tabstop: 2, start_col: 0)
31
+ col = start_col
32
+ out = +""
33
+ str.to_s.each_char do |ch|
34
+ if ch == "\t"
35
+ n = cell_width(ch, col:, tabstop:)
36
+ out << (" " * n)
37
+ col += n
38
+ else
39
+ out << ch
40
+ col += cell_width(ch, col:, tabstop:)
41
+ end
42
+ end
43
+ out
44
+ end
45
+
46
+ def combining_mark?(code)
47
+ (0x0300..0x036F).cover?(code) ||
48
+ (0x1AB0..0x1AFF).cover?(code) ||
49
+ (0x1DC0..0x1DFF).cover?(code) ||
50
+ (0x20D0..0x20FF).cover?(code) ||
51
+ (0xFE20..0xFE2F).cover?(code)
52
+ end
53
+
54
+ def zero_width_codepoint?(code)
55
+ (0x200D..0x200D).cover?(code) || # ZWJ
56
+ (0xFE00..0xFE0F).cover?(code) || # variation selectors
57
+ (0xE0100..0xE01EF).cover?(code) # variation selectors supplement
58
+ end
59
+
60
+ def wide_codepoint?(code)
61
+ (0x1100..0x115F).cover?(code) ||
62
+ (0x2329..0x232A).cover?(code) ||
63
+ (0x2E80..0xA4CF).cover?(code) ||
64
+ (0xAC00..0xD7A3).cover?(code) ||
65
+ (0xF900..0xFAFF).cover?(code) ||
66
+ (0xFE10..0xFE19).cover?(code) ||
67
+ (0xFE30..0xFE6F).cover?(code) ||
68
+ (0xFF00..0xFF60).cover?(code) ||
69
+ (0xFFE0..0xFFE6).cover?(code)
70
+ end
71
+
72
+ def emoji_codepoint?(code)
73
+ (0x1F300..0x1FAFF).cover?(code) ||
74
+ (0x2600..0x27BF).cover?(code)
75
+ end
76
+
77
+ def ambiguous_codepoint?(code)
78
+ (0x00A1..0x00A1).cover?(code) ||
79
+ (0x00A4..0x00A4).cover?(code) ||
80
+ (0x00A7..0x00A8).cover?(code) ||
81
+ (0x00AA..0x00AA).cover?(code) ||
82
+ (0x00AD..0x00AE).cover?(code) ||
83
+ (0x00B0..0x00B4).cover?(code) ||
84
+ (0x00B6..0x00BA).cover?(code) ||
85
+ (0x00BC..0x00BF).cover?(code) ||
86
+ (0x0391..0x03A9).cover?(code) ||
87
+ (0x03B1..0x03C9).cover?(code) ||
88
+ (0x2010..0x2010).cover?(code) ||
89
+ (0x2013..0x2016).cover?(code) ||
90
+ (0x2018..0x2019).cover?(code) ||
91
+ (0x201C..0x201D).cover?(code) ||
92
+ (0x2020..0x2022).cover?(code) ||
93
+ (0x2024..0x2027).cover?(code) ||
94
+ (0x2030..0x2030).cover?(code) ||
95
+ (0x2032..0x2033).cover?(code) ||
96
+ (0x2035..0x2035).cover?(code) ||
97
+ (0x203B..0x203B).cover?(code) ||
98
+ (0x203E..0x203E).cover?(code) ||
99
+ (0x2460..0x24E9).cover?(code) ||
100
+ (0x2500..0x257F).cover?(code)
101
+ end
102
+
103
+ def ambiguous_width
104
+ env = ::ENV["RUVIM_AMBIGUOUS_WIDTH"]
105
+ return 2 if env == "2"
106
+
107
+ 1
108
+ end
109
+ end
110
+ end