ruvim 0.4.0 → 0.6.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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +53 -4
  3. data/README.md +15 -6
  4. data/Rakefile +7 -0
  5. data/benchmark/cext_compare.rb +165 -0
  6. data/benchmark/chunked_load.rb +256 -0
  7. data/benchmark/file_load.rb +140 -0
  8. data/benchmark/hotspots.rb +178 -0
  9. data/docs/binding.md +3 -2
  10. data/docs/command.md +81 -9
  11. data/docs/done.md +23 -0
  12. data/docs/spec.md +105 -19
  13. data/docs/todo.md +9 -0
  14. data/docs/tutorial.md +9 -1
  15. data/docs/vim_diff.md +13 -0
  16. data/ext/ruvim/extconf.rb +5 -0
  17. data/ext/ruvim/ruvim_ext.c +519 -0
  18. data/lib/ruvim/app.rb +217 -2778
  19. data/lib/ruvim/browser.rb +104 -0
  20. data/lib/ruvim/buffer.rb +39 -28
  21. data/lib/ruvim/command_invocation.rb +2 -2
  22. data/lib/ruvim/completion_manager.rb +708 -0
  23. data/lib/ruvim/dispatcher.rb +14 -8
  24. data/lib/ruvim/display_width.rb +91 -45
  25. data/lib/ruvim/editor.rb +64 -81
  26. data/lib/ruvim/ex_command_registry.rb +3 -1
  27. data/lib/ruvim/gh/link.rb +207 -0
  28. data/lib/ruvim/git/blame.rb +16 -6
  29. data/lib/ruvim/git/branch.rb +20 -5
  30. data/lib/ruvim/git/grep.rb +107 -0
  31. data/lib/ruvim/git/handler.rb +42 -1
  32. data/lib/ruvim/global_commands.rb +175 -35
  33. data/lib/ruvim/highlighter.rb +4 -13
  34. data/lib/ruvim/key_handler.rb +1510 -0
  35. data/lib/ruvim/keymap_manager.rb +7 -7
  36. data/lib/ruvim/lang/base.rb +5 -0
  37. data/lib/ruvim/lang/c.rb +116 -0
  38. data/lib/ruvim/lang/cpp.rb +107 -0
  39. data/lib/ruvim/lang/csv.rb +4 -1
  40. data/lib/ruvim/lang/diff.rb +2 -0
  41. data/lib/ruvim/lang/dockerfile.rb +36 -0
  42. data/lib/ruvim/lang/elixir.rb +85 -0
  43. data/lib/ruvim/lang/erb.rb +30 -0
  44. data/lib/ruvim/lang/go.rb +83 -0
  45. data/lib/ruvim/lang/html.rb +34 -0
  46. data/lib/ruvim/lang/javascript.rb +83 -0
  47. data/lib/ruvim/lang/json.rb +6 -0
  48. data/lib/ruvim/lang/lua.rb +76 -0
  49. data/lib/ruvim/lang/makefile.rb +36 -0
  50. data/lib/ruvim/lang/markdown.rb +3 -4
  51. data/lib/ruvim/lang/ocaml.rb +77 -0
  52. data/lib/ruvim/lang/perl.rb +91 -0
  53. data/lib/ruvim/lang/python.rb +85 -0
  54. data/lib/ruvim/lang/registry.rb +102 -0
  55. data/lib/ruvim/lang/ruby.rb +7 -0
  56. data/lib/ruvim/lang/rust.rb +95 -0
  57. data/lib/ruvim/lang/scheme.rb +5 -0
  58. data/lib/ruvim/lang/sh.rb +76 -0
  59. data/lib/ruvim/lang/sql.rb +52 -0
  60. data/lib/ruvim/lang/toml.rb +36 -0
  61. data/lib/ruvim/lang/tsv.rb +4 -1
  62. data/lib/ruvim/lang/typescript.rb +53 -0
  63. data/lib/ruvim/lang/yaml.rb +62 -0
  64. data/lib/ruvim/rich_view/table_renderer.rb +3 -3
  65. data/lib/ruvim/rich_view.rb +14 -7
  66. data/lib/ruvim/screen.rb +126 -72
  67. data/lib/ruvim/stream/file_load.rb +85 -0
  68. data/lib/ruvim/stream/follow.rb +40 -0
  69. data/lib/ruvim/stream/git.rb +43 -0
  70. data/lib/ruvim/stream/run.rb +74 -0
  71. data/lib/ruvim/stream/stdin.rb +55 -0
  72. data/lib/ruvim/stream.rb +35 -0
  73. data/lib/ruvim/stream_mixer.rb +394 -0
  74. data/lib/ruvim/terminal.rb +18 -4
  75. data/lib/ruvim/text_metrics.rb +84 -65
  76. data/lib/ruvim/version.rb +1 -1
  77. data/lib/ruvim/window.rb +5 -5
  78. data/lib/ruvim.rb +23 -6
  79. data/test/app_command_test.rb +382 -0
  80. data/test/app_completion_test.rb +43 -19
  81. data/test/app_dot_repeat_test.rb +27 -3
  82. data/test/app_ex_command_test.rb +154 -0
  83. data/test/app_motion_test.rb +13 -12
  84. data/test/app_register_test.rb +2 -1
  85. data/test/app_scenario_test.rb +15 -10
  86. data/test/app_startup_test.rb +70 -27
  87. data/test/app_text_object_test.rb +2 -1
  88. data/test/app_unicode_behavior_test.rb +3 -2
  89. data/test/browser_test.rb +88 -0
  90. data/test/buffer_test.rb +24 -0
  91. data/test/cli_test.rb +63 -0
  92. data/test/command_invocation_test.rb +33 -0
  93. data/test/config_dsl_test.rb +47 -0
  94. data/test/dispatcher_test.rb +74 -4
  95. data/test/ex_command_registry_test.rb +106 -0
  96. data/test/follow_test.rb +20 -21
  97. data/test/gh_link_test.rb +141 -0
  98. data/test/git_blame_test.rb +96 -17
  99. data/test/git_grep_test.rb +64 -0
  100. data/test/highlighter_test.rb +125 -0
  101. data/test/indent_test.rb +137 -0
  102. data/test/input_screen_integration_test.rb +1 -1
  103. data/test/keyword_chars_test.rb +85 -0
  104. data/test/lang_test.rb +634 -0
  105. data/test/markdown_renderer_test.rb +5 -5
  106. data/test/on_save_hook_test.rb +12 -8
  107. data/test/render_snapshot_test.rb +78 -0
  108. data/test/rich_view_test.rb +42 -42
  109. data/test/run_command_test.rb +307 -0
  110. data/test/screen_test.rb +68 -5
  111. data/test/stream_test.rb +165 -0
  112. data/test/window_test.rb +59 -0
  113. metadata +52 -2
