ruvim 0.3.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 +68 -7
- data/README.md +30 -7
- 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 +18 -1
- data/docs/command.md +156 -10
- data/docs/config.md +10 -2
- data/docs/done.md +23 -0
- data/docs/spec.md +162 -25
- data/docs/todo.md +9 -0
- data/docs/tutorial.md +33 -1
- data/docs/vim_diff.md +31 -8
- data/ext/ruvim/extconf.rb +5 -0
- data/ext/ruvim/ruvim_ext.c +519 -0
- data/lib/ruvim/app.rb +246 -2525
- data/lib/ruvim/browser.rb +104 -0
- data/lib/ruvim/buffer.rb +43 -20
- data/lib/ruvim/cli.rb +6 -0
- 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 +74 -80
- data/lib/ruvim/ex_command_registry.rb +3 -1
- data/lib/ruvim/file_watcher.rb +243 -0
- data/lib/ruvim/gh/link.rb +207 -0
- data/lib/ruvim/git/blame.rb +255 -0
- data/lib/ruvim/git/branch.rb +112 -0
- data/lib/ruvim/git/commit.rb +102 -0
- data/lib/ruvim/git/diff.rb +129 -0
- data/lib/ruvim/git/grep.rb +107 -0
- data/lib/ruvim/git/handler.rb +125 -0
- data/lib/ruvim/git/log.rb +41 -0
- data/lib/ruvim/git/status.rb +103 -0
- data/lib/ruvim/global_commands.rb +351 -77
- data/lib/ruvim/highlighter.rb +4 -11
- data/lib/ruvim/input.rb +1 -0
- 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 +43 -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 +40 -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/json_renderer.rb +131 -0
- data/lib/ruvim/rich_view/jsonl_renderer.rb +57 -0
- data/lib/ruvim/rich_view/table_renderer.rb +3 -3
- data/lib/ruvim/rich_view.rb +30 -7
- data/lib/ruvim/screen.rb +135 -84
- 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 +31 -4
- data/test/app_command_test.rb +382 -0
- data/test/app_completion_test.rb +65 -16
- 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 +182 -8
- 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 +77 -0
- data/test/clipboard_test.rb +67 -0
- data/test/command_invocation_test.rb +33 -0
- data/test/command_line_test.rb +118 -0
- data/test/config_dsl_test.rb +134 -0
- data/test/dispatcher_test.rb +74 -4
- data/test/display_width_test.rb +41 -0
- data/test/ex_command_registry_test.rb +106 -0
- data/test/file_watcher_test.rb +197 -0
- data/test/follow_test.rb +198 -0
- data/test/gh_link_test.rb +141 -0
- data/test/git_blame_test.rb +792 -0
- data/test/git_grep_test.rb +64 -0
- data/test/highlighter_test.rb +169 -0
- data/test/indent_test.rb +223 -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 +279 -23
- data/test/run_command_test.rb +307 -0
- data/test/screen_test.rb +68 -5
- data/test/search_option_test.rb +19 -0
- data/test/stream_test.rb +165 -0
- data/test/test_helper.rb +9 -0
- data/test/window_test.rb +59 -0
- metadata +68 -2
|
@@ -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
|
data/lib/ruvim/lang/scheme.rb
CHANGED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuVim
|
|
4
|
+
module Lang
|
|
5
|
+
module Sh
|
|
6
|
+
KEYWORDS = %w[
|
|
7
|
+
if then else elif fi case esac for while until do done
|
|
8
|
+
in function select return exit break continue
|
|
9
|
+
local export readonly declare typeset unset
|
|
10
|
+
source eval exec trap set shopt
|
|
11
|
+
].freeze
|
|
12
|
+
|
|
13
|
+
KEYWORD_RE = /\b(?:#{KEYWORDS.join("|")})\b/
|
|
14
|
+
STRING_SINGLE_RE = /'[^']*'/
|
|
15
|
+
STRING_DOUBLE_RE = /"(?:\\.|[^"\\])*"/
|
|
16
|
+
VARIABLE_RE = /\$\{?[A-Za-z_]\w*\}?|\$[0-9@#?!\-*$]/
|
|
17
|
+
NUMBER_RE = /\b\d+\b/
|
|
18
|
+
COMMENT_RE = /#.*/
|
|
19
|
+
SHEBANG_RE = /\A#!.*/
|
|
20
|
+
OPERATOR_RE = /(?:\|\||&&|;;|<<|>>)/
|
|
21
|
+
|
|
22
|
+
INDENT_OPEN_RE = /\b(?:then|else|do)\s*$/
|
|
23
|
+
INDENT_CLOSE_RE = /\A\s*(?:fi|done|esac|else|elif)\b/
|
|
24
|
+
|
|
25
|
+
DEDENT_TRIGGERS = {
|
|
26
|
+
"i" => /\A(\s*)fi\z/,
|
|
27
|
+
"e" => /\A(\s*)(?:done|else)\z/,
|
|
28
|
+
"c" => /\A(\s*)esac\z/,
|
|
29
|
+
"f" => /\A(\s*)elif\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? || line.start_with?("#")
|
|
39
|
+
|
|
40
|
+
depth += 1 if line.match?(/\b(?:then|else|elif|do)\s*$/) || line.match?(/\{\s*$/)
|
|
41
|
+
depth -= 1 if line.match?(/\A(?:fi|done|esac)\b/) || line.match?(/\A\}/)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
target_line = lines[target_row].to_s.strip
|
|
45
|
+
depth -= 1 if target_line.match?(/\A(?:fi|done|esac|else|elif)\b/) || target_line.match?(/\A\}/)
|
|
46
|
+
depth = 0 if depth < 0
|
|
47
|
+
depth * shiftwidth
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def indent_trigger?(line)
|
|
51
|
+
stripped = line.to_s.rstrip
|
|
52
|
+
stripped.match?(/\b(?:then|else|elif|do)\s*$/) || stripped.match?(/\{\s*$/)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def dedent_trigger(char)
|
|
56
|
+
DEDENT_TRIGGERS[char]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def color_columns(text)
|
|
60
|
+
cols = {}
|
|
61
|
+
Highlighter.apply_regex(cols, text, STRING_SINGLE_RE, Highlighter::STRING_COLOR)
|
|
62
|
+
Highlighter.apply_regex(cols, text, STRING_DOUBLE_RE, Highlighter::STRING_COLOR)
|
|
63
|
+
Highlighter.apply_regex(cols, text, KEYWORD_RE, Highlighter::KEYWORD_COLOR)
|
|
64
|
+
Highlighter.apply_regex(cols, text, VARIABLE_RE, Highlighter::VARIABLE_COLOR)
|
|
65
|
+
Highlighter.apply_regex(cols, text, NUMBER_RE, Highlighter::NUMBER_COLOR)
|
|
66
|
+
Highlighter.apply_regex(cols, text, COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
|
|
67
|
+
cols
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
Registry.register("sh", mod: Sh,
|
|
72
|
+
extensions: %w[.sh .bash .zsh],
|
|
73
|
+
shebangs: %w[bash sh zsh ksh dash],
|
|
74
|
+
runprg: "bash %")
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuVim
|
|
4
|
+
module Lang
|
|
5
|
+
module Sql
|
|
6
|
+
KEYWORDS = %w[
|
|
7
|
+
SELECT FROM WHERE AND OR NOT IN IS NULL LIKE BETWEEN EXISTS
|
|
8
|
+
INSERT INTO VALUES UPDATE SET DELETE CREATE TABLE ALTER DROP
|
|
9
|
+
INDEX VIEW TRIGGER FUNCTION PROCEDURE
|
|
10
|
+
JOIN INNER LEFT RIGHT OUTER FULL CROSS ON USING
|
|
11
|
+
GROUP BY ORDER ASC DESC HAVING LIMIT OFFSET DISTINCT
|
|
12
|
+
UNION ALL INTERSECT EXCEPT AS CASE WHEN THEN ELSE END
|
|
13
|
+
BEGIN COMMIT ROLLBACK TRANSACTION SAVEPOINT
|
|
14
|
+
PRIMARY KEY FOREIGN REFERENCES UNIQUE CHECK DEFAULT CONSTRAINT
|
|
15
|
+
IF ELSE ELSEIF WHILE LOOP FOR RETURN DECLARE
|
|
16
|
+
INT INTEGER BIGINT SMALLINT TINYINT
|
|
17
|
+
FLOAT DOUBLE DECIMAL NUMERIC REAL
|
|
18
|
+
CHAR VARCHAR TEXT BLOB CLOB BOOLEAN DATE TIME TIMESTAMP
|
|
19
|
+
SERIAL AUTO_INCREMENT IDENTITY
|
|
20
|
+
CASCADE RESTRICT SET GRANT REVOKE WITH RECURSIVE
|
|
21
|
+
COUNT SUM AVG MIN MAX COALESCE NULLIF CAST CONVERT
|
|
22
|
+
EXPLAIN ANALYZE VACUUM REINDEX CLUSTER TRUNCATE
|
|
23
|
+
TRUE FALSE
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
26
|
+
KEYWORD_RE = /\b(?:#{KEYWORDS.join("|")})\b/i
|
|
27
|
+
STRING_SINGLE_RE = /'(?:''|[^'])*'/
|
|
28
|
+
STRING_DOUBLE_RE = /"(?:""|[^"])*"/
|
|
29
|
+
NUMBER_RE = /\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b/
|
|
30
|
+
LINE_COMMENT_RE = /--.*/
|
|
31
|
+
BLOCK_COMMENT_RE = %r{/\*.*?\*/}
|
|
32
|
+
PARAMETER_RE = /[:@$]\w+/
|
|
33
|
+
|
|
34
|
+
module_function
|
|
35
|
+
|
|
36
|
+
def color_columns(text)
|
|
37
|
+
cols = {}
|
|
38
|
+
Highlighter.apply_regex(cols, text, STRING_SINGLE_RE, Highlighter::STRING_COLOR)
|
|
39
|
+
Highlighter.apply_regex(cols, text, STRING_DOUBLE_RE, Highlighter::STRING_COLOR)
|
|
40
|
+
Highlighter.apply_regex(cols, text, KEYWORD_RE, Highlighter::KEYWORD_COLOR)
|
|
41
|
+
Highlighter.apply_regex(cols, text, NUMBER_RE, Highlighter::NUMBER_COLOR)
|
|
42
|
+
Highlighter.apply_regex(cols, text, PARAMETER_RE, Highlighter::VARIABLE_COLOR)
|
|
43
|
+
Highlighter.apply_regex(cols, text, BLOCK_COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
|
|
44
|
+
Highlighter.apply_regex(cols, text, LINE_COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
|
|
45
|
+
cols
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
Registry.register("sql", mod: Sql,
|
|
50
|
+
extensions: %w[.sql])
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuVim
|
|
4
|
+
module Lang
|
|
5
|
+
module Toml
|
|
6
|
+
KEYWORD_RE = /\b(?:true|false)\b/
|
|
7
|
+
TABLE_RE = /\A\s*\[[\w.\-"]+\]/
|
|
8
|
+
ARRAY_TABLE_RE = /\A\s*\[\[[\w.\-"]+\]\]/
|
|
9
|
+
KEY_RE = /\A\s*[\w.\-]+\s*(?==)/
|
|
10
|
+
STRING_DOUBLE_RE = /"(?:\\.|[^"\\])*"/
|
|
11
|
+
STRING_SINGLE_RE = /'[^']*'/
|
|
12
|
+
NUMBER_RE = /(?<=[= ])[+-]?(?:0[xXoObB][\da-fA-F_]+|\d[\d_]*(?:\.[\d_]+)?(?:[eE][+-]?\d+)?)\b/
|
|
13
|
+
DATETIME_RE = /\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?)?/
|
|
14
|
+
COMMENT_RE = /#.*/
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
def color_columns(text)
|
|
19
|
+
cols = {}
|
|
20
|
+
Highlighter.apply_regex(cols, text, ARRAY_TABLE_RE, "\e[1;36m")
|
|
21
|
+
Highlighter.apply_regex(cols, text, TABLE_RE, "\e[1;36m")
|
|
22
|
+
Highlighter.apply_regex(cols, text, KEY_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, KEYWORD_RE, "\e[35m")
|
|
26
|
+
Highlighter.apply_regex(cols, text, DATETIME_RE, Highlighter::CONSTANT_COLOR)
|
|
27
|
+
Highlighter.apply_regex(cols, text, NUMBER_RE, Highlighter::NUMBER_COLOR)
|
|
28
|
+
Highlighter.apply_regex(cols, text, COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
|
|
29
|
+
cols
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
Registry.register("toml", mod: Toml,
|
|
34
|
+
extensions: %w[.toml])
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/ruvim/lang/tsv.rb
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module RuVim
|
|
4
4
|
module Lang
|
|
5
|
+
Registry.register("tsv", mod: Base,
|
|
6
|
+
extensions: %w[.tsv])
|
|
7
|
+
|
|
5
8
|
module Tsv
|
|
6
9
|
module_function
|
|
7
10
|
|
|
@@ -15,5 +18,5 @@ module RuVim
|
|
|
15
18
|
end
|
|
16
19
|
end
|
|
17
20
|
|
|
18
|
-
RichView.register(
|
|
21
|
+
RichView.register(:tsv, RichView::TableRenderer, detector: Lang::Tsv.method(:detect?))
|
|
19
22
|
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuVim
|
|
4
|
+
module Lang
|
|
5
|
+
module Typescript
|
|
6
|
+
TS_KEYWORDS = %w[
|
|
7
|
+
abstract as declare enum implements interface
|
|
8
|
+
module namespace private protected public readonly
|
|
9
|
+
type keyof infer extends
|
|
10
|
+
never unknown any
|
|
11
|
+
override satisfies
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
ALL_KEYWORDS = (Javascript::KEYWORDS + TS_KEYWORDS).uniq.freeze
|
|
15
|
+
|
|
16
|
+
KEYWORD_RE = /\b(?:#{ALL_KEYWORDS.join("|")})\b/
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
def calculate_indent(lines, target_row, shiftwidth)
|
|
21
|
+
Javascript.calculate_indent(lines, target_row, shiftwidth)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def indent_trigger?(line)
|
|
25
|
+
Javascript.indent_trigger?(line)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def dedent_trigger(char)
|
|
29
|
+
Javascript.dedent_trigger(char)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def color_columns(text)
|
|
33
|
+
cols = {}
|
|
34
|
+
Highlighter.apply_regex(cols, text, Javascript::STRING_SINGLE_RE, Highlighter::STRING_COLOR)
|
|
35
|
+
Highlighter.apply_regex(cols, text, Javascript::STRING_DOUBLE_RE, Highlighter::STRING_COLOR)
|
|
36
|
+
Highlighter.apply_regex(cols, text, Javascript::TEMPLATE_RE, Highlighter::STRING_COLOR)
|
|
37
|
+
Highlighter.apply_regex(cols, text, KEYWORD_RE, Highlighter::KEYWORD_COLOR)
|
|
38
|
+
Highlighter.apply_regex(cols, text, Javascript::NUMBER_RE, Highlighter::NUMBER_COLOR)
|
|
39
|
+
Highlighter.apply_regex(cols, text, Javascript::CONSTANT_RE, Highlighter::CONSTANT_COLOR)
|
|
40
|
+
Highlighter.apply_regex(cols, text, Javascript::BLOCK_COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
|
|
41
|
+
Highlighter.apply_regex(cols, text, Javascript::LINE_COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
|
|
42
|
+
cols
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
Registry.register("typescript", mod: Typescript,
|
|
47
|
+
extensions: %w[.ts],
|
|
48
|
+
aliases: %w[typescriptreact],
|
|
49
|
+
runprg: "npx tsx %")
|
|
50
|
+
Registry.register("typescriptreact", mod: Typescript,
|
|
51
|
+
extensions: %w[.tsx])
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuVim
|
|
4
|
+
module Lang
|
|
5
|
+
module Yaml
|
|
6
|
+
KEYWORDS = %w[true false null yes no on off].freeze
|
|
7
|
+
|
|
8
|
+
KEYWORD_RE = /\b(?:#{KEYWORDS.join("|")})\b/
|
|
9
|
+
KEY_RE = /\A\s*[\w.\-\/]+\s*(?=:)/
|
|
10
|
+
TAG_RE = /![\w!.\/\-]*/
|
|
11
|
+
ANCHOR_RE = /[&*]\w+/
|
|
12
|
+
COMMENT_RE = /#.*/
|
|
13
|
+
STRING_SINGLE_RE = /'(?:[^'\\]|\\.)*'/
|
|
14
|
+
STRING_DOUBLE_RE = /"(?:[^"\\]|\\.)*"/
|
|
15
|
+
NUMBER_RE = /(?<=[\s:\-\[,]|^)-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?\s*$/
|
|
16
|
+
BLOCK_SCALAR_RE = /\A\s*[|>][+-]?\s*$/
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
def calculate_indent(lines, target_row, shiftwidth)
|
|
21
|
+
return 0 if target_row == 0
|
|
22
|
+
|
|
23
|
+
prev_row = target_row - 1
|
|
24
|
+
prev_row -= 1 while prev_row > 0 && lines[prev_row].to_s.strip.empty?
|
|
25
|
+
prev = lines[prev_row].to_s
|
|
26
|
+
prev_indent = prev[/\A */].size
|
|
27
|
+
|
|
28
|
+
# Increase indent after mapping key or list with sub-items
|
|
29
|
+
if prev.match?(/:\s*$/) || prev.match?(/[|>][+-]?\s*$/)
|
|
30
|
+
return prev_indent + shiftwidth
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
prev_indent
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def indent_trigger?(line)
|
|
37
|
+
line.to_s.rstrip.match?(/:\s*$/) || line.to_s.rstrip.match?(/[|>][+-]?\s*$/)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def dedent_trigger(_char)
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def color_columns(text)
|
|
45
|
+
cols = {}
|
|
46
|
+
Highlighter.apply_regex(cols, text, STRING_SINGLE_RE, Highlighter::STRING_COLOR)
|
|
47
|
+
Highlighter.apply_regex(cols, text, STRING_DOUBLE_RE, Highlighter::STRING_COLOR)
|
|
48
|
+
Highlighter.apply_regex(cols, text, KEY_RE, Highlighter::KEYWORD_COLOR)
|
|
49
|
+
Highlighter.apply_regex(cols, text, KEYWORD_RE, "\e[35m")
|
|
50
|
+
Highlighter.apply_regex(cols, text, TAG_RE, Highlighter::CONSTANT_COLOR)
|
|
51
|
+
Highlighter.apply_regex(cols, text, ANCHOR_RE, Highlighter::VARIABLE_COLOR)
|
|
52
|
+
Highlighter.apply_regex(cols, text, NUMBER_RE, Highlighter::NUMBER_COLOR)
|
|
53
|
+
Highlighter.apply_regex(cols, text, BLOCK_SCALAR_RE, Highlighter::STRING_COLOR)
|
|
54
|
+
Highlighter.apply_regex(cols, text, COMMENT_RE, Highlighter::COMMENT_COLOR, override: true)
|
|
55
|
+
cols
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
Registry.register("yaml", mod: Yaml,
|
|
60
|
+
extensions: %w[.yml .yaml])
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module RuVim
|
|
6
|
+
module RichView
|
|
7
|
+
module JsonRenderer
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# Signal that this renderer creates a virtual buffer
|
|
11
|
+
# instead of entering rich mode on the same buffer.
|
|
12
|
+
def open_view!(editor)
|
|
13
|
+
buffer = editor.current_buffer
|
|
14
|
+
window = editor.current_window
|
|
15
|
+
text = buffer.lines.join("\n")
|
|
16
|
+
|
|
17
|
+
begin
|
|
18
|
+
parsed = JSON.parse(text)
|
|
19
|
+
rescue JSON::ParserError => e
|
|
20
|
+
editor.echo_error("JSON parse error: #{e.message}")
|
|
21
|
+
return
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Compute cursor's significant char offset before formatting
|
|
25
|
+
cursor_offset = char_offset_for(buffer.lines, window.cursor_y, window.cursor_x)
|
|
26
|
+
sig_count = significant_char_count(text, cursor_offset + 1)
|
|
27
|
+
|
|
28
|
+
formatted = JSON.pretty_generate(parsed)
|
|
29
|
+
lines = formatted.lines(chomp: true)
|
|
30
|
+
|
|
31
|
+
target_line = line_for_significant_count(formatted, sig_count)
|
|
32
|
+
|
|
33
|
+
buf = editor.add_virtual_buffer(
|
|
34
|
+
kind: :json_formatted,
|
|
35
|
+
name: "[JSON Formatted]",
|
|
36
|
+
lines: lines,
|
|
37
|
+
filetype: "json",
|
|
38
|
+
readonly: true,
|
|
39
|
+
modifiable: false
|
|
40
|
+
)
|
|
41
|
+
editor.switch_to_buffer(buf.id)
|
|
42
|
+
RichView.bind_close_keys(editor, buf.id)
|
|
43
|
+
window.cursor_y = [target_line, lines.length - 1].min
|
|
44
|
+
window.cursor_x = 0
|
|
45
|
+
editor.echo("[JSON Formatted] #{lines.length} lines")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Count significant (non-whitespace-outside-strings) characters
|
|
49
|
+
# in text[0...byte_offset].
|
|
50
|
+
def significant_char_count(text, byte_offset)
|
|
51
|
+
in_string = false
|
|
52
|
+
escape = false
|
|
53
|
+
count = 0
|
|
54
|
+
text.each_char.with_index do |ch, i|
|
|
55
|
+
break if i >= byte_offset
|
|
56
|
+
if in_string
|
|
57
|
+
if escape
|
|
58
|
+
escape = false
|
|
59
|
+
elsif ch == "\\"
|
|
60
|
+
escape = true
|
|
61
|
+
elsif ch == '"'
|
|
62
|
+
in_string = false
|
|
63
|
+
end
|
|
64
|
+
count += 1
|
|
65
|
+
else
|
|
66
|
+
case ch
|
|
67
|
+
when '"'
|
|
68
|
+
in_string = true
|
|
69
|
+
count += 1
|
|
70
|
+
when " ", "\n", "\r", "\t"
|
|
71
|
+
# skip whitespace outside strings
|
|
72
|
+
else
|
|
73
|
+
count += 1
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
count
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Find the line number in text where the N-th significant character falls.
|
|
81
|
+
def line_for_significant_count(text, target_count)
|
|
82
|
+
return 0 if target_count <= 0
|
|
83
|
+
|
|
84
|
+
in_string = false
|
|
85
|
+
escape = false
|
|
86
|
+
count = 0
|
|
87
|
+
line = 0
|
|
88
|
+
text.each_char do |ch|
|
|
89
|
+
if ch == "\n" && !in_string
|
|
90
|
+
line += 1
|
|
91
|
+
next
|
|
92
|
+
end
|
|
93
|
+
if in_string
|
|
94
|
+
if escape
|
|
95
|
+
escape = false
|
|
96
|
+
elsif ch == "\\"
|
|
97
|
+
escape = true
|
|
98
|
+
elsif ch == '"'
|
|
99
|
+
in_string = false
|
|
100
|
+
end
|
|
101
|
+
count += 1
|
|
102
|
+
else
|
|
103
|
+
case ch
|
|
104
|
+
when '"'
|
|
105
|
+
in_string = true
|
|
106
|
+
count += 1
|
|
107
|
+
when " ", "\r", "\t"
|
|
108
|
+
# skip
|
|
109
|
+
else
|
|
110
|
+
count += 1
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
return line if count >= target_count
|
|
114
|
+
end
|
|
115
|
+
line
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Compute character offset in joined text from cursor row/col.
|
|
119
|
+
def char_offset_for(lines, row, col)
|
|
120
|
+
offset = 0
|
|
121
|
+
lines.each_with_index do |line, i|
|
|
122
|
+
if i == row
|
|
123
|
+
return offset + [col, line.length].min
|
|
124
|
+
end
|
|
125
|
+
offset += line.length + 1 # +1 for "\n"
|
|
126
|
+
end
|
|
127
|
+
offset
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module RuVim
|
|
6
|
+
module RichView
|
|
7
|
+
module JsonlRenderer
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
SEPARATOR = "---"
|
|
11
|
+
|
|
12
|
+
def open_view!(editor)
|
|
13
|
+
buffer = editor.current_buffer
|
|
14
|
+
window = editor.current_window
|
|
15
|
+
cursor_row = window.cursor_y
|
|
16
|
+
|
|
17
|
+
output_lines = []
|
|
18
|
+
# Map from source line index to starting line in output
|
|
19
|
+
line_map = {}
|
|
20
|
+
|
|
21
|
+
buffer.lines.each_with_index do |raw_line, idx|
|
|
22
|
+
line = raw_line.to_s.strip
|
|
23
|
+
next if line.empty?
|
|
24
|
+
|
|
25
|
+
output_lines << SEPARATOR unless output_lines.empty?
|
|
26
|
+
line_map[idx] = output_lines.length
|
|
27
|
+
|
|
28
|
+
begin
|
|
29
|
+
parsed = JSON.parse(line)
|
|
30
|
+
formatted = JSON.pretty_generate(parsed)
|
|
31
|
+
formatted.each_line(chomp: true) { |l| output_lines << l }
|
|
32
|
+
rescue JSON::ParserError
|
|
33
|
+
output_lines << "// PARSE ERROR: #{raw_line}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
output_lines << "" if output_lines.empty?
|
|
38
|
+
|
|
39
|
+
target_line = line_map[cursor_row] || 0
|
|
40
|
+
|
|
41
|
+
buf = editor.add_virtual_buffer(
|
|
42
|
+
kind: :jsonl_formatted,
|
|
43
|
+
name: "[JSONL Formatted]",
|
|
44
|
+
lines: output_lines,
|
|
45
|
+
filetype: "json",
|
|
46
|
+
readonly: true,
|
|
47
|
+
modifiable: false
|
|
48
|
+
)
|
|
49
|
+
editor.switch_to_buffer(buf.id)
|
|
50
|
+
RichView.bind_close_keys(editor, buf.id)
|
|
51
|
+
window.cursor_y = [target_line, output_lines.length - 1].min
|
|
52
|
+
window.cursor_x = 0
|
|
53
|
+
editor.echo("[JSONL Formatted] #{output_lines.length} lines")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/ruvim/rich_view.rb
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "rich_view/table_renderer"
|
|
4
4
|
require_relative "rich_view/markdown_renderer"
|
|
5
|
+
require_relative "rich_view/json_renderer"
|
|
6
|
+
require_relative "rich_view/jsonl_renderer"
|
|
5
7
|
|
|
6
8
|
module RuVim
|
|
7
9
|
module RichView
|
|
@@ -11,12 +13,12 @@ module RuVim
|
|
|
11
13
|
module_function
|
|
12
14
|
|
|
13
15
|
def register(filetype, renderer, detector: nil)
|
|
14
|
-
@renderers[filetype.
|
|
15
|
-
@detectors << { filetype: filetype.
|
|
16
|
+
@renderers[filetype.to_sym] = renderer
|
|
17
|
+
@detectors << { filetype: filetype.to_sym, detector: detector } if detector
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
def renderer_for(filetype)
|
|
19
|
-
@renderers[filetype.
|
|
21
|
+
@renderers[filetype.to_sym]
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def registered_filetypes
|
|
@@ -24,10 +26,13 @@ module RuVim
|
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
# Detect format from filetype or buffer content.
|
|
27
|
-
# Returns a filetype
|
|
29
|
+
# Returns a filetype symbol (:tsv, :csv, :markdown) or nil.
|
|
28
30
|
def detect_format(buffer)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
raw = buffer.options["filetype"]
|
|
32
|
+
if raw && !raw.to_s.empty?
|
|
33
|
+
ft = raw.to_sym
|
|
34
|
+
return ft if @renderers.key?(ft)
|
|
35
|
+
end
|
|
31
36
|
|
|
32
37
|
# Ask registered detectors
|
|
33
38
|
@detectors.each do |entry|
|
|
@@ -40,12 +45,17 @@ module RuVim
|
|
|
40
45
|
# Enter rich mode on the current buffer (same buffer, no virtual buffer).
|
|
41
46
|
def open!(editor, format: nil)
|
|
42
47
|
buffer = editor.current_buffer
|
|
43
|
-
format
|
|
48
|
+
format = format ? format.to_sym : detect_format(buffer)
|
|
44
49
|
raise RuVim::CommandError, "Cannot detect format for rich view" unless format
|
|
45
50
|
|
|
46
51
|
renderer = @renderers[format]
|
|
47
52
|
raise RuVim::CommandError, "No renderer for format: #{format}" unless renderer
|
|
48
53
|
|
|
54
|
+
if renderer.respond_to?(:open_view!)
|
|
55
|
+
renderer.open_view!(editor)
|
|
56
|
+
return
|
|
57
|
+
end
|
|
58
|
+
|
|
49
59
|
delimiter = renderer.delimiter_for(format)
|
|
50
60
|
editor.enter_rich_mode(format: format, delimiter: delimiter)
|
|
51
61
|
editor.echo("[Rich: #{format}]")
|
|
@@ -89,5 +99,18 @@ module RuVim
|
|
|
89
99
|
|
|
90
100
|
editor.exit_rich_mode
|
|
91
101
|
end
|
|
102
|
+
|
|
103
|
+
# Bind Esc and C-c to close a virtual buffer created by a renderer.
|
|
104
|
+
def bind_close_keys(editor, buffer_id)
|
|
105
|
+
km = editor.keymap_manager
|
|
106
|
+
return unless km
|
|
107
|
+
|
|
108
|
+
km.bind_buffer(buffer_id, "\e", "rich.close_buffer")
|
|
109
|
+
km.bind_buffer(buffer_id, "<C-c>", "rich.close_buffer")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
register(:markdown, MarkdownRenderer)
|
|
113
|
+
register(:json, JsonRenderer)
|
|
114
|
+
register(:jsonl, JsonlRenderer)
|
|
92
115
|
end
|
|
93
116
|
end
|