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.
- checksums.yaml +4 -4
- data/AGENTS.md +53 -4
- data/README.md +15 -6
- data/Rakefile +7 -0
- data/benchmark/cext_compare.rb +165 -0
- data/benchmark/chunked_load.rb +256 -0
- data/benchmark/file_load.rb +140 -0
- data/benchmark/hotspots.rb +178 -0
- data/docs/binding.md +3 -2
- data/docs/command.md +81 -9
- data/docs/done.md +23 -0
- data/docs/spec.md +105 -19
- data/docs/todo.md +9 -0
- data/docs/tutorial.md +9 -1
- data/docs/vim_diff.md +13 -0
- data/ext/ruvim/extconf.rb +5 -0
- data/ext/ruvim/ruvim_ext.c +519 -0
- data/lib/ruvim/app.rb +217 -2778
- data/lib/ruvim/browser.rb +104 -0
- data/lib/ruvim/buffer.rb +39 -28
- data/lib/ruvim/command_invocation.rb +2 -2
- data/lib/ruvim/completion_manager.rb +708 -0
- data/lib/ruvim/dispatcher.rb +14 -8
- data/lib/ruvim/display_width.rb +91 -45
- data/lib/ruvim/editor.rb +64 -81
- data/lib/ruvim/ex_command_registry.rb +3 -1
- data/lib/ruvim/gh/link.rb +207 -0
- data/lib/ruvim/git/blame.rb +16 -6
- data/lib/ruvim/git/branch.rb +20 -5
- data/lib/ruvim/git/grep.rb +107 -0
- data/lib/ruvim/git/handler.rb +42 -1
- data/lib/ruvim/global_commands.rb +175 -35
- data/lib/ruvim/highlighter.rb +4 -13
- data/lib/ruvim/key_handler.rb +1510 -0
- data/lib/ruvim/keymap_manager.rb +7 -7
- data/lib/ruvim/lang/base.rb +5 -0
- data/lib/ruvim/lang/c.rb +116 -0
- data/lib/ruvim/lang/cpp.rb +107 -0
- data/lib/ruvim/lang/csv.rb +4 -1
- data/lib/ruvim/lang/diff.rb +2 -0
- data/lib/ruvim/lang/dockerfile.rb +36 -0
- data/lib/ruvim/lang/elixir.rb +85 -0
- data/lib/ruvim/lang/erb.rb +30 -0
- data/lib/ruvim/lang/go.rb +83 -0
- data/lib/ruvim/lang/html.rb +34 -0
- data/lib/ruvim/lang/javascript.rb +83 -0
- data/lib/ruvim/lang/json.rb +6 -0
- data/lib/ruvim/lang/lua.rb +76 -0
- data/lib/ruvim/lang/makefile.rb +36 -0
- data/lib/ruvim/lang/markdown.rb +3 -4
- data/lib/ruvim/lang/ocaml.rb +77 -0
- data/lib/ruvim/lang/perl.rb +91 -0
- data/lib/ruvim/lang/python.rb +85 -0
- data/lib/ruvim/lang/registry.rb +102 -0
- data/lib/ruvim/lang/ruby.rb +7 -0
- data/lib/ruvim/lang/rust.rb +95 -0
- data/lib/ruvim/lang/scheme.rb +5 -0
- data/lib/ruvim/lang/sh.rb +76 -0
- data/lib/ruvim/lang/sql.rb +52 -0
- data/lib/ruvim/lang/toml.rb +36 -0
- data/lib/ruvim/lang/tsv.rb +4 -1
- data/lib/ruvim/lang/typescript.rb +53 -0
- data/lib/ruvim/lang/yaml.rb +62 -0
- data/lib/ruvim/rich_view/table_renderer.rb +3 -3
- data/lib/ruvim/rich_view.rb +14 -7
- data/lib/ruvim/screen.rb +126 -72
- data/lib/ruvim/stream/file_load.rb +85 -0
- data/lib/ruvim/stream/follow.rb +40 -0
- data/lib/ruvim/stream/git.rb +43 -0
- data/lib/ruvim/stream/run.rb +74 -0
- data/lib/ruvim/stream/stdin.rb +55 -0
- data/lib/ruvim/stream.rb +35 -0
- data/lib/ruvim/stream_mixer.rb +394 -0
- data/lib/ruvim/terminal.rb +18 -4
- data/lib/ruvim/text_metrics.rb +84 -65
- data/lib/ruvim/version.rb +1 -1
- data/lib/ruvim/window.rb +5 -5
- data/lib/ruvim.rb +23 -6
- data/test/app_command_test.rb +382 -0
- data/test/app_completion_test.rb +43 -19
- data/test/app_dot_repeat_test.rb +27 -3
- data/test/app_ex_command_test.rb +154 -0
- data/test/app_motion_test.rb +13 -12
- data/test/app_register_test.rb +2 -1
- data/test/app_scenario_test.rb +15 -10
- data/test/app_startup_test.rb +70 -27
- data/test/app_text_object_test.rb +2 -1
- data/test/app_unicode_behavior_test.rb +3 -2
- data/test/browser_test.rb +88 -0
- data/test/buffer_test.rb +24 -0
- data/test/cli_test.rb +63 -0
- data/test/command_invocation_test.rb +33 -0
- data/test/config_dsl_test.rb +47 -0
- data/test/dispatcher_test.rb +74 -4
- data/test/ex_command_registry_test.rb +106 -0
- data/test/follow_test.rb +20 -21
- data/test/gh_link_test.rb +141 -0
- data/test/git_blame_test.rb +96 -17
- data/test/git_grep_test.rb +64 -0
- data/test/highlighter_test.rb +125 -0
- data/test/indent_test.rb +137 -0
- data/test/input_screen_integration_test.rb +1 -1
- data/test/keyword_chars_test.rb +85 -0
- data/test/lang_test.rb +634 -0
- data/test/markdown_renderer_test.rb +5 -5
- data/test/on_save_hook_test.rb +12 -8
- data/test/render_snapshot_test.rb +78 -0
- data/test/rich_view_test.rb +42 -42
- data/test/run_command_test.rb +307 -0
- data/test/screen_test.rb +68 -5
- data/test/stream_test.rb +165 -0
- data/test/window_test.rb +59 -0
- metadata +52 -2
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuVim
|
|
4
|
+
module Lang
|
|
5
|
+
module Lua
|
|
6
|
+
KEYWORDS = %w[
|
|
7
|
+
and break do else elseif end false for function goto if in
|
|
8
|
+
local nil not or repeat return then true until while
|
|
9
|
+
].freeze
|
|
10
|
+
|
|
11
|
+
KEYWORD_RE = /\b(?:#{KEYWORDS.join("|")})\b/
|
|
12
|
+
STRING_DOUBLE_RE = /"(?:\\.|[^"\\])*"/
|
|
13
|
+
STRING_SINGLE_RE = /'(?:\\.|[^'\\])*'/
|
|
14
|
+
LONG_STRING_RE = /\[\[.*?\]\]/
|
|
15
|
+
NUMBER_RE = /\b(?:0[xX][\da-fA-F]+(?:\.[\da-fA-F]+)?(?:[pP][+-]?\d+)?|\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\b/
|
|
16
|
+
LINE_COMMENT_RE = /--(?!\[\[).*/
|
|
17
|
+
BLOCK_COMMENT_RE = /--\[\[.*?\]\]/
|
|
18
|
+
BUILTIN_RE = /\b(?:print|type|tostring|tonumber|pairs|ipairs|next|select|unpack|require|error|assert|pcall|xpcall|setmetatable|getmetatable|rawget|rawset|rawequal|rawlen|table|string|math|io|os|coroutine|debug|package)\b/
|
|
19
|
+
|
|
20
|
+
INDENT_OPEN_RE = /\b(?:function|if|for|while|repeat|do|else|elseif)\b/
|
|
21
|
+
INDENT_CLOSE_RE = /\A\s*(?:end|until)\b/
|
|
22
|
+
INDENT_MID_RE = /\A\s*(?:else|elseif)\b/
|
|
23
|
+
|
|
24
|
+
DEDENT_TRIGGERS = {
|
|
25
|
+
"d" => /\A(\s*)end\z/,
|
|
26
|
+
"l" => /\A(\s*)until\z/,
|
|
27
|
+
"e" => /\A(\s*)(?:else|elseif)\z/
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
module_function
|
|
31
|
+
|
|
32
|
+
def calculate_indent(lines, target_row, shiftwidth)
|
|
33
|
+
depth = 0
|
|
34
|
+
(0...target_row).each do |row|
|
|
35
|
+
line = lines[row].to_s.strip
|
|
36
|
+
next if line.empty? || line.start_with?("--")
|
|
37
|
+
|
|
38
|
+
depth += 1 if line.match?(/\b(?:function|if|for|while|repeat|do)\b/) && !line.match?(/\bend\b/)
|
|
39
|
+
depth -= 1 if line.match?(/\A(?:end|until)\b/)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
target_line = lines[target_row].to_s.strip
|
|
43
|
+
depth -= 1 if target_line.match?(INDENT_CLOSE_RE) || target_line.match?(INDENT_MID_RE)
|
|
44
|
+
depth = 0 if depth < 0
|
|
45
|
+
depth * shiftwidth
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def indent_trigger?(line)
|
|
49
|
+
stripped = line.to_s.rstrip
|
|
50
|
+
stripped.match?(/\b(?:function|if|for|while|repeat|do|then|else)\b/)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def dedent_trigger(char)
|
|
54
|
+
DEDENT_TRIGGERS[char]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def color_columns(text)
|
|
58
|
+
cols = {}
|
|
59
|
+
Highlighter.apply_regex(cols, text, LONG_STRING_RE, Highlighter::STRING_COLOR)
|
|
60
|
+
Highlighter.apply_regex(cols, text, STRING_DOUBLE_RE, Highlighter::STRING_COLOR)
|
|
61
|
+
Highlighter.apply_regex(cols, text, STRING_SINGLE_RE, Highlighter::STRING_COLOR)
|
|
62
|
+
Highlighter.apply_regex(cols, text, KEYWORD_RE, Highlighter::KEYWORD_COLOR)
|
|
63
|
+
Highlighter.apply_regex(cols, text, BUILTIN_RE, "\e[35m")
|
|
64
|
+
Highlighter.apply_regex(cols, text, NUMBER_RE, Highlighter::NUMBER_COLOR)
|
|
65
|
+
Highlighter.apply_regex(cols, text, BLOCK_COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
|
|
66
|
+
Highlighter.apply_regex(cols, text, LINE_COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
|
|
67
|
+
cols
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
Registry.register("lua", mod: Lua,
|
|
72
|
+
extensions: %w[.lua],
|
|
73
|
+
shebangs: ["lua", /\Alua\d*\z/],
|
|
74
|
+
runprg: "lua %")
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuVim
|
|
4
|
+
module Lang
|
|
5
|
+
module Makefile
|
|
6
|
+
TARGET_RE = /\A[\w.\-\/]+\s*:/
|
|
7
|
+
VARIABLE_DEF_RE = /\A[\w.\-]+\s*[?:+]?=/
|
|
8
|
+
VARIABLE_REF_RE = /\$[({][\w.\-]+[)}]|\$[A-Za-z@<*^?%]/
|
|
9
|
+
DIRECTIVE_RE = /\A\s*(?:ifeq|ifneq|ifdef|ifndef|else|endif|define|endef|include|-include|sinclude|override|export|unexport|vpath)\b/
|
|
10
|
+
FUNCTION_RE = /\$\((?:subst|patsubst|strip|findstring|filter|filter-out|sort|word|wordlist|words|firstword|lastword|dir|notdir|suffix|basename|addsuffix|addprefix|join|wildcard|realpath|abspath|foreach|if|or|and|call|eval|file|value|error|warning|info|shell|origin|flavor|guile)\b/
|
|
11
|
+
COMMENT_RE = /#.*/
|
|
12
|
+
STRING_DOUBLE_RE = /"(?:\\.|[^"\\])*"/
|
|
13
|
+
STRING_SINGLE_RE = /'(?:\\.|[^'\\])*'/
|
|
14
|
+
AUTO_VAR_RE = /\$[@<*^?%]/
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
def color_columns(text)
|
|
19
|
+
cols = {}
|
|
20
|
+
Highlighter.apply_regex(cols, text, TARGET_RE, "\e[1;33m")
|
|
21
|
+
Highlighter.apply_regex(cols, text, VARIABLE_DEF_RE, Highlighter::KEYWORD_COLOR)
|
|
22
|
+
Highlighter.apply_regex(cols, text, DIRECTIVE_RE, Highlighter::KEYWORD_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, FUNCTION_RE, "\e[35m")
|
|
26
|
+
Highlighter.apply_regex(cols, text, VARIABLE_REF_RE, Highlighter::VARIABLE_COLOR)
|
|
27
|
+
Highlighter.apply_regex(cols, text, AUTO_VAR_RE, Highlighter::VARIABLE_COLOR)
|
|
28
|
+
Highlighter.apply_regex(cols, text, COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
|
|
29
|
+
cols
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
Registry.register("make", mod: Makefile,
|
|
34
|
+
basenames: %w[Makefile GNUmakefile makefile Justfile])
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/ruvim/lang/markdown.rb
CHANGED
|
@@ -80,10 +80,6 @@ module RuVim
|
|
|
80
80
|
|
|
81
81
|
module_function
|
|
82
82
|
|
|
83
|
-
def heading?(line)
|
|
84
|
-
line.to_s.match?(HEADING_RE)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
83
|
def heading_level(line)
|
|
88
84
|
m = line.to_s.match(HEADING_RE)
|
|
89
85
|
m ? m[2].length : 0
|
|
@@ -166,5 +162,8 @@ module RuVim
|
|
|
166
162
|
text.length.times { |i| cols[i] = color }
|
|
167
163
|
end
|
|
168
164
|
end
|
|
165
|
+
|
|
166
|
+
Registry.register("markdown", mod: Markdown,
|
|
167
|
+
extensions: %w[.md])
|
|
169
168
|
end
|
|
170
169
|
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuVim
|
|
4
|
+
module Lang
|
|
5
|
+
module Ocaml
|
|
6
|
+
KEYWORDS = %w[
|
|
7
|
+
and as assert begin class constraint do done downto else end
|
|
8
|
+
exception external false for fun function functor if in include
|
|
9
|
+
inherit initializer lazy let match method mod module mutable
|
|
10
|
+
new nonrec object of open or private rec sig struct then to
|
|
11
|
+
true try type val virtual when while with
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
KEYWORD_RE = /\b(?:#{KEYWORDS.join("|")})\b/
|
|
15
|
+
STRING_DOUBLE_RE = /"(?:\\.|[^"\\])*"/
|
|
16
|
+
CHAR_RE = /'(?:\\.|[^'\\])'/
|
|
17
|
+
NUMBER_RE = /\b(?:0[xXoObB][\da-fA-F_]+|\d[\d_]*(?:\.[\d_]+)?(?:[eE][+-]?\d+)?)\b/
|
|
18
|
+
BLOCK_COMMENT_RE = /\(\*.*?\*\)/
|
|
19
|
+
MODULE_RE = /\b[A-Z]\w*(?:\.[A-Z]\w*)*/
|
|
20
|
+
VARIANT_RE = /\b[A-Z]\w*\b/
|
|
21
|
+
TYPE_VAR_RE = /'\w+/
|
|
22
|
+
OPERATOR_RE = /->|::|;;|<-|\|>/
|
|
23
|
+
|
|
24
|
+
INDENT_OPEN_RE = /\b(?:struct|sig|begin|do|then|else)\s*$/
|
|
25
|
+
INDENT_CLOSE_RE = /\A\s*(?:end|done)\b/
|
|
26
|
+
|
|
27
|
+
DEDENT_TRIGGERS = {
|
|
28
|
+
"d" => /\A(\s*)end\z/,
|
|
29
|
+
"e" => /\A(\s*)(?:done|else)\z/
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
module_function
|
|
33
|
+
|
|
34
|
+
def calculate_indent(lines, target_row, shiftwidth)
|
|
35
|
+
depth = 0
|
|
36
|
+
(0...target_row).each do |row|
|
|
37
|
+
line = lines[row].to_s.strip
|
|
38
|
+
next if line.empty?
|
|
39
|
+
|
|
40
|
+
depth += 1 if line.match?(/\b(?:struct|sig|begin|do)\b/)
|
|
41
|
+
depth -= 1 if line.match?(/\A(?:end|done)\b/)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
target_line = lines[target_row].to_s.strip
|
|
45
|
+
depth -= 1 if target_line.match?(INDENT_CLOSE_RE)
|
|
46
|
+
depth = 0 if depth < 0
|
|
47
|
+
depth * shiftwidth
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def indent_trigger?(line)
|
|
51
|
+
line.to_s.rstrip.match?(INDENT_OPEN_RE)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def dedent_trigger(char)
|
|
55
|
+
DEDENT_TRIGGERS[char]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def color_columns(text)
|
|
59
|
+
cols = {}
|
|
60
|
+
Highlighter.apply_regex(cols, text, CHAR_RE, Highlighter::STRING_COLOR)
|
|
61
|
+
Highlighter.apply_regex(cols, text, STRING_DOUBLE_RE, Highlighter::STRING_COLOR)
|
|
62
|
+
Highlighter.apply_regex(cols, text, KEYWORD_RE, Highlighter::KEYWORD_COLOR)
|
|
63
|
+
Highlighter.apply_regex(cols, text, TYPE_VAR_RE, Highlighter::VARIABLE_COLOR)
|
|
64
|
+
Highlighter.apply_regex(cols, text, MODULE_RE, Highlighter::CONSTANT_COLOR)
|
|
65
|
+
Highlighter.apply_regex(cols, text, NUMBER_RE, Highlighter::NUMBER_COLOR)
|
|
66
|
+
Highlighter.apply_regex(cols, text, OPERATOR_RE, Highlighter::KEYWORD_COLOR)
|
|
67
|
+
Highlighter.apply_regex(cols, text, BLOCK_COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
|
|
68
|
+
cols
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
Registry.register("ocaml", mod: Ocaml,
|
|
73
|
+
extensions: %w[.ml .mli],
|
|
74
|
+
shebangs: %w[ocaml],
|
|
75
|
+
runprg: "ocaml %")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuVim
|
|
4
|
+
module Lang
|
|
5
|
+
module Perl
|
|
6
|
+
KEYWORDS = %w[
|
|
7
|
+
my our local sub return if elsif else unless while until for
|
|
8
|
+
foreach do given when default last next redo goto
|
|
9
|
+
use require package no BEGIN END
|
|
10
|
+
die warn print say chomp chop push pop shift unshift
|
|
11
|
+
open close read write seek tell
|
|
12
|
+
map grep sort reverse join split
|
|
13
|
+
defined undef delete exists ref bless
|
|
14
|
+
eval try catch finally
|
|
15
|
+
and or not xor
|
|
16
|
+
eq ne lt gt le ge cmp
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
KEYWORD_RE = /\b(?:#{KEYWORDS.join("|")})\b/
|
|
20
|
+
STRING_DOUBLE_RE = /"(?:\\.|[^"\\])*"/
|
|
21
|
+
STRING_SINGLE_RE = /'(?:\\.|[^'\\])*'/
|
|
22
|
+
REGEX_RE = %r{(?<=[=~(,;\s])(?:m|s|tr|y)?/(?:\\.|[^/\\])*/[gimxsecpodualn]*}
|
|
23
|
+
QW_RE = /\bqw\s*[({<\/|].*?[)}>\/|]/
|
|
24
|
+
SCALAR_RE = /\$[\w]+/
|
|
25
|
+
ARRAY_RE = /@[\w]+/
|
|
26
|
+
HASH_RE = /%[\w]+/
|
|
27
|
+
SPECIAL_VAR_RE = /\$[_!@&`'+\\\/\-\[\]]/
|
|
28
|
+
NUMBER_RE = /\b(?:0[xXoObB][\da-fA-F_]+|\d[\d_]*(?:\.[\d_]+)?(?:[eE][+-]?\d+)?)\b/
|
|
29
|
+
COMMENT_RE = /#.*/
|
|
30
|
+
POD_RE = /\A=[a-zA-Z]\w*/
|
|
31
|
+
|
|
32
|
+
INDENT_OPEN_RE = /\{\s*(?:#.*)?$/
|
|
33
|
+
INDENT_CLOSE_RE = /\A\s*\}/
|
|
34
|
+
|
|
35
|
+
DEDENT_TRIGGERS = {
|
|
36
|
+
"}" => /\A(\s*)\}/
|
|
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
|
|
45
|
+
line.each_char do |ch|
|
|
46
|
+
case ch
|
|
47
|
+
when "{" then depth += 1
|
|
48
|
+
when "}" then depth -= 1
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
target_line = lines[target_row].to_s.lstrip
|
|
54
|
+
depth -= 1 if target_line.match?(INDENT_CLOSE_RE)
|
|
55
|
+
depth = 0 if depth < 0
|
|
56
|
+
depth * shiftwidth
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def indent_trigger?(line)
|
|
60
|
+
line.to_s.rstrip.match?(INDENT_OPEN_RE)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def dedent_trigger(char)
|
|
64
|
+
DEDENT_TRIGGERS[char]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def color_columns(text)
|
|
68
|
+
cols = {}
|
|
69
|
+
# POD documentation
|
|
70
|
+
if text.match?(POD_RE)
|
|
71
|
+
text.length.times { |i| cols[i] = Highlighter::COMMENT_COLOR }
|
|
72
|
+
return cols
|
|
73
|
+
end
|
|
74
|
+
Highlighter.apply_regex(cols, text, STRING_DOUBLE_RE, Highlighter::STRING_COLOR)
|
|
75
|
+
Highlighter.apply_regex(cols, text, STRING_SINGLE_RE, Highlighter::STRING_COLOR)
|
|
76
|
+
Highlighter.apply_regex(cols, text, KEYWORD_RE, Highlighter::KEYWORD_COLOR)
|
|
77
|
+
Highlighter.apply_regex(cols, text, SCALAR_RE, Highlighter::VARIABLE_COLOR)
|
|
78
|
+
Highlighter.apply_regex(cols, text, ARRAY_RE, "\e[35m")
|
|
79
|
+
Highlighter.apply_regex(cols, text, HASH_RE, Highlighter::CONSTANT_COLOR)
|
|
80
|
+
Highlighter.apply_regex(cols, text, NUMBER_RE, Highlighter::NUMBER_COLOR)
|
|
81
|
+
Highlighter.apply_regex(cols, text, COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
|
|
82
|
+
cols
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
Registry.register("perl", mod: Perl,
|
|
87
|
+
extensions: %w[.pl .pm .t],
|
|
88
|
+
shebangs: [/\Aperl(?:\d+(?:\.\d+)*)?\z/],
|
|
89
|
+
runprg: "perl %")
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuVim
|
|
4
|
+
module Lang
|
|
5
|
+
module Python
|
|
6
|
+
KEYWORDS = %w[
|
|
7
|
+
and as assert async await break class continue def del elif else
|
|
8
|
+
except finally for from global if import in is lambda nonlocal
|
|
9
|
+
not or pass raise return try while with yield
|
|
10
|
+
True False None
|
|
11
|
+
].freeze
|
|
12
|
+
|
|
13
|
+
KEYWORD_RE = /\b(?:#{KEYWORDS.join("|")})\b/
|
|
14
|
+
STRING_TRIPLE_DQ_RE = /""".*?"""/m
|
|
15
|
+
STRING_TRIPLE_SQ_RE = /'''.*?'''/m
|
|
16
|
+
STRING_DOUBLE_RE = /"(?:\\.|[^"\\])*"/
|
|
17
|
+
STRING_SINGLE_RE = /'(?:\\.|[^'\\])*'/
|
|
18
|
+
FSTRING_PREFIX_RE = /[fFrRbBuU]{1,2}(?=["'])/
|
|
19
|
+
NUMBER_RE = /\b(?:0[xXoObB][\da-fA-F_]+|\d[\d_]*(?:\.[\d_]+)?(?:[eE][+-]?\d+)?j?)\b/
|
|
20
|
+
DECORATOR_RE = /@[\w.]+/
|
|
21
|
+
COMMENT_RE = /#.*/
|
|
22
|
+
CONSTANT_RE = /\b[A-Z][A-Z0-9_]{1,}\b/
|
|
23
|
+
BUILTIN_RE = /\b(?:print|len|range|type|int|str|float|list|dict|tuple|set|bool|open|input|map|filter|zip|enumerate|sorted|reversed|super|isinstance|issubclass|hasattr|getattr|setattr|delattr|property|staticmethod|classmethod|__\w+__)\b/
|
|
24
|
+
|
|
25
|
+
INDENT_OPEN_RE = /:\s*(?:#.*)?$/
|
|
26
|
+
INDENT_CLOSE_RE = /\A\s*(?:return|break|continue|pass|raise)\b/
|
|
27
|
+
|
|
28
|
+
DEDENT_TRIGGERS = {
|
|
29
|
+
"s" => /\A(\s*)(?:else|class)\z/,
|
|
30
|
+
":" => /\A(\s*)(?:else|elif|except|finally)\s*.*:\z/,
|
|
31
|
+
"f" => /\A(\s*)elif\z/
|
|
32
|
+
}.freeze
|
|
33
|
+
|
|
34
|
+
module_function
|
|
35
|
+
|
|
36
|
+
def calculate_indent(lines, target_row, shiftwidth)
|
|
37
|
+
return 0 if target_row == 0
|
|
38
|
+
|
|
39
|
+
prev_row = target_row - 1
|
|
40
|
+
prev_row -= 1 while prev_row > 0 && lines[prev_row].to_s.strip.empty?
|
|
41
|
+
prev = lines[prev_row].to_s
|
|
42
|
+
prev_indent = prev[/\A */].size
|
|
43
|
+
|
|
44
|
+
if prev.rstrip.match?(INDENT_OPEN_RE)
|
|
45
|
+
return prev_indent + shiftwidth
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
target_line = lines[target_row].to_s.strip
|
|
49
|
+
if target_line.match?(/\A(?:else|elif|except|finally)\b/)
|
|
50
|
+
depth = prev_indent - shiftwidth
|
|
51
|
+
return depth < 0 ? 0 : depth
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
prev_indent
|
|
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, FSTRING_PREFIX_RE, Highlighter::STRING_COLOR)
|
|
70
|
+
Highlighter.apply_regex(cols, text, KEYWORD_RE, Highlighter::KEYWORD_COLOR)
|
|
71
|
+
Highlighter.apply_regex(cols, text, BUILTIN_RE, "\e[35m")
|
|
72
|
+
Highlighter.apply_regex(cols, text, DECORATOR_RE, "\e[35m")
|
|
73
|
+
Highlighter.apply_regex(cols, text, NUMBER_RE, Highlighter::NUMBER_COLOR)
|
|
74
|
+
Highlighter.apply_regex(cols, text, CONSTANT_RE, Highlighter::CONSTANT_COLOR)
|
|
75
|
+
Highlighter.apply_regex(cols, text, COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
|
|
76
|
+
cols
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
Registry.register("python", mod: Python,
|
|
81
|
+
extensions: %w[.py],
|
|
82
|
+
shebangs: [/\Apython(?:\d+(?:\.\d+)*)?\z/],
|
|
83
|
+
runprg: "python3 %")
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuVim
|
|
4
|
+
module Lang
|
|
5
|
+
# Central registry for language modules.
|
|
6
|
+
# Each lang module registers itself at load time via Lang.register.
|
|
7
|
+
module Registry
|
|
8
|
+
@entries = {}
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
# Register a language module.
|
|
12
|
+
#
|
|
13
|
+
# @param filetype [String] primary filetype name (e.g. "ruby")
|
|
14
|
+
# @param mod [Module] the lang module (must respond to :color_columns)
|
|
15
|
+
# @param extensions [Array<String>] file extensions including dot (e.g. [".rb", ".rake"])
|
|
16
|
+
# @param basenames [Array<String>] exact basenames (e.g. ["Makefile"])
|
|
17
|
+
# @param basename_prefix [String, nil] prefix match for basename (e.g. "Dockerfile")
|
|
18
|
+
# @param shebangs [Array<String, Regexp>] shebang command matchers
|
|
19
|
+
# @param aliases [Array<String>] additional filetype names that map to the same module
|
|
20
|
+
# @param runprg [String, nil] default run command (% = filename)
|
|
21
|
+
def register(filetype, mod:, extensions: [], basenames: [], basename_prefix: nil,
|
|
22
|
+
shebangs: [], aliases: [], runprg: nil)
|
|
23
|
+
entry = {
|
|
24
|
+
filetype: filetype,
|
|
25
|
+
mod: mod,
|
|
26
|
+
extensions: extensions,
|
|
27
|
+
basenames: basenames,
|
|
28
|
+
basename_prefix: basename_prefix,
|
|
29
|
+
shebangs: shebangs,
|
|
30
|
+
aliases: aliases,
|
|
31
|
+
runprg: runprg
|
|
32
|
+
}.freeze
|
|
33
|
+
@entries[filetype] = entry
|
|
34
|
+
aliases.each { |a| @entries[a] = entry }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Look up a lang module by filetype string.
|
|
38
|
+
# Returns the module or Lang::Base if not found.
|
|
39
|
+
def resolve_module(ft)
|
|
40
|
+
entry = @entries[ft]
|
|
41
|
+
entry ? entry[:mod] : Lang::Base
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Look up runprg by filetype string. Returns nil if not registered.
|
|
45
|
+
def runprg_for(ft)
|
|
46
|
+
entry = @entries[ft]
|
|
47
|
+
entry&.[](:runprg)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Detect filetype from file extension.
|
|
51
|
+
# Returns filetype string or nil.
|
|
52
|
+
def detect_by_extension(ext)
|
|
53
|
+
ext = ext.downcase
|
|
54
|
+
@entries.each_value do |entry|
|
|
55
|
+
return entry[:filetype] if entry[:extensions].include?(ext)
|
|
56
|
+
end
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Detect filetype from exact basename.
|
|
61
|
+
# Returns filetype string or nil.
|
|
62
|
+
def detect_by_basename(basename)
|
|
63
|
+
@entries.each_value do |entry|
|
|
64
|
+
return entry[:filetype] if entry[:basenames].include?(basename)
|
|
65
|
+
end
|
|
66
|
+
# Prefix match
|
|
67
|
+
@entries.each_value do |entry|
|
|
68
|
+
prefix = entry[:basename_prefix]
|
|
69
|
+
return entry[:filetype] if prefix && basename.start_with?(prefix)
|
|
70
|
+
end
|
|
71
|
+
nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Detect filetype from shebang command name.
|
|
75
|
+
# Returns filetype string or nil.
|
|
76
|
+
def detect_by_shebang(cmd)
|
|
77
|
+
@entries.each_value do |entry|
|
|
78
|
+
entry[:shebangs].each do |matcher|
|
|
79
|
+
if matcher.is_a?(Regexp)
|
|
80
|
+
return entry[:filetype] if matcher.match?(cmd)
|
|
81
|
+
elsif matcher.to_s == cmd
|
|
82
|
+
return entry[:filetype]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Returns true if the filetype has a color_columns method.
|
|
90
|
+
def highlight?(ft)
|
|
91
|
+
entry = @entries[ft]
|
|
92
|
+
entry && entry[:mod].respond_to?(:color_columns)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Look up entry by filetype. Returns nil if not found.
|
|
96
|
+
def [](ft)
|
|
97
|
+
@entries[ft]
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
data/lib/ruvim/lang/ruby.rb
CHANGED
|
@@ -189,6 +189,7 @@ module RuVim
|
|
|
189
189
|
|
|
190
190
|
def on_save(ctx, path)
|
|
191
191
|
return unless path && File.exist?(path)
|
|
192
|
+
return if ctx.editor.respond_to?(:restricted_mode?) && ctx.editor.restricted_mode?
|
|
192
193
|
output, status = Open3.capture2e("ruby", "-wc", path)
|
|
193
194
|
message = output.sub(/^Syntax OK\n?\z/m, "").strip
|
|
194
195
|
|
|
@@ -232,5 +233,11 @@ module RuVim
|
|
|
232
233
|
cols
|
|
233
234
|
end
|
|
234
235
|
end
|
|
236
|
+
|
|
237
|
+
Registry.register("ruby", mod: Ruby,
|
|
238
|
+
extensions: %w[.rb .rake .ru],
|
|
239
|
+
basenames: %w[Gemfile Rakefile Guardfile Vagrantfile],
|
|
240
|
+
shebangs: [/\Aruby(?:\d+(?:\.\d+)*)?\z/],
|
|
241
|
+
runprg: "ruby -w %")
|
|
235
242
|
end
|
|
236
243
|
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuVim
|
|
4
|
+
module Lang
|
|
5
|
+
module Rust
|
|
6
|
+
KEYWORDS = %w[
|
|
7
|
+
as async await break const continue crate dyn else enum extern
|
|
8
|
+
false fn for if impl in let loop match mod move mut pub ref
|
|
9
|
+
return self Self static struct super trait true type unsafe use
|
|
10
|
+
where while
|
|
11
|
+
macro_rules
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
TYPES = %w[
|
|
15
|
+
bool char str
|
|
16
|
+
i8 i16 i32 i64 i128 isize
|
|
17
|
+
u8 u16 u32 u64 u128 usize
|
|
18
|
+
f32 f64
|
|
19
|
+
String Vec Box Rc Arc Option Result HashMap HashSet
|
|
20
|
+
Vec! vec!
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
23
|
+
ALL_KEYWORDS = (KEYWORDS + TYPES).uniq.freeze
|
|
24
|
+
|
|
25
|
+
KEYWORD_RE = /\b(?:#{ALL_KEYWORDS.map { |k| Regexp.escape(k) }.join("|")})\b/
|
|
26
|
+
STRING_DOUBLE_RE = /"(?:\\.|[^"\\])*"/
|
|
27
|
+
STRING_RAW_RE = /r#*"[^"]*"#*/
|
|
28
|
+
CHAR_RE = /'(?:\\.|[^'\\])'/
|
|
29
|
+
LIFETIME_RE = /'[a-z_]\w*/
|
|
30
|
+
NUMBER_RE = /\b(?:0[xXoObB][\da-fA-F_]+|\d[\d_]*(?:\.[\d_]+)?(?:[eE][+-]?\d+)?(?:_?[iu](?:8|16|32|64|128|size)|_?f(?:32|64))?)\b/
|
|
31
|
+
LINE_COMMENT_RE = %r{//.*}
|
|
32
|
+
BLOCK_COMMENT_RE = %r{/\*.*?\*/}
|
|
33
|
+
ATTRIBUTE_RE = /#!?\[[\w:(,)\s"]*\]/
|
|
34
|
+
MACRO_RE = /\b\w+!/
|
|
35
|
+
CONSTANT_RE = /\b[A-Z][A-Z0-9_]{1,}\b/
|
|
36
|
+
|
|
37
|
+
INDENT_OPEN_RE = /[{(\[]\s*(?:\/\/.*)?$/
|
|
38
|
+
INDENT_CLOSE_RE = /\A\s*[}\])]/
|
|
39
|
+
|
|
40
|
+
DEDENT_TRIGGERS = {
|
|
41
|
+
"}" => /\A(\s*)\}/,
|
|
42
|
+
"]" => /\A(\s*)\]/,
|
|
43
|
+
")" => /\A(\s*)\)/
|
|
44
|
+
}.freeze
|
|
45
|
+
|
|
46
|
+
module_function
|
|
47
|
+
|
|
48
|
+
def calculate_indent(lines, target_row, shiftwidth)
|
|
49
|
+
depth = 0
|
|
50
|
+
(0...target_row).each do |row|
|
|
51
|
+
line = lines[row].to_s
|
|
52
|
+
line.each_char do |ch|
|
|
53
|
+
case ch
|
|
54
|
+
when "{", "[", "(" then depth += 1
|
|
55
|
+
when "}", "]", ")" then depth -= 1
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
target_line = lines[target_row].to_s.lstrip
|
|
61
|
+
depth -= 1 if target_line.match?(INDENT_CLOSE_RE)
|
|
62
|
+
depth = 0 if depth < 0
|
|
63
|
+
depth * shiftwidth
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def indent_trigger?(line)
|
|
67
|
+
line.to_s.rstrip.match?(INDENT_OPEN_RE)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def dedent_trigger(char)
|
|
71
|
+
DEDENT_TRIGGERS[char]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def color_columns(text)
|
|
75
|
+
cols = {}
|
|
76
|
+
Highlighter.apply_regex(cols, text, CHAR_RE, Highlighter::STRING_COLOR)
|
|
77
|
+
Highlighter.apply_regex(cols, text, STRING_DOUBLE_RE, Highlighter::STRING_COLOR)
|
|
78
|
+
Highlighter.apply_regex(cols, text, STRING_RAW_RE, Highlighter::STRING_COLOR)
|
|
79
|
+
Highlighter.apply_regex(cols, text, LIFETIME_RE, "\e[35m")
|
|
80
|
+
Highlighter.apply_regex(cols, text, KEYWORD_RE, Highlighter::KEYWORD_COLOR)
|
|
81
|
+
Highlighter.apply_regex(cols, text, MACRO_RE, "\e[35m")
|
|
82
|
+
Highlighter.apply_regex(cols, text, NUMBER_RE, Highlighter::NUMBER_COLOR)
|
|
83
|
+
Highlighter.apply_regex(cols, text, CONSTANT_RE, Highlighter::CONSTANT_COLOR)
|
|
84
|
+
Highlighter.apply_regex(cols, text, ATTRIBUTE_RE, "\e[35m")
|
|
85
|
+
Highlighter.apply_regex(cols, text, BLOCK_COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
|
|
86
|
+
Highlighter.apply_regex(cols, text, LINE_COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
|
|
87
|
+
cols
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
Registry.register("rust", mod: Rust,
|
|
92
|
+
extensions: %w[.rs],
|
|
93
|
+
runprg: "rustc -o /tmp/a.out % && /tmp/a.out")
|
|
94
|
+
end
|
|
95
|
+
end
|