@@ -14,7 +14,7 @@ module RuVim
14
14
 
15
15
  def bind(mode, seq, id, argv: [], kwargs: {}, bang: false)
16
16
  tokens = normalize_seq(seq)
17
- @mode_maps[mode.to_sym][tokens] = build_invocation(id, argv:, kwargs:, bang:, tokens:)
17
+ @mode_maps[mode][tokens] = build_invocation(id, argv:, kwargs:, bang:, tokens:)
18
18
  end
19
19
 
20
20
  def bind_global(seq, id, argv: [], kwargs: {}, bang: false)
@@ -29,20 +29,20 @@ module RuVim
29
29
 
30
30
  def bind_filetype(filetype, seq, id, mode: :normal, argv: [], kwargs: {}, bang: false)
31
31
  tokens = normalize_seq(seq)
32
- @filetype_maps[filetype.to_s][mode.to_sym][tokens] = build_invocation(id, argv:, kwargs:, bang:, tokens:)
32
+ @filetype_maps[filetype.to_s][mode][tokens] = build_invocation(id, argv:, kwargs:, bang:, tokens:)
33
33
  end
34
34
 
35
35
  def resolve(mode, pending_tokens)
36
- resolve_layers([@mode_maps[mode.to_sym]], pending_tokens)
36
+ resolve_layers([@mode_maps[mode]], pending_tokens)
37
37
  end
38
38
 
39
39
  def resolve_with_context(mode, pending_tokens, editor:)
40
40
  buffer = editor.current_buffer
41
41
  filetype = detect_filetype(buffer)
42
42
  layers = []
43
- layers << @filetype_maps[filetype][mode.to_sym] if filetype && @filetype_maps.key?(filetype)
43
+ layers << @filetype_maps[filetype][mode] if filetype && @filetype_maps.key?(filetype)
44
44
  layers << @buffer_maps[buffer.id] if @buffer_maps.key?(buffer.id)
45
- layers << @mode_maps[mode.to_sym]
45
+ layers << @mode_maps[mode]
46
46
  layers << @global_map
47
47
  resolve_layers(layers, pending_tokens)
48
48
  end
@@ -135,7 +135,7 @@ module RuVim
135
135
  def normalized_mode_filter(mode)
136
136
  return nil if mode.nil?
137
137
 
138
- ary = Array(mode).compact.map { |m| m.to_sym }
138
+ ary = Array(mode).compact
139
139
  ary.empty? ? nil : ary
140
140
  end
141
141
 
@@ -153,7 +153,7 @@ module RuVim
153
153
  operator_pending: 5,
154
154
  command_line: 6
155
155
  }
156
- [order.fetch(mode.to_sym, 99), mode.to_s]
156
+ [order.fetch(mode, 99), mode.to_s]
157
157
  end
158
158
 
159
159
  def detect_filetype(buffer)
@@ -21,5 +21,10 @@ module RuVim
21
21
  # no-op
22
22
  end
23
23
  end
24
+
25
+ # Non-highlighted filetypes (extension-only detection)
26
+ Registry.register("text", mod: Base, extensions: %w[.txt])
27
+ Registry.register("css", mod: Base, extensions: %w[.css])
28
+ Registry.register("erlang", mod: Base, extensions: %w[.erl])
24
29
  end
25
30
  end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+
5
+ module RuVim
6
+ module Lang
7
+ module C
8
+ KEYWORDS = %w[
9
+ if else for while return struct enum typedef switch case break
10
+ continue do static const extern unsigned signed sizeof union
11
+ goto default volatile register inline restrict
12
+ ].freeze
13
+
14
+ TYPES = %w[
15
+ int char float double long short void size_t
16
+ uint8_t uint16_t uint32_t uint64_t
17
+ int8_t int16_t int32_t int64_t
18
+ ssize_t ptrdiff_t intptr_t uintptr_t
19
+ bool FILE
20
+ ].freeze
21
+
22
+ ALL_KEYWORDS = (KEYWORDS + TYPES).uniq.freeze
23
+
24
+ KEYWORD_RE = /\b(?:#{ALL_KEYWORDS.join("|")})\b/
25
+ STRING_RE = /"(?:\\.|[^"\\])*"/
26
+ CHAR_RE = /'(?:\\.|[^'\\])'/
27
+ NUMBER_RE = /\b(?:0[xX][0-9a-fA-F]+|0[bB][01]+|\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)[fFlLuU]*\b/
28
+ LINE_COMMENT_RE = %r{//.*}
29
+ BLOCK_COMMENT_RE = %r{/\*.*?\*/}
30
+ PREPROCESSOR_RE = /\A\s*#\s*\w+.*/
31
+ CONSTANT_RE = /\b[A-Z][A-Z0-9_]{1,}\b/
32
+
33
+ INDENT_OPEN_RE = /\{\s*$/
34
+ INDENT_CLOSE_RE = /\A\s*\}/
35
+ CASE_RE = /\A\s*(?:case\b.*:|default\s*:)/
36
+
37
+ DEDENT_TRIGGERS = {
38
+ "}" => /\A(\s*)\}/
39
+ }.freeze
40
+
41
+ module_function
42
+
43
+ def calculate_indent(lines, target_row, shiftwidth)
44
+ depth = 0
45
+ (0...target_row).each do |row|
46
+ line = lines[row].to_s
47
+ line.each_char do |ch|
48
+ case ch
49
+ when "{" then depth += 1
50
+ when "}" then depth -= 1
51
+ end
52
+ end
53
+ end
54
+
55
+ target_line = lines[target_row].to_s.lstrip
56
+ depth -= 1 if target_line.match?(INDENT_CLOSE_RE)
57
+ depth -= 1 if target_line.match?(CASE_RE) && depth > 0
58
+ depth = 0 if depth < 0
59
+ depth * shiftwidth
60
+ end
61
+
62
+ def indent_trigger?(line)
63
+ line.to_s.rstrip.match?(INDENT_OPEN_RE)
64
+ end
65
+
66
+ def dedent_trigger(char)
67
+ DEDENT_TRIGGERS[char]
68
+ end
69
+
70
+ def on_save(ctx, path)
71
+ return unless path && File.exist?(path)
72
+ return if ctx.editor.respond_to?(:restricted_mode?) && ctx.editor.restricted_mode?
73
+
74
+ gcc = ENV["CC"] || "gcc"
75
+ begin
76
+ output, status = Open3.capture2e(gcc, "-fsyntax-only", "-Wall", path)
77
+ rescue Errno::ENOENT
78
+ return
79
+ end
80
+
81
+ if !status.success?
82
+ buffer_id = ctx.buffer.id
83
+ items = output.lines.filter_map { |line|
84
+ if line =~ /\A.+?:(\d+):\d+:/
85
+ { buffer_id: buffer_id, row: $1.to_i - 1, col: 0, text: line.strip }
86
+ end
87
+ }
88
+ items = [{ buffer_id: buffer_id, row: 0, col: 0, text: output.strip }] if items.empty?
89
+ ctx.editor.set_quickfix_list(items)
90
+ first = output.lines.first.to_s.strip
91
+ hint = items.size > 1 ? " (Q to see all, #{items.size} total)" : ""
92
+ ctx.editor.echo_error("#{first}#{hint}")
93
+ else
94
+ ctx.editor.set_quickfix_list([])
95
+ end
96
+ end
97
+
98
+ def color_columns(text)
99
+ cols = {}
100
+ Highlighter.apply_regex(cols, text, CHAR_RE, Highlighter::STRING_COLOR)
101
+ Highlighter.apply_regex(cols, text, STRING_RE, Highlighter::STRING_COLOR)
102
+ Highlighter.apply_regex(cols, text, KEYWORD_RE, Highlighter::KEYWORD_COLOR)
103
+ Highlighter.apply_regex(cols, text, NUMBER_RE, Highlighter::NUMBER_COLOR)
104
+ Highlighter.apply_regex(cols, text, CONSTANT_RE, Highlighter::CONSTANT_COLOR)
105
+ Highlighter.apply_regex(cols, text, PREPROCESSOR_RE, "\e[35m")
106
+ Highlighter.apply_regex(cols, text, BLOCK_COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
107
+ Highlighter.apply_regex(cols, text, LINE_COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
108
+ cols
109
+ end
110
+ end
111
+
112
+ Registry.register("c", mod: C,
113
+ extensions: %w[.c .h],
114
+ runprg: "gcc -Wall -o /tmp/a.out % && /tmp/a.out")
115
+ end
116
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+
5
+ module RuVim
6
+ module Lang
7
+ module Cpp
8
+ CPP_KEYWORDS = %w[
9
+ class namespace template typename virtual override final
10
+ public private protected friend
11
+ new delete dynamic_cast static_cast reinterpret_cast const_cast
12
+ try catch throw noexcept
13
+ using auto decltype nullptr constexpr consteval constinit
14
+ concept requires co_await co_return co_yield
15
+ mutable explicit export
16
+ ].freeze
17
+
18
+ CPP_TYPES = %w[
19
+ string vector map set list deque array
20
+ shared_ptr unique_ptr weak_ptr
21
+ string_view optional variant any
22
+ wchar_t char8_t char16_t char32_t
23
+ ].freeze
24
+
25
+ ALL_KEYWORDS = (C::ALL_KEYWORDS + CPP_KEYWORDS + CPP_TYPES).uniq.freeze
26
+
27
+ KEYWORD_RE = /\b(?:#{ALL_KEYWORDS.join("|")})\b/
28
+
29
+ ACCESS_RE = /\A\s*(?:public|private|protected)\s*:/
30
+
31
+ module_function
32
+
33
+ def calculate_indent(lines, target_row, shiftwidth)
34
+ depth = 0
35
+ (0...target_row).each do |row|
36
+ line = lines[row].to_s
37
+ line.each_char do |ch|
38
+ case ch
39
+ when "{" then depth += 1
40
+ when "}" then depth -= 1
41
+ end
42
+ end
43
+ end
44
+
45
+ target_line = lines[target_row].to_s.lstrip
46
+ depth -= 1 if target_line.match?(C::INDENT_CLOSE_RE)
47
+ depth -= 1 if target_line.match?(C::CASE_RE) && depth > 0
48
+ depth -= 1 if target_line.match?(ACCESS_RE) && depth > 0
49
+ depth = 0 if depth < 0
50
+ depth * shiftwidth
51
+ end
52
+
53
+ def indent_trigger?(line)
54
+ C.indent_trigger?(line)
55
+ end
56
+
57
+ def dedent_trigger(char)
58
+ C.dedent_trigger(char)
59
+ end
60
+
61
+ def on_save(ctx, path)
62
+ return unless path && File.exist?(path)
63
+ return if ctx.editor.respond_to?(:restricted_mode?) && ctx.editor.restricted_mode?
64
+
65
+ compiler = ENV["CXX"] || "g++"
66
+ begin
67
+ output, status = Open3.capture2e(compiler, "-fsyntax-only", "-Wall", path)
68
+ rescue Errno::ENOENT
69
+ return
70
+ end
71
+
72
+ if !status.success?
73
+ buffer_id = ctx.buffer.id
74
+ items = output.lines.filter_map { |line|
75
+ if line =~ /\A.+?:(\d+):\d+:/
76
+ { buffer_id: buffer_id, row: $1.to_i - 1, col: 0, text: line.strip }
77
+ end
78
+ }
79
+ items = [{ buffer_id: buffer_id, row: 0, col: 0, text: output.strip }] if items.empty?
80
+ ctx.editor.set_quickfix_list(items)
81
+ first = output.lines.first.to_s.strip
82
+ hint = items.size > 1 ? " (Q to see all, #{items.size} total)" : ""
83
+ ctx.editor.echo_error("#{first}#{hint}")
84
+ else
85
+ ctx.editor.set_quickfix_list([])
86
+ end
87
+ end
88
+
89
+ def color_columns(text)
90
+ cols = {}
91
+ Highlighter.apply_regex(cols, text, C::CHAR_RE, Highlighter::STRING_COLOR)
92
+ Highlighter.apply_regex(cols, text, C::STRING_RE, Highlighter::STRING_COLOR)
93
+ Highlighter.apply_regex(cols, text, KEYWORD_RE, Highlighter::KEYWORD_COLOR)
94
+ Highlighter.apply_regex(cols, text, C::NUMBER_RE, Highlighter::NUMBER_COLOR)
95
+ Highlighter.apply_regex(cols, text, C::CONSTANT_RE, Highlighter::CONSTANT_COLOR)
96
+ Highlighter.apply_regex(cols, text, C::PREPROCESSOR_RE, "\e[35m")
97
+ Highlighter.apply_regex(cols, text, C::BLOCK_COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
98
+ Highlighter.apply_regex(cols, text, C::LINE_COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
99
+ cols
100
+ end
101
+ end
102
+
103
+ Registry.register("cpp", mod: Cpp,
104
+ extensions: %w[.cpp .cc .cxx .hpp],
105
+ runprg: "g++ -Wall -o /tmp/a.out % && /tmp/a.out")
106
+ end
107
+ end
@@ -2,6 +2,9 @@
2
2
 
3
3
  module RuVim
4
4
  module Lang
5
+ Registry.register("csv", mod: Base,
6
+ extensions: %w[.csv])
7
+
5
8
  module Csv
6
9
  module_function
7
10
 
@@ -14,5 +17,5 @@ module RuVim
14
17
  end
15
18
  end
16
19
 
17
- RichView.register("csv", RichView::TableRenderer, detector: Lang::Csv.method(:detect?))
20
+ RichView.register(:csv, RichView::TableRenderer, detector: Lang::Csv.method(:detect?))
18
21
  end
@@ -37,5 +37,7 @@ module RuVim
37
37
  end
38
38
  end
39
39
  end
40
+
41
+ Registry.register("diff", mod: Diff)
40
42
  end
41
43
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuVim
4
+ module Lang
5
+ module Dockerfile
6
+ INSTRUCTIONS = %w[
7
+ FROM AS RUN CMD EXPOSE ENV ADD COPY ENTRYPOINT VOLUME
8
+ USER WORKDIR ARG ONBUILD STOPSIGNAL HEALTHCHECK SHELL LABEL MAINTAINER
9
+ ].freeze
10
+
11
+ INSTRUCTION_RE = /\A\s*(?:#{INSTRUCTIONS.join("|")})\b/i
12
+ STRING_DOUBLE_RE = /"(?:\\.|[^"\\])*"/
13
+ STRING_SINGLE_RE = /'(?:\\.|[^'\\])*'/
14
+ VARIABLE_RE = /\$\{?[\w]+\}?/
15
+ COMMENT_RE = /\A\s*#.*/
16
+ FLAG_RE = /--[\w\-]+=?/
17
+
18
+ module_function
19
+
20
+ def color_columns(text)
21
+ cols = {}
22
+ Highlighter.apply_regex(cols, text, INSTRUCTION_RE, Highlighter::KEYWORD_COLOR)
23
+ Highlighter.apply_regex(cols, text, FLAG_RE, Highlighter::CONSTANT_COLOR)
24
+ Highlighter.apply_regex(cols, text, STRING_DOUBLE_RE, Highlighter::STRING_COLOR)
25
+ Highlighter.apply_regex(cols, text, STRING_SINGLE_RE, Highlighter::STRING_COLOR)
26
+ Highlighter.apply_regex(cols, text, VARIABLE_RE, Highlighter::VARIABLE_COLOR)
27
+ Highlighter.apply_regex(cols, text, COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
28
+ cols
29
+ end
30
+ end
31
+
32
+ Registry.register("dockerfile", mod: Dockerfile,
33
+ basenames: %w[Dockerfile],
34
+ basename_prefix: "Dockerfile")
35
+ end
36
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuVim
4
+ module Lang
5
+ module Elixir
6
+ KEYWORDS = %w[
7
+ def defp defmodule defmacro defmacrop defstruct defprotocol defimpl
8
+ defguard defguardp defdelegate defoverridable defexception
9
+ do end fn if else unless cond case when with for receive after
10
+ raise rescue try catch throw
11
+ import use alias require
12
+ and or not in
13
+ true false nil
14
+ quote unquote
15
+ ].freeze
16
+
17
+ KEYWORD_RE = /\b(?:#{KEYWORDS.join("|")})\b/
18
+ STRING_DOUBLE_RE = /"(?:\\.|[^"\\])*"/
19
+ STRING_SINGLE_RE = /'(?:\\.|[^'\\])*'/
20
+ HEREDOC_RE = /"""/
21
+ SIGIL_RE = /~[a-zA-Z](?:\(.*?\)|\[.*?\]|\{.*?\}|<.*?>|\/.*?\/|".*?"|\|.*?\|)/
22
+ ATOM_RE = /:\w+|:"(?:\\.|[^"\\])*"/
23
+ MODULE_RE = /\b[A-Z]\w*(?:\.[A-Z]\w*)*/
24
+ NUMBER_RE = /\b(?:0[xXoObB][\da-fA-F_]+|\d[\d_]*(?:\.[\d_]+)?(?:[eE][+-]?\d+)?)\b/
25
+ COMMENT_RE = /#.*/
26
+ VARIABLE_RE = /@\w+/
27
+
28
+ INDENT_OPEN_RE = /\b(?:do|fn)\s*(?:->)?\s*$/
29
+ INDENT_CLOSE_RE = /\A\s*end\b/
30
+ INDENT_MID_RE = /\A\s*(?:else|rescue|catch|after)\b/
31
+
32
+ DEDENT_TRIGGERS = {
33
+ "d" => /\A(\s*)end\z/,
34
+ "e" => /\A(\s*)(?:else|rescue)\z/,
35
+ "h" => /\A(\s*)catch\z/,
36
+ "r" => /\A(\s*)after\z/
37
+ }.freeze
38
+
39
+ module_function
40
+
41
+ def calculate_indent(lines, target_row, shiftwidth)
42
+ depth = 0
43
+ (0...target_row).each do |row|
44
+ line = lines[row].to_s.strip
45
+ next if line.empty? || line.start_with?("#")
46
+
47
+ depth += 1 if line.match?(/\b(?:do|fn)\b/) || line.match?(/->$/)
48
+ depth -= 1 if line.match?(/\Aend\b/)
49
+ end
50
+
51
+ target_line = lines[target_row].to_s.strip
52
+ depth -= 1 if target_line.match?(INDENT_CLOSE_RE) || target_line.match?(INDENT_MID_RE)
53
+ depth = 0 if depth < 0
54
+ depth * shiftwidth
55
+ end
56
+
57
+ def indent_trigger?(line)
58
+ line.to_s.rstrip.match?(INDENT_OPEN_RE)
59
+ end
60
+
61
+ def dedent_trigger(char)
62
+ DEDENT_TRIGGERS[char]
63
+ end
64
+
65
+ def color_columns(text)
66
+ cols = {}
67
+ Highlighter.apply_regex(cols, text, STRING_DOUBLE_RE, Highlighter::STRING_COLOR)
68
+ Highlighter.apply_regex(cols, text, STRING_SINGLE_RE, Highlighter::STRING_COLOR)
69
+ Highlighter.apply_regex(cols, text, ATOM_RE, Highlighter::CONSTANT_COLOR)
70
+ Highlighter.apply_regex(cols, text, KEYWORD_RE, Highlighter::KEYWORD_COLOR)
71
+ Highlighter.apply_regex(cols, text, MODULE_RE, Highlighter::CONSTANT_COLOR)
72
+ Highlighter.apply_regex(cols, text, VARIABLE_RE, Highlighter::VARIABLE_COLOR)
73
+ Highlighter.apply_regex(cols, text, NUMBER_RE, Highlighter::NUMBER_COLOR)
74
+ Highlighter.apply_regex(cols, text, SIGIL_RE, Highlighter::STRING_COLOR)
75
+ Highlighter.apply_regex(cols, text, COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
76
+ cols
77
+ end
78
+ end
79
+
80
+ Registry.register("elixir", mod: Elixir,
81
+ extensions: %w[.ex .exs],
82
+ shebangs: %w[elixir iex],
83
+ runprg: "elixir %")
84
+ end
85
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuVim
4
+ module Lang
5
+ module Erb
6
+ ERB_COMMENT_RE = /<%#.*?%>/
7
+ ERB_TAG_RE = /<%[=\-]?|[-%]?%>/
8
+
9
+ module_function
10
+
11
+ def color_columns(text)
12
+ cols = {}
13
+
14
+ # First apply HTML highlighting as base
15
+ Html.color_columns(text).each { |k, v| cols[k] = v }
16
+
17
+ # ERB delimiters (<%= %> <% %> <%- -%>)
18
+ Highlighter.apply_regex(cols, text, ERB_TAG_RE, "\e[35m", override: true)
19
+
20
+ # ERB comment tags override everything (including delimiters)
21
+ Highlighter.apply_regex(cols, text, ERB_COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
22
+
23
+ cols
24
+ end
25
+ end
26
+
27
+ Registry.register("erb", mod: Erb,
28
+ extensions: %w[.erb])
29
+ end
30
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuVim
4
+ module Lang
5
+ module Go
6
+ KEYWORDS = %w[
7
+ break case chan const continue default defer else fallthrough
8
+ for func go goto if import interface map package range return
9
+ select struct switch type var
10
+ true false nil iota
11
+ ].freeze
12
+
13
+ TYPES = %w[
14
+ bool byte complex64 complex128 error float32 float64
15
+ int int8 int16 int32 int64 rune string
16
+ uint uint8 uint16 uint32 uint64 uintptr any comparable
17
+ ].freeze
18
+
19
+ ALL_KEYWORDS = (KEYWORDS + TYPES).uniq.freeze
20
+
21
+ KEYWORD_RE = /\b(?:#{ALL_KEYWORDS.join("|")})\b/
22
+ STRING_DOUBLE_RE = /"(?:\\.|[^"\\])*"/
23
+ STRING_RAW_RE = /`[^`]*`/
24
+ CHAR_RE = /'(?:\\.|[^'\\])'/
25
+ NUMBER_RE = /\b(?:0[xXoObB][\da-fA-F_]+|\d[\d_]*(?:\.[\d_]+)?(?:[eE][+-]?\d+)?i?)\b/
26
+ LINE_COMMENT_RE = %r{//.*}
27
+ BLOCK_COMMENT_RE = %r{/\*.*?\*/}
28
+ CONSTANT_RE = /\b[A-Z][A-Z0-9_]{1,}\b/
29
+
30
+ INDENT_OPEN_RE = /\{\s*(?:\/\/.*)?$/
31
+ INDENT_CLOSE_RE = /\A\s*\}/
32
+
33
+ DEDENT_TRIGGERS = {
34
+ "}" => /\A(\s*)\}/
35
+ }.freeze
36
+
37
+ module_function
38
+
39
+ def calculate_indent(lines, target_row, shiftwidth)
40
+ depth = 0
41
+ (0...target_row).each do |row|
42
+ line = lines[row].to_s
43
+ line.each_char do |ch|
44
+ case ch
45
+ when "{" then depth += 1
46
+ when "}" then depth -= 1
47
+ end
48
+ end
49
+ end
50
+
51
+ target_line = lines[target_row].to_s.lstrip
52
+ depth -= 1 if target_line.match?(INDENT_CLOSE_RE)
53
+ depth = 0 if depth < 0
54
+ depth * shiftwidth
55
+ end
56
+
57
+ def indent_trigger?(line)
58
+ line.to_s.rstrip.match?(INDENT_OPEN_RE)
59
+ end
60
+
61
+ def dedent_trigger(char)
62
+ DEDENT_TRIGGERS[char]
63
+ end
64
+
65
+ def color_columns(text)
66
+ cols = {}
67
+ Highlighter.apply_regex(cols, text, CHAR_RE, Highlighter::STRING_COLOR)
68
+ Highlighter.apply_regex(cols, text, STRING_DOUBLE_RE, Highlighter::STRING_COLOR)
69
+ Highlighter.apply_regex(cols, text, STRING_RAW_RE, Highlighter::STRING_COLOR)
70
+ Highlighter.apply_regex(cols, text, KEYWORD_RE, Highlighter::KEYWORD_COLOR)
71
+ Highlighter.apply_regex(cols, text, NUMBER_RE, Highlighter::NUMBER_COLOR)
72
+ Highlighter.apply_regex(cols, text, CONSTANT_RE, Highlighter::CONSTANT_COLOR)
73
+ Highlighter.apply_regex(cols, text, BLOCK_COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
74
+ Highlighter.apply_regex(cols, text, LINE_COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
75
+ cols
76
+ end
77
+ end
78
+
79
+ Registry.register("go", mod: Go,
80
+ extensions: %w[.go],
81
+ runprg: "go run %")
82
+ end
83
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuVim
4
+ module Lang
5
+ module Html
6
+ TAG_RE = /<\/?[\w\-]+/
7
+ TAG_CLOSE_RE = /\/?>/
8
+ ATTR_NAME_RE = /\b[\w\-]+(?==)/
9
+ STRING_DOUBLE_RE = /"(?:[^"\\]|\\.)*"/
10
+ STRING_SINGLE_RE = /'(?:[^'\\]|\\.)*'/
11
+ COMMENT_RE = /<!--.*?-->/
12
+ DOCTYPE_RE = /<!DOCTYPE\b[^>]*/i
13
+ ENTITY_RE = /&\w+;|&#\d+;|&#x[\da-fA-F]+;/
14
+
15
+ module_function
16
+
17
+ def color_columns(text)
18
+ cols = {}
19
+ Highlighter.apply_regex(cols, text, TAG_RE, Highlighter::KEYWORD_COLOR)
20
+ Highlighter.apply_regex(cols, text, TAG_CLOSE_RE, Highlighter::KEYWORD_COLOR)
21
+ Highlighter.apply_regex(cols, text, DOCTYPE_RE, "\e[35m")
22
+ Highlighter.apply_regex(cols, text, ATTR_NAME_RE, Highlighter::VARIABLE_COLOR)
23
+ Highlighter.apply_regex(cols, text, STRING_DOUBLE_RE, Highlighter::STRING_COLOR)
24
+ Highlighter.apply_regex(cols, text, STRING_SINGLE_RE, Highlighter::STRING_COLOR)
25
+ Highlighter.apply_regex(cols, text, ENTITY_RE, Highlighter::CONSTANT_COLOR)
26
+ Highlighter.apply_regex(cols, text, COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
27
+ cols
28
+ end
29
+ end
30
+
31
+ Registry.register("html", mod: Html,
32
+ extensions: %w[.html .htm .xml])
33
+ end
34
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuVim
4
+ module Lang
5
+ module Javascript
6
+ KEYWORDS = %w[
7
+ async await break case catch class const continue debugger default
8
+ delete do else export extends finally for from function if import
9
+ in instanceof let new of return static super switch this throw
10
+ try typeof var void while with yield
11
+ true false null undefined NaN Infinity
12
+ ].freeze
13
+
14
+ KEYWORD_RE = /\b(?:#{KEYWORDS.join("|")})\b/
15
+ STRING_DOUBLE_RE = /"(?:\\.|[^"\\])*"/
16
+ STRING_SINGLE_RE = /'(?:\\.|[^'\\])*'/
17
+ TEMPLATE_RE = /`(?:\\.|[^`\\])*`/
18
+ NUMBER_RE = /\b(?:0[xXoObB][\da-fA-F_]+|\d[\d_]*(?:\.[\d_]+)?(?:[eE][+-]?\d+)?n?)\b/
19
+ LINE_COMMENT_RE = %r{//.*}
20
+ BLOCK_COMMENT_RE = %r{/\*.*?\*/}
21
+ CONSTANT_RE = /\b[A-Z][A-Z0-9_]{1,}\b/
22
+ REGEX_RE = %r{/(?:\\.|[^/\\])+/[gimsuvy]*}
23
+
24
+ INDENT_OPEN_RE = /[{(\[]\s*(?:\/\/.*)?$/
25
+ INDENT_CLOSE_RE = /\A\s*[}\])]/
26
+
27
+ DEDENT_TRIGGERS = {
28
+ "}" => /\A(\s*)\}/,
29
+ "]" => /\A(\s*)\]/,
30
+ ")" => /\A(\s*)\)/
31
+ }.freeze
32
+
33
+ module_function
34
+
35
+ def calculate_indent(lines, target_row, shiftwidth)
36
+ depth = 0
37
+ (0...target_row).each do |row|
38
+ line = lines[row].to_s
39
+ line.each_char do |ch|
40
+ case ch
41
+ when "{", "[", "(" then depth += 1
42
+ when "}", "]", ")" then depth -= 1
43
+ end
44
+ end
45
+ end
46
+
47
+ target_line = lines[target_row].to_s.lstrip
48
+ depth -= 1 if target_line.match?(INDENT_CLOSE_RE)
49
+ depth = 0 if depth < 0
50
+ depth * shiftwidth
51
+ end
52
+
53
+ def indent_trigger?(line)
54
+ line.to_s.rstrip.match?(INDENT_OPEN_RE)
55
+ end
56
+
57
+ def dedent_trigger(char)
58
+ DEDENT_TRIGGERS[char]
59
+ end
60
+
61
+ def color_columns(text)
62
+ cols = {}
63
+ Highlighter.apply_regex(cols, text, STRING_SINGLE_RE, Highlighter::STRING_COLOR)
64
+ Highlighter.apply_regex(cols, text, STRING_DOUBLE_RE, Highlighter::STRING_COLOR)
65
+ Highlighter.apply_regex(cols, text, TEMPLATE_RE, Highlighter::STRING_COLOR)
66
+ Highlighter.apply_regex(cols, text, KEYWORD_RE, Highlighter::KEYWORD_COLOR)
67
+ Highlighter.apply_regex(cols, text, NUMBER_RE, Highlighter::NUMBER_COLOR)
68
+ Highlighter.apply_regex(cols, text, CONSTANT_RE, Highlighter::CONSTANT_COLOR)
69
+ Highlighter.apply_regex(cols, text, BLOCK_COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
70
+ Highlighter.apply_regex(cols, text, LINE_COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
71
+ cols
72
+ end
73
+ end
74
+
75
+ Registry.register("javascript", mod: Javascript,
76
+ extensions: %w[.js .mjs .cjs],
77
+ aliases: %w[javascriptreact],
78
+ shebangs: %w[node nodejs deno],
79
+ runprg: "node %")
80
+ Registry.register("javascriptreact", mod: Javascript,
81
+ extensions: %w[.jsx])
82
+ end
83
+ end
@@ -48,5 +48,11 @@ module RuVim
48
48
  cols
49
49
  end
50
50
  end
51
+
52
+ Registry.register("json", mod: Json,
53
+ extensions: %w[.json],
54
+ aliases: %w[jsonl])
55
+ Registry.register("jsonl", mod: Json,
56
+ extensions: %w[.jsonl])
51
57
  end
52
58
  end