├── .dockerignore ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .zed └── tasks.json ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── Makefile ├── config.toml ├── img.png ├── langs ├── c │ └── highlights.scm ├── cpp │ └── highlights.scm ├── go │ └── highlights.scm ├── html │ └── highlights.scm ├── java │ └── highlights.scm ├── javascript │ └── highlights.scm ├── json │ └── highlights.scm ├── kotlin │ └── highlights.scm ├── lua │ └── highlights.scm ├── python │ ├── highlights.scm │ └── tests.scm ├── rust │ ├── highlights.scm │ └── tests.scm ├── shell │ └── highlights.scm ├── text │ └── highlights.scm ├── toml │ └── highlights.scm ├── typescript │ └── highlights.scm ├── yaml │ └── highlights.scm └── zig │ └── highlights.scm ├── readme.md ├── red.log ├── red1.log ├── src ├── code.rs ├── config.rs ├── editor.rs ├── lsp.rs ├── main.rs ├── process.rs ├── search.rs ├── selection.rs ├── tests.rs ├── tree.rs └── utils.rs ├── test ├── ptest.py ├── test.c ├── test.cpp ├── test.go ├── test.html ├── test.java ├── test.js ├── test.json ├── test.kt ├── test.lua ├── test.py ├── test.rs ├── test.sh ├── test.ts ├── test.tsx ├── test.zig ├── test │ └── test │ │ └── test.rs ├── testb.js ├── testb.py └── tests.js ├── themes ├── darcula.yml ├── material.yml ├── red.yml └── vesper.yml ├── tmux-last.sh └── tmux.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | target 2 | flamegraph.svg 3 | img*.png 4 | .vscode 5 | .idea 6 | .pytest* 7 | .DS_Store 8 | __pycache__ 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .DS_Store 3 | .idea 4 | .vscode 5 | flamegraph.svg 6 | test/__pycache__ 7 | graph.png 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug executable 'red'", 11 | "cargo": { 12 | "args": [ 13 | "build", 14 | "--bin=red", 15 | "--package=red" 16 | ], 17 | "filter": { 18 | "name": "red", 19 | "kind": "bin" 20 | } 21 | }, 22 | "args": ["test/test.ts"], 23 | "env": {"RED_LOG": "red.log"}, 24 | "cwd": "${workspaceFolder}" 25 | }, 26 | { 27 | "type": "lldb", 28 | "request": "launch", 29 | "name": "Debug unit tests in executable 'red'", 30 | "cargo": { 31 | "args": [ 32 | "test", 33 | "--no-run", 34 | "--bin=red", 35 | "--package=red" 36 | ], 37 | "filter": { 38 | "name": "red", 39 | "kind": "bin" 40 | } 41 | }, 42 | "args": [], 43 | "cwd": "${workspaceFolder}" 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "makefile.configureOnOpen": false 3 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "red", 6 | "type": "process", 7 | "command": "cargo", 8 | "args": [ "run", "--release" ] 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /.zed/tasks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "label": "red", 4 | "command": "cargo run --release", 5 | "cwd": "$ZED_WORKTREE_ROOT" 6 | } 7 | ] -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "red" 3 | version = "0.0.1" 4 | edition = "2021" 5 | 6 | [profile.release] 7 | opt-level = 3 8 | debug = true 9 | # opt-level = 3 10 | # lto = true 11 | # codegen-units = 1 12 | # panic = "abort" 13 | 14 | [build-dependencies] 15 | cc="*" 16 | 17 | [dependencies] 18 | crossterm = {version="0.27.0", features=["event-stream", "use-dev-tty"]} 19 | ropey = "1.6.1" 20 | tree-sitter = "0.24.3" 21 | tree-sitter-rust = "0.23.0" 22 | tree-sitter-python = "0.23.2" 23 | tree-sitter-javascript = "0.23.1" 24 | tree-sitter-typescript = "0.23.2" 25 | tree-sitter-html = "0.23.0" 26 | tree-sitter-go = "0.23.0" 27 | tree-sitter-java = "0.23.2" 28 | # tree-sitter-kotlin = "0.3.8" 29 | tree-sitter-c = "0.23.1" 30 | tree-sitter-cpp = "0.23.1" 31 | tree-sitter-yaml = "0.6.1" 32 | tree-sitter-bash = "0.23.1" 33 | tree-sitter-json = "0.23.0" 34 | tree-sitter-zig = { git = "https://github.com/maxxnino/tree-sitter-zig.git" } 35 | tree-sitter-lua = { git = "https://github.com/tree-sitter-grammars/tree-sitter-lua", tag = "v0.0.19"} 36 | tree-sitter-toml-ng = "0.7.0" 37 | serde_yaml = "0.9.31" 38 | detect-lang = "0.1.5" 39 | tokio = { version = "1.37.0", features = ["full"] } 40 | # arboard = { version = "3.3.2"} 41 | futures = "0.3.30" 42 | futures-timer = "3.0.2" 43 | ctrlc = "3.4.2" 44 | signal-hook = "0.3.17" 45 | serde_json = "1.0.113" 46 | serde = { version = "1.0", features = ["derive"] } 47 | log2 = "0.1.10" 48 | toml = "0.8.12" 49 | strfmt = "0.2.4" 50 | rayon = "1.9.0" 51 | depgraph = "0.3.0" 52 | copypasta = "0.10.1" 53 | streaming-iterator = "0.1.9" 54 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust as builder 2 | WORKDIR /app 3 | 4 | # a trick to avoid downloading and building 5 | COPY Cargo.toml Cargo.lock ./ 6 | RUN mkdir src \ 7 | && echo "fn main() {}" > src/main.rs \ 8 | && cargo build --release \ 9 | && rm -rf src 10 | 11 | COPY . . 12 | RUN cargo install --path . 13 | 14 | FROM gcr.io/distroless/cc-debian12 15 | WORKDIR /app 16 | COPY --from=builder /app/target/release/red . 17 | COPY --from=builder /app/config.toml . 18 | COPY --from=builder /app/themes ./themes 19 | COPY --from=builder /app/langs ./langs 20 | COPY --from=builder /app/test ./test 21 | ENV RED_HOME=/app 22 | CMD ["./red"] 23 | 24 | # docker build -t red:latest . 25 | # docker run -it --rm red:latest -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 red-rs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: build 2 | 3 | build: 4 | RUSTFLAGS=-Awarnings cargo build --release 5 | 6 | flamegraph: 7 | sudo cargo flamegraph -- test/testb.py 8 | 9 | bloat: 10 | cargo bloat --crates --release 11 | 12 | build-timings: 13 | cargo build --timings --release 14 | 15 | depgraph: 16 | cargo depgraph | dot -Tpng > graph.png 17 | 18 | docker: 19 | docker build -f .dockerfile -t red:latest . 20 | 21 | docker-run: 22 | docker run -it --rm red:latest 23 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | theme = "themes/vesper.yml" 2 | left_panel_width = 25 3 | 4 | [[language]] 5 | name = "rust" 6 | types = ["rs"] 7 | comment = "//" 8 | lsp = ["rust-analyzer"] 9 | indent = { width = 4, unit = " " } 10 | executable = true 11 | exec = "cargo run {file}" 12 | exectest = "cargo test -- --show-output {file} {test}" 13 | 14 | [[language]] 15 | name = "go" 16 | types = ["go"] 17 | comment = "//" 18 | lsp = ["gopls"] 19 | indent = { width = 4, unit = "\t" } 20 | 21 | [[language]] 22 | name = "python" 23 | types = ["py"] 24 | comment = "#" 25 | lsp = ["pylsp"] 26 | indent = { width = 4, unit = " " } 27 | executable = true 28 | exec = "python {file}" 29 | exectest = "python -m pytest -k {test} {file}" 30 | 31 | [[language]] 32 | name = "javascript" 33 | types = ["js", "jsx"] 34 | comment = "//" 35 | lsp = ["typescript-language-server", "--stdio"] 36 | indent = { width = 2, unit = " " } 37 | executable = true 38 | exec = "tsx {file}" 39 | # exectest = "tsx -m pyt 40 | 41 | [[language]] 42 | name = "typescript" 43 | types = ["ts", "tsx"] 44 | comment = "//" 45 | lsp = ["typescript-language-server", "--stdio"] 46 | indent = { width = 2, unit = " " } 47 | executable = true 48 | exec = "tsx {file}" 49 | # exectest = "tsx -m pytest -k {test} {file}" 50 | 51 | [[language]] 52 | name = "java" 53 | types = ["java"] 54 | comment = "//" 55 | lsp = ["jdtls"] 56 | indent = { width = 2, unit = " " } 57 | executable = true 58 | exec = "java {file}" 59 | 60 | [[language]] 61 | name = "kotlin" 62 | types = ["kt"] 63 | comment = "//" 64 | lsp = ["kotlin-language-server"] 65 | indent = { width = 2, unit = " " } 66 | 67 | [[language]] 68 | name = "cpp" 69 | types = ["cpp"] 70 | comment = "//" 71 | lsp = ["clangd"] 72 | indent = { width = 2, unit = " " } 73 | 74 | [[language]] 75 | name = "c" 76 | types = ["c", "h"] 77 | comment = "//" 78 | lsp = ["clangd"] 79 | indent = { width = 2, unit = " " } 80 | 81 | [[language]] 82 | name = "zig" 83 | types = ["zig"] 84 | comment = "//" 85 | lsp = ["zls"] 86 | indent = { width = 2, unit = " " } 87 | 88 | [[language]] 89 | name = "lua" 90 | types = ["lua"] 91 | comment = "--" 92 | lsp = ["lua-language-server"] 93 | indent = { width = 2, unit = " " } 94 | executable = true 95 | exec = "lua {file}" 96 | 97 | [[language]] 98 | name = "shell" 99 | types = [".sh"] 100 | comment = "#" 101 | lsp = ["bash-language-server start"] 102 | indent = { width = 2, unit = " " } 103 | executable = true 104 | exec = "bash {file}" 105 | 106 | [[language]] 107 | name = "json" 108 | types = [".json"] 109 | comment = "//" 110 | indent = { width = 2, unit = " " } -------------------------------------------------------------------------------- /img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/red-rs/red/84639fb1a020a3387d7b5d4d0acc0dbd53baf1e9/img.png -------------------------------------------------------------------------------- /langs/c/highlights.scm: -------------------------------------------------------------------------------- 1 | [ 2 | "enum" 3 | "struct" 4 | "typedef" 5 | "union" 6 | ] @keyword.storage.type 7 | 8 | [ 9 | "extern" 10 | "register" 11 | (type_qualifier) 12 | (storage_class_specifier) 13 | ] @keyword.storage.modifier 14 | 15 | [ 16 | "goto" 17 | "break" 18 | "continue" 19 | ] @keyword.control 20 | 21 | [ 22 | "do" 23 | "for" 24 | "while" 25 | ] @keyword.control.repeat 26 | 27 | [ 28 | "if" 29 | "else" 30 | "switch" 31 | "case" 32 | "default" 33 | ] @keyword.control.conditional 34 | 35 | "return" @keyword.control.return 36 | 37 | [ 38 | "defined" 39 | "#define" 40 | "#elif" 41 | "#else" 42 | "#endif" 43 | "#if" 44 | "#ifdef" 45 | "#ifndef" 46 | "#include" 47 | (preproc_directive) 48 | ] @keyword.directive 49 | 50 | (pointer_declarator "*" @type.builtin) 51 | (abstract_pointer_declarator "*" @type.builtin) 52 | 53 | [(true) (false)] @constant.builtin.boolean 54 | 55 | (enumerator name: (identifier) @type.enum.variant) 56 | 57 | (string_literal) @string 58 | (system_lib_string) @string 59 | 60 | (null) @constant 61 | (number_literal) @constant.numeric 62 | (char_literal) @constant.character 63 | (escape_sequence) @constant.character.escape 64 | 65 | 66 | (call_expression 67 | function: (identifier) @function) 68 | (call_expression 69 | function: (field_expression 70 | field: (field_identifier) @function)) 71 | (call_expression (argument_list (identifier) @variable)) 72 | (function_declarator 73 | declarator: [(identifier) (field_identifier)] @function) 74 | (parameter_declaration 75 | declarator: (identifier) @variable.parameter) 76 | (parameter_declaration 77 | (pointer_declarator 78 | declarator: (identifier) @variable.parameter)) 79 | (preproc_function_def 80 | name: (identifier) @function.special) 81 | 82 | (attribute 83 | name: (identifier) @attribute) 84 | 85 | (field_identifier) @variable.other.member 86 | (statement_identifier) @label 87 | (type_identifier) @type 88 | (primitive_type) @type.builtin 89 | (sized_type_specifier) @type.builtin 90 | 91 | (identifier) @variable 92 | 93 | (comment) @comment -------------------------------------------------------------------------------- /langs/cpp/highlights.scm: -------------------------------------------------------------------------------- 1 | 2 | "sizeof" @keyword 3 | 4 | [ 5 | "enum" 6 | "struct" 7 | "typedef" 8 | "union" 9 | ] @keyword.storage.type 10 | 11 | [ 12 | "extern" 13 | "register" 14 | (type_qualifier) 15 | (storage_class_specifier) 16 | ] @keyword.storage.modifier 17 | 18 | [ 19 | "goto" 20 | "break" 21 | "continue" 22 | ] @keyword.control 23 | 24 | [ 25 | "do" 26 | "for" 27 | "while" 28 | ] @keyword.control.repeat 29 | 30 | [ 31 | "if" 32 | "else" 33 | "switch" 34 | "case" 35 | "default" 36 | ] @keyword.control.conditional 37 | 38 | "return" @keyword.control.return 39 | 40 | [ 41 | "defined" 42 | "#define" 43 | "#elif" 44 | "#else" 45 | "#endif" 46 | "#if" 47 | "#ifdef" 48 | "#ifndef" 49 | "#include" 50 | (preproc_directive) 51 | ] @keyword.directive 52 | 53 | (pointer_declarator "*" @type.builtin) 54 | (abstract_pointer_declarator "*" @type.builtin) 55 | 56 | [(true) (false)] @constant.builtin.boolean 57 | 58 | (enumerator name: (identifier) @type.enum.variant) 59 | 60 | (string_literal) @string 61 | (system_lib_string) @string 62 | 63 | (null) @constant 64 | (number_literal) @constant.numeric 65 | (char_literal) @constant.character 66 | (escape_sequence) @constant.character.escape 67 | 68 | 69 | (call_expression 70 | function: (identifier) @function) 71 | (call_expression 72 | function: (field_expression 73 | field: (field_identifier) @function)) 74 | (call_expression (argument_list (identifier) @variable)) 75 | (function_declarator 76 | declarator: [(identifier) (field_identifier)] @function) 77 | (parameter_declaration 78 | declarator: (identifier) @variable.parameter) 79 | (parameter_declaration 80 | (pointer_declarator 81 | declarator: (identifier) @variable.parameter)) 82 | (preproc_function_def 83 | name: (identifier) @function.special) 84 | 85 | (attribute 86 | name: (identifier) @attribute) 87 | 88 | (field_identifier) @variable.other.member 89 | (statement_identifier) @label 90 | (type_identifier) @type 91 | (primitive_type) @type.builtin 92 | (sized_type_specifier) @type.builtin 93 | 94 | (identifier) @variable 95 | 96 | (comment) @comment 97 | 98 | 99 | ; Keywords 100 | 101 | [ 102 | "try" 103 | "catch" 104 | "noexcept" 105 | "throw" 106 | ] @exception @keyword 107 | 108 | [ 109 | "class" 110 | "decltype" 111 | "explicit" 112 | "friend" 113 | "namespace" 114 | "override" 115 | "template" 116 | "typename" 117 | "using" 118 | ;"concept" 119 | ;"requires" 120 | ] @keyword 121 | 122 | [ 123 | "co_await" 124 | ] @keyword.coroutine 125 | 126 | [ 127 | "co_yield" 128 | "co_return" 129 | ] @keyword.coroutine.return 130 | 131 | [ 132 | "public" 133 | "private" 134 | "protected" 135 | "virtual" 136 | "final" 137 | ] @type.qualifier @keyword 138 | 139 | [ 140 | "new" 141 | "delete" 142 | ] @keyword.operator 143 | 144 | ; Constants 145 | 146 | (this) @variable.builtin 147 | ;(null "nullptr" @constant.builtin) 148 | 149 | (true) @boolean 150 | (false) @boolean 151 | 152 | ; Literals 153 | 154 | (raw_string_literal) @string 155 | 156 | (namespace_identifier) @namespace 157 | 158 | ;(null "nullptr" @constant.builtin) 159 | 160 | (auto) @type.builtin 161 | 162 | (operator_name) @function 163 | "operator" @function -------------------------------------------------------------------------------- /langs/go/highlights.scm: -------------------------------------------------------------------------------- 1 | ; Function calls 2 | 3 | (call_expression 4 | function: (identifier) @function.builtin 5 | (.match? @function.builtin "^(append|cap|close|complex|copy|delete|imag|len|make|new|panic|print|println|real|recover)$")) 6 | 7 | (call_expression 8 | function: (identifier) @function) 9 | 10 | (call_expression 11 | function: (selector_expression 12 | field: (field_identifier) @function.method)) 13 | 14 | ; Function definitions 15 | 16 | (function_declaration 17 | name: (identifier) @function) 18 | 19 | (method_declaration 20 | name: (field_identifier) @function.method) 21 | 22 | ; Identifiers 23 | 24 | (type_identifier) @type 25 | (field_identifier) @property 26 | (identifier) @variable 27 | 28 | ; Operators 29 | 30 | [ 31 | "--" 32 | "-" 33 | "-=" 34 | ":=" 35 | "!" 36 | "!=" 37 | "..." 38 | "*" 39 | "*" 40 | "*=" 41 | "/" 42 | "/=" 43 | "&" 44 | "&&" 45 | "&=" 46 | "%" 47 | "%=" 48 | "^" 49 | "^=" 50 | "+" 51 | "++" 52 | "+=" 53 | "<-" 54 | "<" 55 | "<<" 56 | "<<=" 57 | "<=" 58 | "=" 59 | "==" 60 | ">" 61 | ">=" 62 | ">>" 63 | ">>=" 64 | "|" 65 | "|=" 66 | "||" 67 | "~" 68 | ] @operator 69 | 70 | ; Keywords 71 | 72 | [ 73 | "break" 74 | "case" 75 | "chan" 76 | "const" 77 | "continue" 78 | "default" 79 | "defer" 80 | "else" 81 | "fallthrough" 82 | "for" 83 | "func" 84 | "go" 85 | "goto" 86 | "if" 87 | "import" 88 | "interface" 89 | "map" 90 | "package" 91 | "range" 92 | "return" 93 | "select" 94 | "struct" 95 | "switch" 96 | "type" 97 | "var" 98 | ] @keyword 99 | 100 | ; Literals 101 | 102 | [ 103 | (interpreted_string_literal) 104 | (raw_string_literal) 105 | (rune_literal) 106 | ] @string 107 | 108 | (escape_sequence) @escape 109 | 110 | [ 111 | (int_literal) 112 | (float_literal) 113 | (imaginary_literal) 114 | ] @number 115 | 116 | [ 117 | (true) 118 | (false) 119 | (nil) 120 | (iota) 121 | ] @constant.builtin 122 | 123 | (comment) @comment -------------------------------------------------------------------------------- /langs/html/highlights.scm: -------------------------------------------------------------------------------- 1 | (tag_name) @tag 2 | (erroneous_end_tag_name) @tag.error 3 | (doctype) @constant 4 | (attribute_name) @attribute 5 | (attribute_value) @string 6 | (comment) @comment 7 | 8 | [ 9 | "<" 10 | ">" 11 | "" 13 | ] @punctuation.bracket -------------------------------------------------------------------------------- /langs/java/highlights.scm: -------------------------------------------------------------------------------- 1 | ; Methods 2 | 3 | (method_declaration 4 | name: (identifier) @function.method) 5 | (method_invocation 6 | name: (identifier) @function.method) 7 | (super) @function.builtin 8 | 9 | ; Annotations 10 | 11 | (annotation 12 | name: (identifier) @attribute) 13 | (marker_annotation 14 | name: (identifier) @attribute) 15 | 16 | ; Types 17 | 18 | (interface_declaration 19 | name: (identifier) @type) 20 | (class_declaration 21 | name: (identifier) @type) 22 | (record_declaration 23 | name: (identifier) @type) 24 | (enum_declaration 25 | name: (identifier) @type) 26 | 27 | ((field_access 28 | object: (identifier) @type) 29 | (#match? @type "^[A-Z]")) 30 | ((scoped_identifier 31 | scope: (identifier) @type) 32 | (#match? @type "^[A-Z]")) 33 | 34 | (constructor_declaration 35 | name: (identifier) @type) 36 | (compact_constructor_declaration 37 | name: (identifier) @type) 38 | 39 | (type_identifier) @type 40 | 41 | [ 42 | (boolean_type) 43 | (integral_type) 44 | (floating_point_type) 45 | (floating_point_type) 46 | (void_type) 47 | ] @type.builtin 48 | 49 | (type_arguments 50 | (wildcard "?" @type.builtin)) 51 | 52 | ; Variables 53 | 54 | ((identifier) @constant 55 | (#match? @constant "^_*[A-Z][A-Z\\d_]+$")) 56 | 57 | (identifier) @variable 58 | 59 | (this) @variable.builtin 60 | 61 | ; Literals 62 | 63 | [ 64 | (hex_integer_literal) 65 | (decimal_integer_literal) 66 | (octal_integer_literal) 67 | (binary_integer_literal) 68 | ] @constant.numeric.integer 69 | 70 | [ 71 | (decimal_floating_point_literal) 72 | (hex_floating_point_literal) 73 | ] @constant.numeric.float 74 | 75 | (character_literal) @constant.character 76 | 77 | [ 78 | (string_literal) 79 | ] @string 80 | 81 | [ 82 | (true) 83 | (false) 84 | (null_literal) 85 | ] @constant.builtin 86 | 87 | (line_comment) @comment 88 | (block_comment) @comment 89 | 90 | ; Punctuation 91 | 92 | [ 93 | "::" 94 | "." 95 | ";" 96 | "," 97 | ] @punctuation.delimiter 98 | 99 | [ 100 | "@" 101 | "..." 102 | ] @punctuation.special 103 | 104 | [ 105 | "(" 106 | ")" 107 | "[" 108 | "]" 109 | "{" 110 | "}" 111 | ] @punctuation.bracket 112 | 113 | (type_arguments 114 | [ 115 | "<" 116 | ">" 117 | ] @punctuation.bracket) 118 | 119 | (type_parameters 120 | [ 121 | "<" 122 | ">" 123 | ] @punctuation.bracket) 124 | 125 | ; Operators 126 | 127 | [ 128 | "=" 129 | ">" 130 | "<" 131 | "!" 132 | "~" 133 | "?" 134 | ":" 135 | "->" 136 | "==" 137 | ">=" 138 | "<=" 139 | "!=" 140 | "&&" 141 | "||" 142 | "++" 143 | "--" 144 | "+" 145 | "-" 146 | "*" 147 | "/" 148 | "&" 149 | "|" 150 | "^" 151 | "%" 152 | "<<" 153 | ">>" 154 | ">>>" 155 | "+=" 156 | "-=" 157 | "*=" 158 | "/=" 159 | "&=" 160 | "|=" 161 | "^=" 162 | "%=" 163 | "<<=" 164 | ">>=" 165 | ">>>=" 166 | ] @operator 167 | 168 | ; Keywords 169 | 170 | [ 171 | "abstract" 172 | "assert" 173 | "break" 174 | "case" 175 | "catch" 176 | "class" 177 | "continue" 178 | "default" 179 | "do" 180 | "else" 181 | "enum" 182 | "exports" 183 | "extends" 184 | "final" 185 | "finally" 186 | "for" 187 | "if" 188 | "implements" 189 | "import" 190 | "instanceof" 191 | "interface" 192 | "module" 193 | "native" 194 | "new" 195 | "non-sealed" 196 | "open" 197 | "opens" 198 | "package" 199 | "permits" 200 | "private" 201 | "protected" 202 | "provides" 203 | "public" 204 | "requires" 205 | "record" 206 | "return" 207 | "sealed" 208 | "static" 209 | "strictfp" 210 | "switch" 211 | "synchronized" 212 | "throw" 213 | "throws" 214 | "to" 215 | "transient" 216 | "transitive" 217 | "try" 218 | "uses" 219 | "volatile" 220 | "while" 221 | "with" 222 | "yield" 223 | ] @keyword -------------------------------------------------------------------------------- /langs/javascript/highlights.scm: -------------------------------------------------------------------------------- 1 | ; Special identifiers 2 | ;-------------------- 3 | 4 | ([ 5 | (identifier) 6 | (shorthand_property_identifier) 7 | (shorthand_property_identifier_pattern) 8 | ] @constant 9 | (#match? @constant "^[A-Z_][A-Z\\d_]+$")) 10 | 11 | 12 | ((identifier) @constructor 13 | (#match? @constructor "^[A-Z]")) 14 | 15 | ((identifier) @variable.builtin 16 | (#match? @variable.builtin "^(arguments|module|console|window|document)$") 17 | (#is-not? local)) 18 | 19 | ((identifier) @function.builtin 20 | (#eq? @function.builtin "require") 21 | (#is-not? local)) 22 | 23 | ; Function and method definitions 24 | ;-------------------------------- 25 | 26 | (function_expression 27 | name: (identifier) @function) 28 | (function_declaration 29 | name: (identifier) @function) 30 | (method_definition 31 | name: (property_identifier) @function.method) 32 | 33 | (pair 34 | key: (property_identifier) @function.method 35 | value: [(function_expression) (arrow_function)]) 36 | 37 | (assignment_expression 38 | left: (member_expression 39 | property: (property_identifier) @function.method) 40 | right: [(function_expression) (arrow_function)]) 41 | 42 | (variable_declarator 43 | name: (identifier) @function 44 | value: [(function_expression) (arrow_function)]) 45 | 46 | (assignment_expression 47 | left: (identifier) @function 48 | right: [(function_expression) (arrow_function)]) 49 | 50 | ; Function and method calls 51 | ;-------------------------- 52 | 53 | (call_expression 54 | function: (identifier) @function) 55 | 56 | (call_expression 57 | function: (member_expression 58 | property: (property_identifier) @function.method)) 59 | 60 | ; Variables 61 | ;---------- 62 | 63 | (identifier) @variable 64 | 65 | ; Properties 66 | ;----------- 67 | 68 | (property_identifier) @property 69 | 70 | ; Literals 71 | ;--------- 72 | 73 | (this) @variable.builtin 74 | (super) @variable.builtin 75 | 76 | [ 77 | (true) 78 | (false) 79 | (null) 80 | (undefined) 81 | ] @constant.builtin 82 | 83 | (comment) @comment 84 | 85 | [ 86 | (string) 87 | (template_string) 88 | ] @string 89 | 90 | (regex) @string.special 91 | (number) @number 92 | 93 | ; Tokens 94 | ;------- 95 | 96 | (template_substitution 97 | "${" @punctuation.special 98 | "}" @punctuation.special) @embedded 99 | 100 | [ 101 | ";" 102 | (optional_chain) 103 | "." 104 | "," 105 | ] @punctuation.delimiter 106 | 107 | [ 108 | "-" 109 | "--" 110 | "-=" 111 | "+" 112 | "++" 113 | "+=" 114 | "*" 115 | "*=" 116 | "**" 117 | "**=" 118 | "/" 119 | "/=" 120 | "%" 121 | "%=" 122 | "<" 123 | "<=" 124 | "<<" 125 | "<<=" 126 | "=" 127 | "==" 128 | "===" 129 | "!" 130 | "!=" 131 | "!==" 132 | "=>" 133 | ">" 134 | ">=" 135 | ">>" 136 | ">>=" 137 | ">>>" 138 | ">>>=" 139 | "~" 140 | "^" 141 | "&" 142 | "|" 143 | "^=" 144 | "&=" 145 | "|=" 146 | "&&" 147 | "||" 148 | "??" 149 | "&&=" 150 | "||=" 151 | "??=" 152 | ] @operator 153 | 154 | [ 155 | "(" 156 | ")" 157 | "[" 158 | "]" 159 | "{" 160 | "}" 161 | ] @punctuation.bracket 162 | 163 | [ 164 | "as" 165 | "async" 166 | "await" 167 | "break" 168 | "case" 169 | "catch" 170 | "class" 171 | "const" 172 | "continue" 173 | "debugger" 174 | "default" 175 | "delete" 176 | "do" 177 | "else" 178 | "export" 179 | "extends" 180 | "finally" 181 | "for" 182 | "from" 183 | "function" 184 | "get" 185 | "if" 186 | "import" 187 | "in" 188 | "instanceof" 189 | "let" 190 | "new" 191 | "of" 192 | "return" 193 | "set" 194 | "static" 195 | "switch" 196 | "target" 197 | "throw" 198 | "try" 199 | "typeof" 200 | "var" 201 | "void" 202 | "while" 203 | "with" 204 | "yield" 205 | ] @keyword -------------------------------------------------------------------------------- /langs/json/highlights.scm: -------------------------------------------------------------------------------- 1 | [ 2 | (true) 3 | (false) 4 | ] @constant.builtin.boolean 5 | (null) @constant.builtin 6 | (number) @constant.numeric 7 | (pair 8 | key: (_) @variable) 9 | 10 | (string) @string 11 | (escape_sequence) @constant.character.escape 12 | (ERROR) @error 13 | 14 | -------------------------------------------------------------------------------- /langs/kotlin/highlights.scm: -------------------------------------------------------------------------------- 1 | ; Identifiers 2 | (simple_identifier) @variable 3 | 4 | ; `it` keyword inside lambdas 5 | ; FIXME: This will highlight the keyword outside of lambdas since tree-sitter 6 | ; does not allow us to check for arbitrary nestation 7 | ((simple_identifier) @variable.builtin 8 | (#eq? @variable.builtin "it")) 9 | 10 | ; `field` keyword inside property getter/setter 11 | ; FIXME: This will highlight the keyword outside of getters and setters 12 | ; since tree-sitter does not allow us to check for arbitrary nestation 13 | ((simple_identifier) @variable.builtin 14 | (#eq? @variable.builtin "field")) 15 | 16 | [ 17 | "this" 18 | "super" 19 | "this@" 20 | "super@" 21 | ] @variable.builtin 22 | 23 | ; NOTE: for consistency with "super@" 24 | (super_expression 25 | "@" @variable.builtin) 26 | 27 | (class_parameter 28 | (simple_identifier) @variable.member) 29 | 30 | ; NOTE: temporary fix for treesitter bug that causes delay in file opening 31 | ;(class_body 32 | ; (property_declaration 33 | ; (variable_declaration 34 | ; (simple_identifier) @variable.member))) 35 | ; id_1.id_2.id_3: `id_2` and `id_3` are assumed as object properties 36 | (_ 37 | (navigation_suffix 38 | (simple_identifier) @variable.member)) 39 | 40 | ; SCREAMING CASE identifiers are assumed to be constants 41 | ((simple_identifier) @constant 42 | (#lua-match? @constant "^[A-Z][A-Z0-9_]*$")) 43 | 44 | (_ 45 | (navigation_suffix 46 | (simple_identifier) @constant 47 | (#lua-match? @constant "^[A-Z][A-Z0-9_]*$"))) 48 | 49 | (enum_entry 50 | (simple_identifier) @constant) 51 | 52 | (type_identifier) @type 53 | 54 | ; '?' operator, replacement for Java @Nullable 55 | (nullable_type) @punctuation.special 56 | 57 | (type_alias 58 | (type_identifier) @type.definition) 59 | 60 | ((type_identifier) @type.builtin 61 | (#any-of? @type.builtin 62 | "Byte" "Short" "Int" "Long" "UByte" "UShort" "UInt" "ULong" "Float" "Double" "Boolean" "Char" 63 | "String" "Array" "ByteArray" "ShortArray" "IntArray" "LongArray" "UByteArray" "UShortArray" 64 | "UIntArray" "ULongArray" "FloatArray" "DoubleArray" "BooleanArray" "CharArray" "Map" "Set" 65 | "List" "EmptyMap" "EmptySet" "EmptyList" "MutableMap" "MutableSet" "MutableList")) 66 | 67 | (package_header 68 | "package" @keyword 69 | . 70 | (identifier 71 | (simple_identifier) @module)) 72 | 73 | (import_header 74 | "import" @keyword.import) 75 | 76 | ; The last `simple_identifier` in a `import_header` will always either be a function 77 | ; or a type. Classes can appear anywhere in the import path, unlike functions 78 | (import_header 79 | (identifier 80 | (simple_identifier) @type @_import) 81 | (import_alias 82 | (type_identifier) @type.definition)? 83 | (#lua-match? @_import "^[A-Z]")) 84 | 85 | (import_header 86 | (identifier 87 | (simple_identifier) @function @_import .) 88 | (import_alias 89 | (type_identifier) @function)? 90 | (#lua-match? @_import "^[a-z]")) 91 | 92 | (label) @label 93 | 94 | ; Function definitions 95 | (function_declaration 96 | (simple_identifier) @function) 97 | 98 | (getter 99 | "get" @function.builtin) 100 | 101 | (setter 102 | "set" @function.builtin) 103 | 104 | (primary_constructor) @constructor 105 | 106 | (secondary_constructor 107 | "constructor" @constructor) 108 | 109 | (constructor_invocation 110 | (user_type 111 | (type_identifier) @constructor)) 112 | 113 | (anonymous_initializer 114 | "init" @constructor) 115 | 116 | (parameter 117 | (simple_identifier) @variable.parameter) 118 | 119 | (parameter_with_optional_type 120 | (simple_identifier) @variable.parameter) 121 | 122 | ; lambda parameters 123 | (lambda_literal 124 | (lambda_parameters 125 | (variable_declaration 126 | (simple_identifier) @variable.parameter))) 127 | 128 | ; Function calls 129 | ; function() 130 | (call_expression 131 | . 132 | (simple_identifier) @function.call) 133 | 134 | ; ::function 135 | (callable_reference 136 | . 137 | (simple_identifier) @function.call) 138 | 139 | ; object.function() or object.property.function() 140 | (call_expression 141 | (navigation_expression 142 | (navigation_suffix 143 | (simple_identifier) @function.call) .)) 144 | 145 | (call_expression 146 | . 147 | (simple_identifier) @function.builtin 148 | (#any-of? @function.builtin 149 | "arrayOf" "arrayOfNulls" "byteArrayOf" "shortArrayOf" "intArrayOf" "longArrayOf" "ubyteArrayOf" 150 | "ushortArrayOf" "uintArrayOf" "ulongArrayOf" "floatArrayOf" "doubleArrayOf" "booleanArrayOf" 151 | "charArrayOf" "emptyArray" "mapOf" "setOf" "listOf" "emptyMap" "emptySet" "emptyList" 152 | "mutableMapOf" "mutableSetOf" "mutableListOf" "print" "println" "error" "TODO" "run" 153 | "runCatching" "repeat" "lazy" "lazyOf" "enumValues" "enumValueOf" "assert" "check" 154 | "checkNotNull" "require" "requireNotNull" "with" "suspend" "synchronized")) 155 | 156 | ; Literals 157 | [ 158 | (line_comment) 159 | (multiline_comment) 160 | ] @comment @spell 161 | 162 | ((multiline_comment) @comment.documentation 163 | (#lua-match? @comment.documentation "^/[*][*][^*].*[*]/$")) 164 | 165 | (shebang_line) @keyword.directive 166 | 167 | (real_literal) @number.float 168 | 169 | [ 170 | (integer_literal) 171 | (long_literal) 172 | (hex_literal) 173 | (bin_literal) 174 | (unsigned_literal) 175 | ] @number 176 | 177 | [ 178 | "null" 179 | ; should be highlighted the same as booleans 180 | (boolean_literal) 181 | ] @boolean 182 | 183 | (character_literal) @character 184 | 185 | (string_literal) @string 186 | 187 | ; NOTE: Escapes not allowed in multi-line strings 188 | (character_literal 189 | (character_escape_seq) @string.escape) 190 | 191 | ; There are 3 ways to define a regex 192 | ; - "[abc]?".toRegex() 193 | (call_expression 194 | (navigation_expression 195 | (string_literal) @string.regexp 196 | (navigation_suffix 197 | ((simple_identifier) @_function 198 | (#eq? @_function "toRegex"))))) 199 | 200 | ; - Regex("[abc]?") 201 | (call_expression 202 | ((simple_identifier) @_function 203 | (#eq? @_function "Regex")) 204 | (call_suffix 205 | (value_arguments 206 | (value_argument 207 | (string_literal) @string.regexp)))) 208 | 209 | ; - Regex.fromLiteral("[abc]?") 210 | (call_expression 211 | (navigation_expression 212 | ((simple_identifier) @_class 213 | (#eq? @_class "Regex")) 214 | (navigation_suffix 215 | ((simple_identifier) @_function 216 | (#eq? @_function "fromLiteral")))) 217 | (call_suffix 218 | (value_arguments 219 | (value_argument 220 | (string_literal) @string.regexp)))) 221 | 222 | ; Keywords 223 | (type_alias 224 | "typealias" @keyword) 225 | 226 | (companion_object 227 | "companion" @keyword) 228 | 229 | [ 230 | (class_modifier) 231 | (member_modifier) 232 | (function_modifier) 233 | (property_modifier) 234 | (platform_modifier) 235 | (variance_modifier) 236 | (parameter_modifier) 237 | (visibility_modifier) 238 | (reification_modifier) 239 | (inheritance_modifier) 240 | ] @type.qualifier 241 | 242 | [ 243 | "val" 244 | "var" 245 | "enum" 246 | "class" 247 | "object" 248 | "interface" 249 | ; "typeof" ; NOTE: It is reserved for future use 250 | ] @keyword 251 | 252 | [ 253 | "return" 254 | "return@" 255 | ] @keyword.return 256 | 257 | "suspend" @keyword.coroutine 258 | 259 | "fun" @keyword.function 260 | 261 | [ 262 | "if" 263 | "else" 264 | "when" 265 | ] @keyword.conditional 266 | 267 | [ 268 | "for" 269 | "do" 270 | "while" 271 | "continue" 272 | "continue@" 273 | "break" 274 | "break@" 275 | ] @keyword.repeat 276 | 277 | [ 278 | "try" 279 | "catch" 280 | "throw" 281 | "finally" 282 | ] @keyword.exception 283 | 284 | (annotation 285 | "@" @attribute 286 | (use_site_target)? @attribute) 287 | 288 | (annotation 289 | (user_type 290 | (type_identifier) @attribute)) 291 | 292 | (annotation 293 | (constructor_invocation 294 | (user_type 295 | (type_identifier) @attribute))) 296 | 297 | (file_annotation 298 | "@" @attribute 299 | "file" @attribute 300 | ":" @attribute) 301 | 302 | (file_annotation 303 | (user_type 304 | (type_identifier) @attribute)) 305 | 306 | (file_annotation 307 | (constructor_invocation 308 | (user_type 309 | (type_identifier) @attribute))) 310 | 311 | ; Operators & Punctuation 312 | [ 313 | "!" 314 | "!=" 315 | "!==" 316 | "=" 317 | "==" 318 | "===" 319 | ">" 320 | ">=" 321 | "<" 322 | "<=" 323 | "||" 324 | "&&" 325 | "+" 326 | "++" 327 | "+=" 328 | "-" 329 | "--" 330 | "-=" 331 | "*" 332 | "*=" 333 | "/" 334 | "/=" 335 | "%" 336 | "%=" 337 | "?." 338 | "?:" 339 | "!!" 340 | "is" 341 | "!is" 342 | "in" 343 | "!in" 344 | "as" 345 | "as?" 346 | ".." 347 | "->" 348 | ] @operator 349 | 350 | [ 351 | "(" 352 | ")" 353 | "[" 354 | "]" 355 | "{" 356 | "}" 357 | ] @punctuation.bracket 358 | 359 | [ 360 | "." 361 | "," 362 | ";" 363 | ":" 364 | "::" 365 | ] @punctuation.delimiter 366 | 367 | (super_expression 368 | [ 369 | "<" 370 | ">" 371 | ] @punctuation.delimiter) 372 | 373 | (type_arguments 374 | [ 375 | "<" 376 | ">" 377 | ] @punctuation.delimiter) 378 | 379 | (type_parameters 380 | [ 381 | "<" 382 | ">" 383 | ] @punctuation.delimiter) 384 | 385 | ; NOTE: `interpolated_identifier`s can be highlighted in any way 386 | (string_literal 387 | "$" @punctuation.special 388 | (interpolated_identifier) @none @variable) 389 | 390 | (string_literal 391 | "${" @punctuation.special 392 | (interpolated_expression) @none 393 | "}" @punctuation.special) -------------------------------------------------------------------------------- /langs/lua/highlights.scm: -------------------------------------------------------------------------------- 1 | ;;; Highlighting for lua 2 | 3 | ;;; Builtins 4 | ((identifier) @variable.builtin 5 | (#eq? @variable.builtin "self")) 6 | 7 | ;; Keywords 8 | 9 | (if_statement 10 | [ 11 | "if" 12 | "then" 13 | "end" 14 | ] @keyword.control.conditional) 15 | 16 | (elseif_statement 17 | [ 18 | "elseif" 19 | "then" 20 | "end" 21 | ] @keyword.control.conditional) 22 | 23 | (else_statement 24 | [ 25 | "else" 26 | "end" 27 | ] @keyword.control.conditional) 28 | 29 | (for_statement 30 | [ 31 | "for" 32 | "do" 33 | "end" 34 | ] @keyword.control.repeat) 35 | 36 | (while_statement 37 | [ 38 | "while" 39 | "do" 40 | "end" 41 | ] @keyword.control.repeat) 42 | 43 | (repeat_statement 44 | [ 45 | "repeat" 46 | "until" 47 | ] @keyword.control.repeat) 48 | 49 | (do_statement 50 | [ 51 | "do" 52 | "end" 53 | ] @keyword) 54 | 55 | "return" @keyword.control.return 56 | 57 | [ 58 | "in" 59 | "local" 60 | (break_statement) 61 | "goto" 62 | ] @keyword 63 | 64 | (function_declaration 65 | [ 66 | "function" 67 | "end" 68 | ] @keyword.function) 69 | 70 | (function_definition 71 | [ 72 | "function" 73 | "end" 74 | ] @keyword.function) 75 | 76 | ;; Operators 77 | 78 | [ 79 | "not" 80 | "and" 81 | "or" 82 | ] @keyword.operator 83 | 84 | [ 85 | "=" 86 | "~=" 87 | "==" 88 | "<=" 89 | ">=" 90 | "<" 91 | ">" 92 | "+" 93 | "-" 94 | "%" 95 | "/" 96 | "//" 97 | "*" 98 | "^" 99 | "&" 100 | "~" 101 | "|" 102 | ">>" 103 | "<<" 104 | ".." 105 | "#" 106 | ] @operator 107 | 108 | ;; Punctuation 109 | ["," "." ":" ";"] @punctuation.delimiter 110 | 111 | ;; Brackets 112 | 113 | [ 114 | "(" 115 | ")" 116 | "[" 117 | "]" 118 | "{" 119 | "}" 120 | ] @punctuation.bracket 121 | 122 | ; ;; Constants 123 | [ 124 | (false) 125 | (true) 126 | ] @constant.builtin.boolean 127 | (nil) @constant.builtin 128 | (vararg_expression) @constant 129 | 130 | ((identifier) @constant 131 | (#match? @constant "^[A-Z][A-Z_0-9]*$")) 132 | 133 | ;; Parameters 134 | (parameters 135 | (identifier) @variable.parameter) 136 | 137 | ; ;; Functions 138 | (function_declaration name: (identifier) @function) 139 | (function_call name: (identifier) @function.call) 140 | 141 | (function_declaration name: (dot_index_expression field: (identifier) @function)) 142 | (function_call name: (dot_index_expression field: (identifier) @function.call)) 143 | 144 | ; TODO: incorrectly highlights variable N in `N, nop = 42, function() end` 145 | (assignment_statement 146 | (variable_list 147 | name: (identifier) @function) 148 | (expression_list 149 | value: (function_definition))) 150 | 151 | (method_index_expression method: (identifier) @function.method) 152 | 153 | ;; Nodes 154 | (comment) @comment 155 | (string) @string 156 | (number) @constant.numeric.integer 157 | (label_statement) @label 158 | ; A bit of a tricky one, this will only match field names 159 | (field . (identifier) @variable.other.member (_)) 160 | (hash_bang_line) @comment 161 | 162 | ;; Property 163 | (dot_index_expression field: (identifier) @variable.other.member) 164 | 165 | ;; Variable 166 | (identifier) @variable 167 | 168 | ;; Error 169 | (ERROR) @error -------------------------------------------------------------------------------- /langs/python/highlights.scm: -------------------------------------------------------------------------------- 1 | ; Imports 2 | 3 | (dotted_name 4 | (identifier)* @namespace) 5 | 6 | (aliased_import 7 | alias: (identifier) @namespace) 8 | 9 | ; Builtin functions 10 | 11 | ((call 12 | function: (identifier) @function.builtin) 13 | (#match? 14 | @function.builtin 15 | "^(abs|all|any|ascii|bin|bool|breakpoint|bytearray|bytes|callable|chr|classmethod|compile|complex|delattr|dict|dir|divmod|enumerate|eval|exec|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|isinstance|issubclass|iter|len|list|locals|map|max|memoryview|min|next|object|oct|open|ord|pow|print|property|range|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|vars|zip|__import__)$")) 16 | 17 | ; Function calls 18 | 19 | [ 20 | "def" 21 | "lambda" 22 | ] @keyword.function 23 | 24 | (call 25 | function: (attribute attribute: (identifier) @constructor) 26 | (#match? @constructor "^[A-Z]")) 27 | (call 28 | function: (identifier) @constructor 29 | (#match? @constructor "^[A-Z]")) 30 | 31 | (call 32 | function: (attribute attribute: (identifier) @function.method)) 33 | 34 | (call 35 | function: (identifier) @function) 36 | 37 | ; Function definitions 38 | 39 | (function_definition 40 | name: (identifier) @constructor 41 | (#match? @constructor "^(__new__|__init__)$")) 42 | 43 | (function_definition 44 | name: (identifier) @function) 45 | 46 | ; Decorators 47 | 48 | (decorator) @function 49 | (decorator (identifier) @function) 50 | (decorator (attribute attribute: (identifier) @function)) 51 | (decorator (call 52 | function: (attribute attribute: (identifier) @function))) 53 | 54 | ; Parameters 55 | 56 | ((identifier) @variable.builtin 57 | (#match? @variable.builtin "^(self|cls)$")) 58 | 59 | (parameters (identifier) @variable.parameter) 60 | (parameters (typed_parameter (identifier) @variable.parameter)) 61 | (parameters (default_parameter name: (identifier) @variable.parameter)) 62 | (parameters (typed_default_parameter name: (identifier) @variable.parameter)) 63 | 64 | (parameters 65 | (list_splat_pattern ; *args 66 | (identifier) @variable.parameter)) 67 | (parameters 68 | (dictionary_splat_pattern ; **kwargs 69 | (identifier) @variable.parameter)) 70 | 71 | (lambda_parameters 72 | (identifier) @variable.parameter) 73 | 74 | ; Types 75 | 76 | ((identifier) @type.builtin 77 | (#match? 78 | @type.builtin 79 | "^(bool|bytes|dict|float|frozenset|int|list|set|str|tuple)$")) 80 | 81 | ; In type hints make everything types to catch non-conforming identifiers 82 | ; (e.g., datetime.datetime) and None 83 | (type [(identifier) (none)] @type) 84 | ; Handle [] . and | nesting 4 levels deep 85 | (type 86 | (_ [(identifier) (none)]? @type 87 | (_ [(identifier) (none)]? @type 88 | (_ [(identifier) (none)]? @type 89 | (_ [(identifier) (none)]? @type))))) 90 | 91 | (class_definition name: (identifier) @type) 92 | (class_definition superclasses: (argument_list (identifier) @type)) 93 | 94 | ; Variables 95 | 96 | ((identifier) @constant 97 | (#match? @constant "^_*[A-Z][A-Z\\d_]*$")) 98 | 99 | ((identifier) @type 100 | (#match? @type "^[A-Z]")) 101 | 102 | (attribute attribute: (identifier) @variable.other.member) 103 | (identifier) @variable 104 | 105 | ; Literals 106 | (none) @constant.builtin 107 | [ 108 | (true) 109 | (false) 110 | ] @constant.builtin.boolean 111 | 112 | (integer) @constant.numeric.integer 113 | (float) @constant.numeric.float 114 | (comment) @comment 115 | (string) @string 116 | (escape_sequence) @constant.character.escape 117 | 118 | ["," "." ":" ";" (ellipsis)] @punctuation.delimiter 119 | (interpolation 120 | "{" @punctuation.special 121 | "}" @punctuation.special) @embedded 122 | ["(" ")" "[" "]" "{" "}"] @punctuation.bracket 123 | 124 | [ 125 | "-" 126 | "-=" 127 | "!=" 128 | "*" 129 | "**" 130 | "**=" 131 | "*=" 132 | "/" 133 | "//" 134 | "//=" 135 | "/=" 136 | "&" 137 | "%" 138 | "%=" 139 | "^" 140 | "+" 141 | "->" 142 | "+=" 143 | "<" 144 | "<<" 145 | "<=" 146 | "<>" 147 | "=" 148 | ":=" 149 | "==" 150 | ">" 151 | ">=" 152 | ">>" 153 | "|" 154 | "~" 155 | ] @operator 156 | 157 | [ 158 | "as" 159 | "assert" 160 | "await" 161 | "from" 162 | "pass" 163 | 164 | "with" 165 | ] @keyword.control 166 | 167 | [ 168 | "if" 169 | "elif" 170 | "else" 171 | "match" 172 | "case" 173 | ] @keyword.control.conditional 174 | 175 | [ 176 | "while" 177 | "for" 178 | "break" 179 | "continue" 180 | ] @keyword.control.repeat 181 | 182 | [ 183 | "return" 184 | "yield" 185 | ] @keyword.control.return 186 | (yield "from" @keyword.control.return) 187 | 188 | [ 189 | "raise" 190 | "try" 191 | "except" 192 | "finally" 193 | ] @keyword.control.except 194 | (raise_statement "from" @keyword.control.except) 195 | "import" @keyword.control.import 196 | 197 | (for_statement "in" @keyword.control) 198 | (for_in_clause "in" @keyword.control) 199 | 200 | [ 201 | "and" 202 | "async" 203 | "class" 204 | "exec" 205 | "global" 206 | "nonlocal" 207 | "print" 208 | ] @keyword 209 | [ 210 | "and" 211 | "or" 212 | "in" 213 | "not" 214 | "del" 215 | "is" 216 | ] @keyword.operator 217 | 218 | ((identifier) @type.builtin 219 | (#match? @type.builtin 220 | "^(BaseException|Exception|ArithmeticError|BufferError|LookupError|AssertionError|AttributeError|EOFError|FloatingPointError|GeneratorExit|ImportError|ModuleNotFoundError|IndexError|KeyError|KeyboardInterrupt|MemoryError|NameError|NotImplementedError|OSError|OverflowError|RecursionError|ReferenceError|RuntimeError|StopIteration|StopAsyncIteration|SyntaxError|IndentationError|TabError|SystemError|SystemExit|TypeError|UnboundLocalError|UnicodeError|UnicodeEncodeError|UnicodeDecodeError|UnicodeTranslateError|ValueError|ZeroDivisionError|EnvironmentError|IOError|WindowsError|BlockingIOError|ChildProcessError|ConnectionError|BrokenPipeError|ConnectionAbortedError|ConnectionRefusedError|ConnectionResetError|FileExistsError|FileNotFoundError|InterruptedError|IsADirectoryError|NotADirectoryError|PermissionError|ProcessLookupError|TimeoutError|Warning|UserWarning|DeprecationWarning|PendingDeprecationWarning|SyntaxWarning|RuntimeWarning|FutureWarning|ImportWarning|UnicodeWarning|BytesWarning|ResourceWarning)$")) 221 | 222 | (ERROR) @error -------------------------------------------------------------------------------- /langs/python/tests.scm: -------------------------------------------------------------------------------- 1 | ( 2 | (class_definition 3 | body: (block 4 | (function_definition 5 | name: (identifier) @method @test-name))) 6 | (#match? @method "test") 7 | ) 8 | 9 | ( 10 | class_definition name: (identifier) @test-name 11 | (#match? @test-name "[Tt]est") 12 | ) -------------------------------------------------------------------------------- /langs/rust/highlights.scm: -------------------------------------------------------------------------------- 1 | ; ------- 2 | ; Tree-Sitter doesn't allow overrides in regards to captures, 3 | ; though it is possible to affect the child node of a captured 4 | ; node. Thus, the approach here is to flip the order so that 5 | ; overrides are unnecessary. 6 | ; ------- 7 | 8 | ; ------- 9 | ; Types 10 | ; ------- 11 | 12 | ; --- 13 | ; Primitives 14 | ; --- 15 | 16 | (escape_sequence) @constant.character.escape 17 | (primitive_type) @type.builtin 18 | (boolean_literal) @constant.builtin.boolean 19 | (integer_literal) @constant.numeric.integer 20 | (float_literal) @constant.numeric.float 21 | (char_literal) @constant.character 22 | [ 23 | (string_literal) 24 | (raw_string_literal) 25 | ] @string 26 | [ 27 | (line_comment) 28 | (block_comment) 29 | ] @comment 30 | 31 | ; --- 32 | ; Extraneous 33 | ; --- 34 | 35 | (self) @variable.builtin 36 | (enum_variant (identifier) @type.enum.variant) 37 | 38 | (field_initializer 39 | (field_identifier) @variable.other.member) 40 | (shorthand_field_initializer 41 | (identifier) @variable.other.member) 42 | (shorthand_field_identifier) @variable.other.member 43 | 44 | (lifetime 45 | "'" @label 46 | (identifier) @label) 47 | ;(loop_label 48 | ; "'" @label 49 | ; (identifier) @label) 50 | 51 | 52 | ; --- 53 | ; Variables 54 | ; --- 55 | 56 | (let_declaration 57 | pattern: [ 58 | ((identifier) @variable) 59 | ((tuple_pattern 60 | (identifier) @variable)) 61 | ]) 62 | 63 | ; It needs to be anonymous to not conflict with `call_expression` further below. 64 | (_ 65 | value: (field_expression 66 | value: (identifier)? @variable 67 | field: (field_identifier) @variable.other.member)) 68 | 69 | (parameter 70 | pattern: (identifier) @variable.parameter) 71 | (closure_parameters 72 | (identifier) @variable.parameter) 73 | 74 | ; ------- 75 | ; Keywords 76 | ; ------- 77 | 78 | (for_expression 79 | "for" @keyword.control.repeat) 80 | ((identifier) @keyword.control 81 | (#match? @keyword.control "^yield$")) 82 | 83 | "in" @keyword.control 84 | 85 | [ 86 | "match" 87 | "if" 88 | "else" 89 | ] @keyword.control.conditional 90 | 91 | [ 92 | "while" 93 | "loop" 94 | ] @keyword.control.repeat 95 | 96 | [ 97 | "break" 98 | "continue" 99 | "return" 100 | "await" 101 | ] @keyword.control.return 102 | 103 | "use" @keyword.control.import 104 | (mod_item "mod" @keyword.control.import !body) 105 | (use_as_clause "as" @keyword.control.import) 106 | 107 | (type_cast_expression "as" @keyword.operator) 108 | 109 | [ 110 | (crate) 111 | (super) 112 | "as" 113 | "pub" 114 | "mod" 115 | "extern" 116 | 117 | "impl" 118 | "where" 119 | "trait" 120 | "for" 121 | 122 | "default" 123 | "async" 124 | ] @keyword 125 | 126 | [ 127 | "struct" 128 | "enum" 129 | "union" 130 | "type" 131 | ] @keyword.storage.type 132 | 133 | "let" @keyword.storage 134 | "fn" @keyword.function 135 | "unsafe" @keyword.special 136 | "macro_rules!" @function.macro 137 | 138 | (mutable_specifier) @keyword.storage.modifier.mut 139 | 140 | (reference_type "&" @keyword.storage.modifier.ref) 141 | (self_parameter "&" @keyword.storage.modifier.ref) 142 | 143 | [ 144 | "static" 145 | "const" 146 | "ref" 147 | "move" 148 | "dyn" 149 | ] @keyword.storage.modifier 150 | 151 | ; TODO: variable.mut to highlight mutable identifiers via locals.scm 152 | 153 | ; ------- 154 | ; Guess Other Types 155 | ; ------- 156 | 157 | ((identifier) @constant 158 | (#match? @constant "^[A-Z][A-Z\\d_]*$")) 159 | 160 | ; --- 161 | ; PascalCase identifiers in call_expressions (e.g. `Ok()`) 162 | ; are assumed to be enum constructors. 163 | ; --- 164 | 165 | (call_expression 166 | function: [ 167 | ((identifier) @type.enum.variant 168 | (#match? @type.enum.variant "^[A-Z]")) 169 | (scoped_identifier 170 | name: ((identifier) @type.enum.variant 171 | (#match? @type.enum.variant "^[A-Z]"))) 172 | ]) 173 | 174 | ; --- 175 | ; Assume that types in match arms are enums and not 176 | ; tuple structs. Same for `if let` expressions. 177 | ; --- 178 | 179 | (match_pattern 180 | (scoped_identifier 181 | name: (identifier) @constructor)) 182 | (tuple_struct_pattern 183 | type: [ 184 | ((identifier) @constructor) 185 | (scoped_identifier 186 | name: (identifier) @constructor) 187 | ]) 188 | (struct_pattern 189 | type: [ 190 | ((type_identifier) @constructor) 191 | (scoped_type_identifier 192 | name: (type_identifier) @constructor) 193 | ]) 194 | 195 | ; --- 196 | ; Other PascalCase identifiers are assumed to be structs. 197 | ; --- 198 | 199 | ((identifier) @type 200 | (#match? @type "^[A-Z]")) 201 | 202 | ; ------- 203 | ; Functions 204 | ; ------- 205 | 206 | (call_expression 207 | function: [ 208 | ((identifier) @function) 209 | (scoped_identifier 210 | name: (identifier) @function) 211 | (field_expression 212 | field: (field_identifier) @function) 213 | ]) 214 | (generic_function 215 | function: [ 216 | ((identifier) @function) 217 | (scoped_identifier 218 | name: (identifier) @function) 219 | (field_expression 220 | field: (field_identifier) @function.method) 221 | ]) 222 | 223 | (function_item 224 | name: (identifier) @function) 225 | 226 | (function_signature_item 227 | name: (identifier) @function) 228 | 229 | ; --- 230 | ; Macros 231 | ; --- 232 | 233 | (attribute 234 | (identifier) @special 235 | arguments: (token_tree (identifier) @type) 236 | (#eq? @special "derive") 237 | ) 238 | 239 | (attribute 240 | (identifier) @function.macro) 241 | (attribute 242 | [ 243 | (identifier) @function.macro 244 | (scoped_identifier 245 | name: (identifier) @function.macro) 246 | ] 247 | (token_tree (identifier) @function.macro)?) 248 | 249 | (inner_attribute_item) @attribute 250 | 251 | (macro_definition 252 | name: (identifier) @function.macro) 253 | (macro_invocation 254 | macro: [ 255 | ((identifier) @function.macro) 256 | (scoped_identifier 257 | name: (identifier) @function.macro) 258 | ] 259 | "!" @function.macro) 260 | 261 | (metavariable) @variable.parameter 262 | (fragment_specifier) @type 263 | 264 | ; ------- 265 | ; Paths 266 | ; ------- 267 | 268 | (use_declaration 269 | argument: (identifier) @namespace) 270 | (use_wildcard 271 | (identifier) @namespace) 272 | (extern_crate_declaration 273 | name: (identifier) @namespace) 274 | (mod_item 275 | name: (identifier) @namespace) 276 | (scoped_use_list 277 | path: (identifier)? @namespace) 278 | (use_list 279 | (identifier) @namespace) 280 | (use_as_clause 281 | path: (identifier)? @namespace 282 | alias: (identifier) @namespace) 283 | 284 | ; --- 285 | ; Remaining Paths 286 | ; --- 287 | 288 | (scoped_identifier 289 | path: (identifier)? @namespace 290 | name: (identifier) @namespace) 291 | (scoped_type_identifier 292 | path: (identifier) @namespace) 293 | 294 | ; ------- 295 | ; Remaining Identifiers 296 | ; ------- 297 | 298 | "?" @special 299 | 300 | (type_identifier) @type 301 | (identifier) @variable 302 | (field_identifier) @variable.other.member -------------------------------------------------------------------------------- /langs/rust/tests.scm: -------------------------------------------------------------------------------- 1 | ( 2 | mod_item name: (identifier) @test-name 3 | (#match? @test-name "[Tt]est") 4 | ) 5 | 6 | ( 7 | function_item name: (identifier) @test-name 8 | (#match? @test-name "[Tt]est") 9 | ) 10 | -------------------------------------------------------------------------------- /langs/shell/highlights.scm: -------------------------------------------------------------------------------- 1 | [ 2 | (string) 3 | (raw_string) 4 | ;(ansi_c_string) 5 | (heredoc_body) 6 | ] @string 7 | 8 | (variable_assignment (word) @string) 9 | ;(command argument: "$" @string) 10 | 11 | 12 | [ 13 | "if" 14 | "then" 15 | "else" 16 | "elif" 17 | "fi" 18 | "case" 19 | "in" 20 | "esac" 21 | ] @conditional @keyword 22 | 23 | 24 | [ 25 | "for" 26 | "do" 27 | "done" 28 | ;"select" 29 | ;"until" 30 | "while" 31 | ] @repeat @keyword 32 | 33 | [ 34 | "declare" 35 | "typeset" 36 | "export" 37 | "readonly" 38 | "local" 39 | "unset" 40 | "unsetenv" 41 | ] @keyword 42 | 43 | "function" @keyword.function 44 | (special_variable_name) @constant 45 | 46 | ; trap -l 47 | ((word) @constant.builtin 48 | (#match? @constant.builtin "^SIG(HUP|INT|QUIT|ILL|TRAP|ABRT|BUS|FPE|KILL|USR[12]|SEGV|PIPE|ALRM|TERM|STKFLT|CHLD|CONT|STOP|TSTP|TT(IN|OU)|URG|XCPU|XFSZ|VTALRM|PROF|WINCH|IO|PWR|SYS|RTMIN([+]([1-9]|1[0-5]))?|RTMAX(-([1-9]|1[0-4]))?)$")) 49 | 50 | ((word) @boolean 51 | (#any-of? @boolean "true" "false")) 52 | 53 | (comment) @comment @spell 54 | 55 | (test_operator) @operator 56 | 57 | (function_definition 58 | name: (word) @function) 59 | 60 | (command_name (word) @function.call) 61 | 62 | (variable_name) @variable @identifier 63 | 64 | (case_item 65 | value: (word) @parameter) 66 | 67 | 68 | ((program . (comment) @preproc) 69 | (#lua-match? @preproc "^#!/")) -------------------------------------------------------------------------------- /langs/text/highlights.scm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/red-rs/red/84639fb1a020a3387d7b5d4d0acc0dbd53baf1e9/langs/text/highlights.scm -------------------------------------------------------------------------------- /langs/toml/highlights.scm: -------------------------------------------------------------------------------- 1 | ; Properties 2 | ;----------- 3 | 4 | (table [ 5 | (bare_key) 6 | (dotted_key) 7 | (quoted_key) 8 | ] @type) 9 | 10 | (table_array_element [ 11 | (bare_key) 12 | (dotted_key) 13 | (quoted_key) 14 | ] @type) 15 | 16 | (pair [ 17 | (bare_key) 18 | (dotted_key) 19 | (quoted_key) 20 | ] @variable.other.member) 21 | 22 | ; Literals 23 | ;--------- 24 | 25 | (boolean) @constant.builtin.boolean 26 | (comment) @comment 27 | (string) @string 28 | (integer) @constant.numeric.integer 29 | (float) @constant.numeric.float 30 | (offset_date_time) @string.special 31 | (local_date_time) @string.special 32 | (local_date) @string.special 33 | (local_time) @string.special 34 | 35 | ; Punctuation 36 | ;------------ 37 | 38 | "." @punctuation.delimiter 39 | "," @punctuation.delimiter 40 | 41 | "=" @operator 42 | 43 | "[" @punctuation.bracket 44 | "]" @punctuation.bracket 45 | "[[" @punctuation.bracket 46 | "]]" @punctuation.bracket 47 | "{" @punctuation.bracket 48 | "}" @punctuation.bracket -------------------------------------------------------------------------------- /langs/typescript/highlights.scm: -------------------------------------------------------------------------------- 1 | ; Variables 2 | 3 | (identifier) @variable 4 | 5 | ; Properties 6 | 7 | (property_identifier) @property 8 | (shorthand_property_identifier) @property 9 | (shorthand_property_identifier_pattern) @property 10 | 11 | ; Function and method calls 12 | 13 | (call_expression 14 | function: (identifier) @function) 15 | 16 | (call_expression 17 | function: (member_expression 18 | property: (property_identifier) @function.method)) 19 | 20 | ; Function and method definitions 21 | 22 | (function_expression 23 | name: (identifier) @function) 24 | (function_declaration 25 | name: (identifier) @function) 26 | (method_definition 27 | name: (property_identifier) @function.method) 28 | 29 | (pair 30 | key: (property_identifier) @function.method 31 | value: [(function_expression) (arrow_function)]) 32 | 33 | (assignment_expression 34 | left: (member_expression 35 | property: (property_identifier) @function.method) 36 | right: [(function_expression) (arrow_function)]) 37 | 38 | (variable_declarator 39 | name: (identifier) @function 40 | value: [(function_expression) (arrow_function)]) 41 | 42 | (assignment_expression 43 | left: (identifier) @function 44 | right: [(function_expression) (arrow_function)]) 45 | 46 | ; Special identifiers 47 | 48 | ((identifier) @constructor 49 | (#match? @constructor "^[A-Z]")) 50 | 51 | ((identifier) @type 52 | (#match? @type "^[A-Z]")) 53 | (type_identifier) @type 54 | (predefined_type) @type.builtin 55 | 56 | ([ 57 | (identifier) 58 | (shorthand_property_identifier) 59 | (shorthand_property_identifier_pattern) 60 | ] @constant 61 | (#match? @constant "^_*[A-Z_][A-Z\\d_]*$")) 62 | 63 | ; Literals 64 | 65 | (this) @variable.special 66 | (super) @variable.special 67 | 68 | [ 69 | (null) 70 | (undefined) 71 | ] @constant.builtin 72 | 73 | [ 74 | (true) 75 | (false) 76 | ] @boolean 77 | 78 | (comment) @comment 79 | 80 | [ 81 | (string) 82 | (template_string) 83 | (template_literal_type) 84 | ] @string 85 | 86 | (escape_sequence) @string.escape 87 | 88 | (regex) @string.regex 89 | (number) @number 90 | 91 | ; Tokens 92 | 93 | [ 94 | ";" 95 | "?." 96 | "." 97 | "," 98 | ":" 99 | "?" 100 | ] @punctuation.delimiter 101 | 102 | [ 103 | "..." 104 | "-" 105 | "--" 106 | "-=" 107 | "+" 108 | "++" 109 | "+=" 110 | "*" 111 | "*=" 112 | "**" 113 | "**=" 114 | "/" 115 | "/=" 116 | "%" 117 | "%=" 118 | "<" 119 | "<=" 120 | "<<" 121 | "<<=" 122 | "=" 123 | "==" 124 | "===" 125 | "!" 126 | "!=" 127 | "!==" 128 | "=>" 129 | ">" 130 | ">=" 131 | ">>" 132 | ">>=" 133 | ">>>" 134 | ">>>=" 135 | "~" 136 | "^" 137 | "&" 138 | "|" 139 | "^=" 140 | "&=" 141 | "|=" 142 | "&&" 143 | "||" 144 | "??" 145 | "&&=" 146 | "||=" 147 | "??=" 148 | ] @operator 149 | 150 | [ 151 | "(" 152 | ")" 153 | "[" 154 | "]" 155 | "{" 156 | "}" 157 | ] @punctuation.bracket 158 | 159 | (ternary_expression 160 | [ 161 | "?" 162 | ":" 163 | ] @operator 164 | ) 165 | 166 | [ 167 | "as" 168 | "async" 169 | "await" 170 | "break" 171 | "case" 172 | "catch" 173 | "class" 174 | "const" 175 | "continue" 176 | "debugger" 177 | "default" 178 | "delete" 179 | "do" 180 | "else" 181 | "export" 182 | "extends" 183 | "finally" 184 | "for" 185 | "from" 186 | "function" 187 | "get" 188 | "if" 189 | "import" 190 | "in" 191 | "instanceof" 192 | "is" 193 | "let" 194 | "new" 195 | "of" 196 | "return" 197 | "satisfies" 198 | "set" 199 | "static" 200 | "switch" 201 | "target" 202 | "throw" 203 | "try" 204 | "typeof" 205 | "using" 206 | "var" 207 | "void" 208 | "while" 209 | "with" 210 | "yield" 211 | ] @keyword 212 | 213 | (template_substitution 214 | "${" @punctuation.special 215 | "}" @punctuation.special) @embedded 216 | 217 | (template_type 218 | "${" @punctuation.special 219 | "}" @punctuation.special) @embedded 220 | 221 | (type_arguments 222 | "<" @punctuation.bracket 223 | ">" @punctuation.bracket) 224 | 225 | ; Keywords 226 | 227 | [ "abstract" 228 | "declare" 229 | "enum" 230 | "export" 231 | "implements" 232 | "infer" 233 | "interface" 234 | "keyof" 235 | "namespace" 236 | "private" 237 | "protected" 238 | "public" 239 | "type" 240 | "readonly" 241 | "override" 242 | ] @keyword -------------------------------------------------------------------------------- /langs/yaml/highlights.scm: -------------------------------------------------------------------------------- 1 | 2 | (block_mapping_pair 3 | key: (flow_node [(double_quote_scalar) (single_quote_scalar)] @identifier)) 4 | (block_mapping_pair 5 | key: (flow_node (plain_scalar (string_scalar) @identifier))) 6 | 7 | (flow_mapping 8 | (_ key: (flow_node [(double_quote_scalar) (single_quote_scalar)] @identifier))) 9 | (flow_mapping 10 | (_ key: (flow_node (plain_scalar (string_scalar) @identifier)))) 11 | 12 | (boolean_scalar) @boolean 13 | (null_scalar) @constant.builtin 14 | (double_quote_scalar) @string 15 | (single_quote_scalar) @string 16 | ((block_scalar) @string (#set! "priority" 99)) 17 | ;(string_scalar) @string 18 | (plain_scalar) @identifier 19 | (escape_sequence) @string.escape 20 | (integer_scalar) @number 21 | (float_scalar) @number 22 | (comment) @comment @spell 23 | (anchor_name) @type 24 | (alias_name) @type 25 | (tag) @type 26 | (ERROR) @error 27 | 28 | [ 29 | (yaml_directive) 30 | (tag_directive) 31 | (reserved_directive) 32 | ] @preproc 33 | 34 | 35 | [ 36 | "," 37 | "-" 38 | ":" 39 | ">" 40 | "?" 41 | "|" 42 | ] @punctuation.delimiter 43 | 44 | [ 45 | "[" 46 | "]" 47 | "{" 48 | "}" 49 | ] @punctuation.bracket 50 | 51 | [ 52 | "*" 53 | "&" 54 | "---" 55 | "..." 56 | ] @punctuation.special 57 | -------------------------------------------------------------------------------- /langs/zig/highlights.scm: -------------------------------------------------------------------------------- 1 | [ 2 | (container_doc_comment) 3 | (doc_comment) 4 | ] @comment.documentation 5 | 6 | [ 7 | (line_comment) 8 | ] @comment.line 9 | 10 | ;; assume TitleCase is a type 11 | ( 12 | [ 13 | variable_type_function: (IDENTIFIER) 14 | field_access: (IDENTIFIER) 15 | parameter: (IDENTIFIER) 16 | ] @type 17 | (#match? @type "^[A-Z]([a-z]+[A-Za-z0-9]*)+$") 18 | ) 19 | 20 | ;; assume camelCase is a function 21 | ( 22 | [ 23 | variable_type_function: (IDENTIFIER) 24 | field_access: (IDENTIFIER) 25 | parameter: (IDENTIFIER) 26 | ] @function 27 | (#match? @function "^[a-z]+([A-Z][a-z0-9]*)+$") 28 | ) 29 | 30 | ;; assume all CAPS_1 is a constant 31 | ( 32 | [ 33 | variable_type_function: (IDENTIFIER) 34 | field_access: (IDENTIFIER) 35 | ] @constant 36 | (#match? @constant "^[A-Z][A-Z_0-9]+$") 37 | ) 38 | 39 | ;; _ 40 | ( 41 | (IDENTIFIER) @variable.builtin 42 | (#eq? @variable.builtin "_") 43 | ) 44 | 45 | ;; C Pointers [*c]T 46 | (PtrTypeStart "c" @variable.builtin) 47 | 48 | [ 49 | variable: (IDENTIFIER) 50 | variable_type_function: (IDENTIFIER) 51 | ] @variable 52 | 53 | parameter: (IDENTIFIER) @variable.parameter 54 | 55 | [ 56 | field_member: (IDENTIFIER) 57 | field_access: (IDENTIFIER) 58 | ] @variable.other.member 59 | 60 | [ 61 | function_call: (IDENTIFIER) 62 | function: (IDENTIFIER) 63 | ] @function 64 | 65 | exception: "!" @keyword.control.exception 66 | 67 | field_constant: (IDENTIFIER) @constant 68 | 69 | (BUILTINIDENTIFIER) @function.builtin 70 | 71 | ((BUILTINIDENTIFIER) @keyword.control.import 72 | (#any-of? @keyword.control.import "@import" "@cImport")) 73 | 74 | (INTEGER) @constant.numeric.integer 75 | 76 | (FLOAT) @constant.numeric.float 77 | 78 | [ 79 | (LINESTRING) 80 | (STRINGLITERALSINGLE) 81 | ] @string 82 | 83 | (CHAR_LITERAL) @constant.character 84 | (EscapeSequence) @constant.character.escape 85 | (FormatSequence) @string.special 86 | 87 | [ 88 | "anytype" 89 | "anyframe" 90 | (BuildinTypeExpr) 91 | ] @type.builtin 92 | 93 | (BreakLabel (IDENTIFIER) @label) 94 | (BlockLabel (IDENTIFIER) @label) 95 | 96 | [ 97 | "true" 98 | "false" 99 | ] @constant.builtin.boolean 100 | 101 | [ 102 | "undefined" 103 | "unreachable" 104 | "null" 105 | ] @constant.builtin 106 | 107 | [ 108 | "else" 109 | "if" 110 | "switch" 111 | ] @keyword.control.conditional 112 | 113 | [ 114 | "for" 115 | "while" 116 | ] @keyword.control.repeat 117 | 118 | [ 119 | "or" 120 | "and" 121 | "orelse" 122 | ] @keyword.operator 123 | 124 | [ 125 | "enum" 126 | ] @type.enum 127 | 128 | [ 129 | "struct" 130 | "union" 131 | "packed" 132 | "opaque" 133 | "export" 134 | "extern" 135 | "linksection" 136 | ] @keyword.storage.type 137 | 138 | [ 139 | "const" 140 | "var" 141 | "threadlocal" 142 | "allowzero" 143 | "volatile" 144 | "align" 145 | ] @keyword.storage.modifier 146 | 147 | [ 148 | "try" 149 | "error" 150 | "catch" 151 | ] @keyword.control.exception 152 | 153 | [ 154 | "fn" 155 | ] @keyword.function 156 | 157 | [ 158 | "test" 159 | ] @keyword 160 | 161 | [ 162 | "pub" 163 | "usingnamespace" 164 | ] @keyword.control.import 165 | 166 | [ 167 | "return" 168 | "break" 169 | "continue" 170 | ] @keyword.control.return 171 | 172 | [ 173 | "defer" 174 | "errdefer" 175 | "async" 176 | "nosuspend" 177 | "await" 178 | "suspend" 179 | "resume" 180 | ] @function.macro 181 | 182 | [ 183 | "comptime" 184 | "inline" 185 | "noinline" 186 | "asm" 187 | "callconv" 188 | "noalias" 189 | ] @keyword.directive 190 | 191 | [ 192 | (CompareOp) 193 | (BitwiseOp) 194 | (BitShiftOp) 195 | (AdditionOp) 196 | (AssignOp) 197 | (MultiplyOp) 198 | (PrefixOp) 199 | "*" 200 | "**" 201 | "->" 202 | ".?" 203 | ".*" 204 | "?" 205 | ] @operator 206 | 207 | [ 208 | ";" 209 | "." 210 | "," 211 | ":" 212 | ] @punctuation.delimiter 213 | 214 | [ 215 | ".." 216 | "..." 217 | ] @punctuation.special 218 | 219 | [ 220 | "[" 221 | "]" 222 | "(" 223 | ")" 224 | "{" 225 | "}" 226 | (Payload "|") 227 | (PtrPayload "|") 228 | (PtrIndexPayload "|") 229 | ] @punctuation.bracket 230 | 231 | (ERROR) @keyword.control.exception -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # RED 2 | 3 | ## Introduction 4 | 5 | `red` is a console-based text editor designed to be simple and efficient. It provides a straightforward interface for users who prefer command-line editing. 6 | 7 | 8 | ![editor](img.png) 9 | 10 | ## Features 11 | 12 | ### Intuitive 13 | 14 | `red` provides a clean and straightforward interface, making it easy to navigate and edit text efficiently. `red` also features intuitive key bindings, allowing users to perform common tasks quickly and effortlessly. 15 | 16 | ### Performance 17 | 18 | `red` leverages Rust's efficiency for lightning-fast text editing, ensuring that it consumes fewer resources as possble. With optimized cursor movement, content redraw, and Tree Sitter integration for highlighting, it ensures a responsive and efficient editing experience. 19 | 20 | ### Mouse Support 21 | 22 | `red` provides robust mouse and touchpad support, enabling smooth navigation, scrolling, selection. Furthermore, `red` is designed with keyboard-only users in mind, ensuring a seamless experience for all input preferences. 23 | 24 | ### Code execution 25 | With `red`, executing code to a Tmux session is as simple as a button click. Enjoy the convenience of seamlessly integrating code execution with your Tmux sessions, streamlining your workflow with ease. 26 | 27 | ### Lsp 28 | `red` supports the Language Server Protocol (LSP), enhancing your coding experience with intelligent code completion, real-time diagnostics, go to definition, references and more. 29 | 30 | ### Search 31 | `red` offers both local and global search capabilities, allowing users to effortlessly find text within their documents. With simple match movement, navigating through search results is intuitive and efficient, ensuring a seamless editing experience. 32 | 33 | ### Themes 34 | `red` comes with a variety of themes to personalize your editing experience. Choose from a selection of carefully crafted themes or create your own to suit your preferences. Compare to others terminal editors, red distinguishes itself by prioritizing background color customization, allowing the window to be fully transparent in accordance with glassmorphism design trends. 35 | 36 | ### Discussion 37 | 38 | https://t.me/red_rs_editor 39 | 40 | ### Prerequisites 41 | 42 | - Rust: Ensure you have Rust installed on your system. 43 | - Cargo: `red` is built using Cargo, Rust's package manager. 44 | 45 | ## Installation 46 | 47 | 1. Clone the repository: 48 | ```bash 49 | git clone https://github.com/red-rs/red.git 50 | ``` 51 | 52 | 2. Navigate to the project directory: 53 | ```bash 54 | cd red 55 | ``` 56 | 57 | 3. Install Rust: 58 | ```bash 59 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 60 | source $HOME/.cargo/env 61 | ``` 62 | 63 | 4. Build the project: 64 | ```bash 65 | cargo build --release 66 | ``` 67 | 68 | 5. Set the RED_HOME env, add it to the PATH and apply (~/.bashrc or ~/.zshrc): 69 | ```bash 70 | # bash 71 | echo "export RED_HOME=$(pwd)" >> ~/.bashrc 72 | echo "export PATH=$RED_HOME/target/release:$PATH" >> ~/.bashrc 73 | source ~/.bashrc 74 | 75 | # zsh 76 | echo "export RED_HOME=$(pwd)" >> ~/.zshrc 77 | echo "export PATH=$RED_HOME/target/release:$PATH" >> ~/.zshrc 78 | source ~/.zshrc 79 | ``` 80 | 6. Run red: 81 | ```bash 82 | red file.txt 83 | ``` 84 | 85 | ## Key bindings and features: 86 | - `Control + q` - quit 87 | - `Control + s` - save 88 | - `Control + c` - copy 89 | - `Control + v` - paste 90 | - `Control + x` - cut 91 | - `Control + d` - duplicate 92 | - `Control + z` - undo 93 | - `Control + f` - find 94 | - `Control + f, type prefix, Control + g` - global find 95 | - `Control + o` - cursor back 96 | - `Control + p` - cursor forward 97 | 98 | - `Shift + arrow` - select text 99 | - `Option + right/left` - smart horizontal movement 100 | - `Option + down/up` - smart selection 101 | - `Option + delete` - delete line 102 | - `Option + /` - comment line 103 | - `Control + Shift + down/up` - lines swap 104 | 105 | - `mouse selection` - select text 106 | - `mouse double click` - select word 107 | - `mouse triple click` - select line 108 | 109 | 110 | - `Control + space` - lsp completion 111 | - `Control + h` - lsp hover 112 | - `Control + g / Control + mouse click` - lsp definition 113 | - `Control + r / Option + mouse click` - lsp references 114 | - `Control + e` - lsp diagnostic (errors) 115 | 116 | ## LSP 117 | 118 | `red` assumes that you will install the LSP server by yourself and lsp is available from the terminal. 119 | 120 | ```shell 121 | 122 | # rust 123 | # curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 124 | rustup component add rust-analyzer 125 | 126 | # python 127 | pip install -U 'python-lsp-server[all]' 128 | 129 | # js/ts 130 | npm i -g typescript typescript-language-server 131 | 132 | # html 133 | npm i -g vscode-langservers-extracted 134 | 135 | # c/c++ 136 | # go to https://clangd.llvm.org/installation.html 137 | clangd 138 | 139 | # go 140 | go install golang.org/x/tools/gopls@latest 141 | 142 | # java 143 | # jdtls 144 | 145 | # kotlin 146 | # https://github.com/fwcd/kotlin-language-server 147 | # mac 148 | brew install kotlin-language-server 149 | 150 | # swift 151 | # https://github.com/apple/sourcekit-lsp 152 | xcrun 153 | 154 | # bash 155 | npm i -g bash-language-server 156 | 157 | 158 | # zig 159 | # mac 160 | brew install zls 161 | 162 | ``` 163 | 164 | You can change the LSP settings in the `config.toml` file. 165 | 166 | ## Performance Comparison 167 | 168 | In the following test cases, the performance of `red` is compared to other popular editors, including Neovim (`nvim`), Helix, IntelliJ IDEA (`idea`), and different terminal emulators. 169 | 170 | | Test Case | `red` | `nvim` | `nvim (astro)` | `helix` | `red+alacritty`|`red+alacritty +rust-analyzer` | `idea` | `red+mac terminal` | `red+iterm` | 171 | | ---------------------- |-------|--------|----------------|---------| ---------------| -------- | ----- | ------------------ | ----------- | 172 | | Cursor movement, cpu % | 0.5 | 5 | 6 | 4 | 0.4 + 12 | | 60 | 25 | 30 | 173 | | Arrow scrolling, cpu % | 3 | 7 | 20 | 6 | 3+20 | | 65 | 40 | 50 | 174 | | Mouse scrolling, cpu % | 15 | 30 | 45 | 30 | 20+30 | | 55 | 60 | 130 | 175 | | Text editing, cpu % | 5 | 10 | 30 | 8 | 4+20 | 4+20+70 | 600 | 40 | 45 | 176 | 177 | -------------------------------------------------------------------------------- /red1.log: -------------------------------------------------------------------------------- 1 | [2024-11-05 17:22:40.729] [DEBUG] main: starting red 2 | [2024-11-05 17:22:40.729] [DEBUG] config: RED_HOME: /Users/max/apps/rust/red 3 | [2024-11-05 17:22:40.729] [DEBUG] config: config path: /Users/max/apps/rust/red/config.toml 4 | [2024-11-05 17:22:41.724] [DEBUG] lsp: -> {"id":0,"jsonrpc":"2.0","method":"initialize","params":{"capabilities":{"textDocument":{"completion":{"completionItem":{"insertReplaceSupport":true,"labelDetailsSupport":true,"resolveProvider":true,"resolveSupport":{"properties":["documentation","detail","additionalTextEdits"]},"snippetSupport":false}},"hover":{"contentFormat":["plaintext"]},"publishDiagnostics":{"codeDescriptionSupport":true,"dataSupport":true,"relatedInformation":false,"versionSupport":false},"signatureHelp":{"signatureInformation":{"documentationFormat":["plaintext"]}},"synchronization":{"dynamicRegistration":true}}},"clientInfo":{"name":"red","version":"1.0.0"},"processId":12680,"rootPath":"/Users/max/apps/rust/red","rootUri":"file:///Users/max/apps/rust/red","workspaceFolders":[{"name":"red","uri":"file:///Users/max/apps/rust/red"}]}} 5 | [2024-11-05 17:22:42.174] [DEBUG] lsp: <- {"jsonrpc":"2.0","id":0,"result":{"capabilities":{"textDocumentSync":1,"completionProvider":{"resolveProvider":true,"triggerCharacters":["$","{"]},"hoverProvider":true,"documentHighlightProvider":true,"definitionProvider":true,"documentSymbolProvider":true,"workspaceSymbolProvider":true,"referencesProvider":true,"codeActionProvider":{"codeActionKinds":["quickfix"],"resolveProvider":false,"workDoneProgress":false}}}} 6 | [2024-11-05 17:22:42.174] [DEBUG] lsp: -> {"jsonrpc":"2.0","method":"initialized","params":{}} 7 | [2024-11-05 17:22:42.185] [DEBUG] lsp: <- {"jsonrpc":"2.0","method":"window/logMessage","params":{"type":3,"message":"14:22:42.174 INFO BackgroundAnalysis: resolving glob \"**/*@(.sh|.inc|.bash|.command)\" inside \"file:///Users/max/apps/rust/red\"..."}} 8 | [2024-11-05 17:22:42.185] [DEBUG] lsp: -> {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"languageId":"shell","text":"#!/bin/sh\n\nPANES=$(tmux list-panes | wc -l)\n\nif [ \"$PANES\" -le 1 ]; then\n tmux split-window -v\nfi\n\ntmux send-keys -t 1 \"$@\" Enter\necho \"$1\" > /tmp/prev-tmux-command","uri":"file:///Users/max/apps/rust/red/tmux.sh","version":0}}} 9 | [2024-11-05 17:22:42.255] [DEBUG] lsp: <- {"jsonrpc":"2.0","method":"window/logMessage","params":{"type":3,"message":"14:22:42.255 INFO BackgroundAnalysis: Glob resolved with 3 files after 0.075 seconds"}} 10 | [2024-11-05 17:22:42.257] [DEBUG] lsp: <- {"jsonrpc":"2.0","method":"window/logMessage","params":{"type":3,"message":"14:22:42.257 INFO BackgroundAnalysis: Completed after 0.077 seconds."}} 11 | [2024-11-05 17:22:42.453] [DEBUG] main: stopping red 12 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::path::Path; 3 | 4 | #[derive(Debug, Deserialize, Clone)] 5 | pub struct Config { 6 | pub theme: String, 7 | pub left_panel_width: Option, 8 | pub language: Vec, 9 | } 10 | 11 | #[derive(Debug, Deserialize, Clone)] 12 | pub struct Language { 13 | pub name: String, 14 | pub types: Vec, 15 | pub comment: String, 16 | pub lsp: Option>, 17 | pub indent: IndentConfig, 18 | pub executable: Option, 19 | pub exec: Option, 20 | pub exectest: Option, 21 | } 22 | 23 | #[derive(Debug, Deserialize, Clone)] 24 | pub struct IndentConfig { 25 | pub width: i32, 26 | pub unit: String, 27 | } 28 | 29 | pub fn get() -> Config { 30 | let red_home = std::env::var("RED_HOME").expect("RED_HOME must be set"); 31 | let config_path = Path::new(&red_home).join("config.toml"); 32 | let toml_str = std::fs::read_to_string(config_path).expect("Unable to read config.toml file"); 33 | let config: Config = toml::from_str(&toml_str).expect("Unable to parse TOML"); 34 | config 35 | } 36 | 37 | #[cfg(test)] 38 | mod congif_tests { 39 | #[test] 40 | fn test_read_config() { 41 | let config = crate::config::get(); 42 | 43 | println!("Theme: {}", config.theme); 44 | println!(); 45 | 46 | for language in config.language { 47 | println!("Language: {}", language.name); 48 | println!("File Types: {:?}", language.types); 49 | println!("Comment Token: {}", language.comment); 50 | println!("LSP: {:?}", language.lsp); 51 | println!("Indent: {:?}", language.indent); 52 | println!(); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/lsp.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use serde_json::Result; 3 | use serde_json::{json, Value}; 4 | use std::collections::{HashMap, HashSet}; 5 | use std::io::Error; 6 | use std::process::Stdio; 7 | use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; 8 | use std::sync::Arc; 9 | use tokio::sync::Mutex; 10 | use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}; 11 | use tokio::process::Command; 12 | use tokio::sync::mpsc; 13 | use tokio::sync::oneshot; 14 | use tokio::time::{self}; 15 | use tokio::time::{sleep, Duration}; 16 | use tokio::io::{self}; 17 | 18 | use self::lsp_messages::{ 19 | CompletionResponse, CompletionResponse2, CompletionResult, 20 | DefinitionResponse, DefinitionResult, 21 | DiagnosticParams, 22 | HoverResponse, HoverResult, 23 | ReferencesResponse, ReferencesResult 24 | }; 25 | 26 | use log2::*; 27 | 28 | pub struct Lsp { 29 | lang: String, 30 | kill_send: Option>, 31 | stdin_send: Option>, 32 | next_id: AtomicUsize, 33 | versions: HashMap, 34 | pending: Arc>>>, 35 | ready: AtomicBool, 36 | opened: HashSet, 37 | } 38 | 39 | impl Lsp { 40 | pub fn new() -> Self { 41 | Self { 42 | lang: String::new(), 43 | kill_send: None, 44 | stdin_send: None, 45 | next_id: AtomicUsize::new(1), 46 | versions: HashMap::new(), 47 | pending: Arc::new(Mutex::new(HashMap::new())), 48 | ready: AtomicBool::new(false), 49 | opened: HashSet::new(), 50 | } 51 | } 52 | 53 | pub fn start( 54 | &mut self, 55 | lang: &str, 56 | cmd: &str, 57 | diagnostic_updates: Option> 58 | )-> io::Result<()> { 59 | 60 | let s: Vec<&str> = cmd.split(" ").collect(); 61 | let cmd = s[0]; 62 | let args = &s[1..]; 63 | 64 | self.lang = lang.to_string(); 65 | 66 | let (kill_send, mut kill_recv) = tokio::sync::mpsc::channel::<()>(1); 67 | self.kill_send = Some(kill_send); 68 | 69 | let (stdin_send, mut stdin_recv) = tokio::sync::mpsc::channel::(1); 70 | self.stdin_send = Some(stdin_send); 71 | 72 | // spawn lsp process 73 | let mut child = Command::new(cmd) 74 | .args(args) 75 | .stdin(Stdio::piped()) 76 | .stdout(Stdio::piped()) 77 | .stderr(Stdio::piped()) 78 | .spawn()?; 79 | 80 | let mut stdin = child.stdin.take().unwrap(); 81 | let mut stdout = child.stdout.take().unwrap(); 82 | 83 | // reading from channel and write to child stdin 84 | tokio::spawn(async move { 85 | while let Some(m) = stdin_recv.recv().await { 86 | debug!("-> {}", m); 87 | let header = format!("Content-Length: {}\r\n\r\n", m.len()); 88 | stdin.write_all(header.as_bytes()).await; 89 | stdin.write_all(m.as_bytes()).await; 90 | stdin.flush().await; 91 | } 92 | }); 93 | 94 | let pending = self.pending.clone(); 95 | 96 | // reading from child stdout 97 | tokio::spawn(async move { 98 | let mut reader = BufReader::new(stdout); 99 | 100 | loop { 101 | let mut size = None; 102 | let mut buf = String::new(); 103 | 104 | loop { 105 | buf.clear(); 106 | if reader.read_line(&mut buf).await.unwrap_or(0) == 0 { return; } 107 | if !buf.ends_with("\r\n") { return; } 108 | let buf = &buf[..buf.len() - 2]; 109 | if buf.is_empty() { break; } 110 | let mut parts = buf.splitn(2, ": "); 111 | let header_name = parts.next().unwrap(); 112 | let header_value = parts.next().unwrap(); 113 | if header_name == "Content-Length" { 114 | size = Some(header_value.parse::().unwrap()); 115 | } 116 | } 117 | 118 | let content_length: usize = size.unwrap(); 119 | let mut content = vec![0; content_length]; 120 | reader.read_exact(&mut content).await; 121 | 122 | let msg = std::str::from_utf8(&content).expect("invalid utf8 from server"); 123 | 124 | debug!("<- {}", msg); 125 | 126 | let parsed_json: Value = serde_json::from_str(msg).unwrap(); 127 | 128 | if let Some(id) = parsed_json["id"].as_u64() { // response 129 | let id = id as usize; 130 | if let Some(sender) = pending.lock().await.get(&id) { 131 | let s = sender.clone(); 132 | let msg = msg.to_string(); 133 | tokio::spawn(async move { 134 | s.send(msg).await; // send to request channel 135 | }); 136 | } 137 | } 138 | 139 | 140 | if let Some(method) = parsed_json["method"].as_str() { 141 | if method.eq("textDocument/publishDiagnostics") { 142 | match serde_json::from_str::(&msg) { 143 | Ok(d) => { 144 | match diagnostic_updates.as_ref() { 145 | Some(diagnostic_send) => { 146 | diagnostic_send.send(d.params).await; 147 | }, 148 | None => {}, 149 | } 150 | 151 | }, 152 | Err(e) => { 153 | error!("<- {:?} ", e); 154 | }, 155 | } 156 | } 157 | } 158 | 159 | } 160 | }); 161 | 162 | // wait for child end or kill 163 | tokio::spawn(async move { 164 | tokio::select! { 165 | _ = child.wait() => { 166 | debug!("lsp process wait done"); 167 | } 168 | _ = kill_recv.recv() => { 169 | child.kill().await.expect("kill failed"); 170 | debug!("lsp process killed manually"); 171 | } 172 | } 173 | }); 174 | 175 | Ok(()) 176 | } 177 | 178 | pub async fn stop(&mut self) { 179 | if let Some(kill_send) = self.kill_send.take() { 180 | kill_send.send(()).await.expect("Failed to send kill signal"); 181 | } 182 | } 183 | 184 | fn send_async(&self, message: String) { 185 | if let Some(stdin_send) = &self.stdin_send { 186 | let stdin_send = stdin_send.clone(); 187 | tokio::spawn(async move { 188 | if let Err(err) = stdin_send.send(message).await { 189 | error!("Failed to send message: {:?}", err); 190 | } 191 | }); 192 | } 193 | } 194 | pub async fn add_pending(&mut self, id: usize, sender: mpsc::Sender) { 195 | self.pending.lock().await.insert(id, sender); 196 | } 197 | pub async fn remove_pending(&mut self, id: usize) { 198 | self.pending.lock().await.remove(&id); 199 | } 200 | 201 | pub async fn wait_for(&mut self, id: usize, mut rx: mpsc::Receiver) -> Option { 202 | let timeout = time::sleep(Duration::from_secs(1)); 203 | tokio::pin!(timeout); 204 | 205 | tokio::select! { 206 | msg = rx.recv() => msg, 207 | _ = &mut timeout => None 208 | } 209 | } 210 | 211 | pub async fn init(&mut self, dir: &str) { 212 | let id = 0; 213 | let message = lsp_messages::initialize(dir); 214 | 215 | let (tx, mut rx) = tokio::sync::mpsc::channel::(1); 216 | self.add_pending(id, tx).await; 217 | 218 | self.send_async(message); 219 | 220 | let result = self.wait_for(id, rx).await; 221 | self.remove_pending(id).await; 222 | 223 | self.initialized(); 224 | tokio::time::sleep(Duration::from_millis(10)).await; 225 | 226 | self.ready.store(true, Ordering::SeqCst) 227 | } 228 | 229 | pub fn is_ready(&mut self) -> bool { 230 | self.ready.load(Ordering::SeqCst) 231 | } 232 | 233 | pub fn initialized(&mut self) { 234 | let message = lsp_messages::initialized(); 235 | self.send_async(message); 236 | } 237 | 238 | pub fn did_open(&mut self, lang: &str, path: &str, text: &str) { 239 | if self.opened.contains(path) { return; } 240 | 241 | self.opened.insert(path.to_string()); 242 | 243 | let message = lsp_messages::did_open(lang, path, text); 244 | self.send_async(message); 245 | } 246 | 247 | fn get_next_version(&mut self, path: &str) -> usize { 248 | let version = self.versions.entry(path.to_string()) 249 | .or_insert_with(|| AtomicUsize::new(0)); 250 | 251 | version.fetch_add(1, Ordering::SeqCst) 252 | } 253 | 254 | fn get_next_id(&mut self, ) -> usize { 255 | self.next_id.fetch_add(1, Ordering::SeqCst) 256 | } 257 | 258 | pub async fn did_change(&mut self, 259 | line: usize,character: usize, 260 | line_end: usize,character_end: usize, 261 | path: &str, text: &str, 262 | ) { 263 | if !self.is_ready() { return; } 264 | 265 | let version = self.get_next_version(path); 266 | 267 | let message = lsp_messages::did_change( 268 | line, character, 269 | line_end, character_end, 270 | path, text, version, 271 | ); 272 | self.send_async(message); 273 | } 274 | 275 | pub async fn completion( 276 | &mut self, path: &str, line: usize, character: usize 277 | ) -> Option { 278 | if !self.is_ready() { return None; } 279 | 280 | let id = self.get_next_id(); 281 | let message = json!({ 282 | "id": id, "jsonrpc": "2.0", "method": "textDocument/completion", 283 | "params": { 284 | "textDocument": { "uri": format!("file://{}", path) }, 285 | "position": { "line": line, "character": character }, 286 | "context": { "triggerKind": 1 } 287 | } 288 | }); 289 | 290 | let (tx, rx) = mpsc::channel::(1); 291 | self.add_pending(id, tx).await; 292 | self.send_async(message.to_string()); 293 | 294 | let result = self.wait_for(id, rx).await; 295 | self.remove_pending(id).await; 296 | 297 | result.and_then(|message| { 298 | let res = serde_json::from_str::(&message) 299 | .map_err(|e| debug!("lsp json parsing error {}", e)) 300 | .ok().and_then(|r| r.result); 301 | 302 | if res.is_some() { res } 303 | else { 304 | // try to parse to CompletionResponse2 305 | serde_json::from_str::(&message) 306 | .map_err(|e| debug!("lsp json parsing error {}", e)) 307 | .ok().and_then(|r| Some(CompletionResult { 308 | isIncomplete: Some(false), items: r.result 309 | })) 310 | } 311 | }) 312 | } 313 | 314 | pub async fn definition( 315 | &mut self, path: &str, line: usize, character: usize 316 | ) -> Option> { 317 | if !self.is_ready() { return None; } 318 | 319 | let id = self.get_next_id(); 320 | let message = json!({ 321 | "id": id, "jsonrpc": "2.0", "method": "textDocument/definition", 322 | "params": { 323 | "textDocument": { "uri": format!("file://{}", path) }, 324 | "position": { "line": line, "character": character }, 325 | } 326 | }); 327 | 328 | let (tx, rx) = mpsc::channel::(1); 329 | self.add_pending(id, tx).await; 330 | self.send_async(message.to_string()); 331 | 332 | let result = self.wait_for(id, rx).await; 333 | self.remove_pending(id).await; 334 | 335 | result.and_then(|message| { 336 | serde_json::from_str::(&message) 337 | .map_err(|e| debug!("lsp json parsing error {}", e)) 338 | .ok().and_then(|r| Some(r.result)) 339 | }) 340 | } 341 | 342 | pub async fn references( 343 | &mut self, path: &str, line: usize, character: usize 344 | ) -> Option> { 345 | if !self.is_ready() { return None; } 346 | 347 | let id = self.get_next_id(); 348 | let message = json!({ 349 | "id": id, "jsonrpc": "2.0", "method": "textDocument/references", 350 | "params": { 351 | "textDocument": { "uri": format!("file://{}", path) }, 352 | "position": { "line": line, "character": character }, 353 | "context" : { "includeDeclaration": false } 354 | } 355 | }); 356 | 357 | let (tx, rx) = mpsc::channel::(1); 358 | self.add_pending(id, tx).await; 359 | self.send_async(message.to_string()); 360 | 361 | let result = self.wait_for(id, rx).await; 362 | self.remove_pending(id).await; 363 | 364 | result.and_then(|message| { 365 | serde_json::from_str::(&message) 366 | .map_err(|e| debug!("lsp json parsing error {}", e)) 367 | .ok().and_then(|r| Some(r.result)) 368 | }) 369 | } 370 | 371 | 372 | pub async fn hover( 373 | &mut self, path: &str, line: usize, character: usize 374 | ) -> Option { 375 | if !self.is_ready() { return None; } 376 | 377 | let id = self.get_next_id(); 378 | let message = json!({ 379 | "id": id, "jsonrpc": "2.0", "method": "textDocument/hover", 380 | "params": { 381 | "textDocument": { "uri": format!("file://{}", path) }, 382 | "position": { "line": line, "character": character }, 383 | } 384 | }); 385 | 386 | let (tx, rx) = mpsc::channel::(1); 387 | self.add_pending(id, tx).await; 388 | self.send_async(message.to_string()); 389 | 390 | let result = self.wait_for(id, rx).await; 391 | self.remove_pending(id).await; 392 | 393 | result.and_then(|message| { 394 | serde_json::from_str::(&message) 395 | .map_err(|e| debug!("lsp json parsing error {}", e)) 396 | .ok().and_then(|r| Some(r.result)) 397 | }) 398 | } 399 | } 400 | 401 | #[tokio::test] 402 | async fn test_lsp() { 403 | let lang = "rust"; 404 | let mut lsp = Lsp::new(); 405 | 406 | lsp.start(lang, "rust-analyzer", None); 407 | println!("after lsp start"); 408 | 409 | sleep(Duration::from_secs(2)).await; 410 | 411 | let dir = "/Users/max/apps/rust/red"; 412 | lsp.init(dir); 413 | println!("after lsp init"); 414 | 415 | sleep(Duration::from_secs(2)).await; 416 | 417 | lsp.initialized(); 418 | println!("after lsp initialized"); 419 | 420 | sleep(Duration::from_secs(3)).await; 421 | 422 | let file_name = format!("{}/src/main.rs", dir); 423 | let file_content = std::fs::read_to_string(&file_name).unwrap(); 424 | 425 | lsp.did_open(lang, &file_name, &file_content); 426 | println!("after lsp did_open"); 427 | 428 | sleep(Duration::from_secs(5)).await; 429 | 430 | let cr = lsp.completion(&file_name, 17 - 1, 17 - 1).await; 431 | println!("after lsp completion"); 432 | 433 | match cr { 434 | Some(result) => { 435 | for item in result.items { 436 | println!("{}", item.label) 437 | } 438 | } 439 | None => {} 440 | } 441 | 442 | sleep(Duration::from_secs(3)).await; 443 | 444 | lsp.stop().await; 445 | println!("after stop"); 446 | 447 | sleep(Duration::from_secs(3)).await; 448 | } 449 | 450 | #[tokio::test] 451 | async fn main() { 452 | let (send, recv) = tokio::sync::oneshot::channel::<()>(); 453 | let (send2, mut recv2) = tokio::sync::mpsc::channel::(1); 454 | let send2_arc = Arc::new(send2).clone(); 455 | 456 | let mut child = Command::new("cat") 457 | .stdin(Stdio::piped()) 458 | .stdout(Stdio::piped()) 459 | .spawn() 460 | .expect("command not found"); 461 | 462 | let mut stdin = child.stdin.take().unwrap(); 463 | let mut stdout = child.stdout.take().unwrap(); 464 | 465 | // sending to child stdin task 466 | tokio::spawn(async move { 467 | while let Some(m) = recv2.recv().await { 468 | println!("sending message to child stdin {}", m); 469 | stdin.write_all(m.as_bytes()).await; 470 | } 471 | }); 472 | 473 | // reading child stdout task 474 | tokio::spawn(async move { 475 | let reader = BufReader::new(stdout); 476 | let mut lines = reader.lines(); 477 | 478 | println!("reading stdout task start"); 479 | while let Ok(Some(line)) = lines.next_line().await { 480 | println!("Received line from child: {}", line); 481 | } 482 | println!("reading stdout task end"); 483 | }); 484 | 485 | tokio::spawn(async move { 486 | sleep(Duration::from_secs(3)).await; 487 | send2_arc.send("hello\n".to_string()).await; 488 | }); 489 | 490 | // tokio::spawn(async move { 491 | // sleep(Duration::from_secs(2)).await; 492 | // println!("sending kill"); 493 | // send.send(()) 494 | // }); 495 | 496 | // wait for process stopped or killed 497 | tokio::select! { 498 | _ = child.wait() => { 499 | println!("child wait done, exited"); 500 | } 501 | _ = recv => { 502 | child.kill().await.expect("kill failed"); 503 | println!("killed"); 504 | } 505 | } 506 | } 507 | 508 | pub mod lsp_messages { 509 | use serde::{Deserialize, Serialize}; 510 | use serde_json::Result; 511 | use serde_json::{json, Value}; 512 | 513 | // todo, replace it to struct in the future 514 | 515 | pub fn initialize(dir: &str) -> String { 516 | json!({ 517 | "id": 0, 518 | "jsonrpc": "2.0", 519 | "method": "initialize", 520 | "params": { 521 | "rootPath": dir, 522 | "rootUri": format!("file://{}", dir), 523 | "processId": std::process::id(), 524 | "workspaceFolders": [ 525 | { 526 | "name": std::path::Path::new(dir).file_name().unwrap().to_str(), 527 | "uri": format!("file://{}", dir) 528 | } 529 | ], 530 | "clientInfo": { 531 | "name": "red", 532 | "version": "1.0.0" 533 | }, 534 | "capabilities": { 535 | "textDocument": { 536 | "synchronization": { 537 | "dynamicRegistration": true, 538 | }, 539 | "hover": { 540 | "contentFormat": [ 541 | "plaintext", 542 | ] 543 | }, 544 | "publishDiagnostics": { 545 | "relatedInformation": false, 546 | "versionSupport": false, 547 | "codeDescriptionSupport": true, 548 | "dataSupport": true 549 | }, 550 | "signatureHelp": { 551 | "signatureInformation": { 552 | "documentationFormat": [ 553 | "plaintext", 554 | ] 555 | } 556 | }, 557 | "completion": { 558 | "completionItem": { 559 | "resolveProvider": true, 560 | "snippetSupport": false, 561 | "insertReplaceSupport": true, 562 | "labelDetailsSupport": true, 563 | "resolveSupport": { 564 | "properties": [ 565 | "documentation", 566 | "detail", 567 | "additionalTextEdits" 568 | ] 569 | } 570 | } 571 | } 572 | } 573 | } 574 | } 575 | }) 576 | .to_string() 577 | } 578 | 579 | pub fn initialized() -> String { 580 | json!({"jsonrpc": "2.0","method": "initialized","params": {}}).to_string() 581 | } 582 | 583 | pub fn did_change_configuration() -> String { 584 | json!({ 585 | "jsonrpc":"2.0", 586 | "method":"workspace/didChangeConfiguration", 587 | "params":{ 588 | "settings":{"hints":{ 589 | "assignVariableTypes":true, 590 | "compositeLiteralFields":true, 591 | "constantValues":true, 592 | "functionTypeParameters":true, 593 | "parameterNames":true, 594 | "rangeVariableTypes":true 595 | } 596 | } 597 | } 598 | }) 599 | .to_string() 600 | } 601 | 602 | pub fn did_open(lang: &str, path: &str, text: &str) -> String { 603 | json!({ 604 | "jsonrpc": "2.0", 605 | "method": "textDocument/didOpen", 606 | "params": { 607 | "textDocument": { 608 | "languageId": lang, 609 | "text": text, 610 | "uri": format!("file://{}", path), 611 | "version": 0 612 | } 613 | } 614 | }) 615 | .to_string() 616 | } 617 | 618 | pub fn did_change_watched_files(path: &str) -> String { 619 | json!({ 620 | "jsonrpc": "2.0", 621 | "method": "workspace/didChangeWatchedFiles", 622 | "params": { 623 | "changes":[ 624 | { "uri":format!("file://{}", path), "type":2 } 625 | ] 626 | 627 | } 628 | }) 629 | .to_string() 630 | } 631 | 632 | pub fn document_link(path: &str) -> String { 633 | json!({ 634 | "jsonrpc": "2.0", 635 | "method": "textDocument/documentLink", 636 | "params": { 637 | "textDocument": { 638 | "uri": format!("file://{}", path), 639 | } 640 | } 641 | }) 642 | .to_string() 643 | } 644 | 645 | pub fn did_change( 646 | line: usize, 647 | character: usize, 648 | line_end: usize, 649 | character_end: usize, 650 | path: &str, 651 | text: &str, 652 | version: usize, 653 | ) -> String { 654 | json!({ 655 | "jsonrpc": "2.0", 656 | "method": "textDocument/didChange", 657 | "params": { 658 | "contentChanges": [ 659 | { 660 | "range": { 661 | "start": { 662 | "line": line, 663 | "character": character 664 | }, 665 | "end": { 666 | "line": line_end, 667 | "character": character_end 668 | } 669 | }, 670 | "text": text 671 | } 672 | ], 673 | "textDocument": { 674 | "uri": format!("file://{}", path), 675 | "version": version 676 | } 677 | } 678 | }) 679 | .to_string() 680 | } 681 | 682 | pub fn completion(id: usize, path: &str, line: usize, character: usize) -> String { 683 | json!({ 684 | "id": id, "jsonrpc": "2.0", "method": "textDocument/completion", 685 | "params": { 686 | "textDocument": { "uri": format!("file://{}", path) }, 687 | "position": { "line": line, "character": character }, 688 | "context": { "triggerKind": 1 } 689 | } 690 | }) 691 | .to_string() 692 | } 693 | 694 | #[derive(Debug, Serialize, Deserialize, Clone)] 695 | pub struct CompletionResponse { 696 | pub jsonrpc: String, 697 | #[serde(default)] 698 | pub result: Option, 699 | pub id: f64, 700 | } 701 | 702 | #[derive(Debug, Serialize, Deserialize, Clone)] 703 | pub struct CompletionResponse2 { 704 | pub jsonrpc: String, 705 | #[serde(default)] 706 | pub result: Vec, 707 | pub id: f64, 708 | } 709 | 710 | #[derive(Debug, Serialize, Deserialize, Clone)] 711 | pub struct CompletionResult { 712 | pub isIncomplete: Option, 713 | pub items: Vec, 714 | } 715 | 716 | #[derive(Debug, Serialize, Deserialize, Clone)] 717 | pub struct CompletionItem { 718 | pub label: String, 719 | pub kind: f64, 720 | pub detail: Option, 721 | pub preselect: Option, 722 | pub sortText: Option, 723 | pub insertText: Option, 724 | pub filterText: Option, 725 | pub insertTextFormat: Option, 726 | pub textEdit: Option, 727 | pub data: Option, 728 | } 729 | 730 | #[derive(Debug, Serialize, Deserialize, Clone)] 731 | pub struct TextEdit { 732 | pub range: Option, 733 | pub replace: Option, 734 | pub insert: Option, 735 | pub newText: String, 736 | } 737 | #[derive(Debug, Serialize, Deserialize, Clone)] 738 | pub struct Range { 739 | pub start: PositionResponse, 740 | pub end: PositionResponse, 741 | } 742 | 743 | #[derive(Debug, Serialize, Deserialize, Clone)] 744 | pub struct PositionResponse { 745 | pub line: f64, 746 | pub character: f64, 747 | } 748 | 749 | 750 | #[derive(Debug, Serialize, Deserialize, Clone)] 751 | pub struct DiagnosticResponse { 752 | pub jsonrpc: String, 753 | pub method: String, 754 | pub params: DiagnosticParams, 755 | } 756 | 757 | #[derive(Debug, Serialize, Deserialize, Clone)] 758 | pub struct DiagnosticParams { 759 | pub uri: String, 760 | pub version: Option, 761 | pub diagnostics: Vec, 762 | } 763 | 764 | #[derive(Debug, Serialize, Deserialize, Clone)] 765 | pub struct Diagnostic { 766 | pub range: Range, 767 | pub severity: i32, 768 | pub code: Option, 769 | pub code_description: Option, 770 | pub source: String, 771 | pub message: String, 772 | } 773 | 774 | #[derive(Debug, Serialize, Deserialize, Clone)] 775 | pub struct CodeDescription { 776 | pub href: String, 777 | } 778 | 779 | 780 | #[derive(Debug, Serialize, Deserialize, Clone)] 781 | pub struct DefinitionResponse { 782 | pub jsonrpc: String, 783 | pub result: Vec, 784 | pub id: f64, 785 | } 786 | 787 | #[derive(Debug, Serialize, Deserialize, Clone)] 788 | pub struct DefinitionResult { 789 | pub uri: String, 790 | pub range: Range, 791 | } 792 | 793 | 794 | #[derive(Debug, Serialize, Deserialize, Clone)] 795 | pub struct ReferencesResponse { 796 | pub jsonrpc: String, 797 | pub result: Vec, 798 | pub id: f64, 799 | } 800 | 801 | #[derive(Debug, Serialize, Deserialize, Clone)] 802 | pub struct ReferencesResult { 803 | pub uri: String, 804 | pub range: Range, 805 | } 806 | 807 | 808 | #[derive(Debug, Serialize, Deserialize, Clone)] 809 | pub struct HoverResponse { 810 | pub jsonrpc: String, 811 | pub result: HoverResult, 812 | pub id: f64, 813 | } 814 | 815 | #[derive(Debug, Serialize, Deserialize, Clone)] 816 | pub struct HoverResult { 817 | pub contents: Contents, 818 | pub range: Range, 819 | } 820 | 821 | #[derive(Debug, Serialize, Deserialize, Clone)] 822 | pub struct Contents { 823 | pub kind: String, 824 | pub value: String, 825 | } 826 | } 827 | 828 | 829 | 830 | #[cfg(test)] 831 | mod tests { 832 | use super::*; 833 | use serde_json; 834 | use tests::lsp_messages::CompletionResponse2; 835 | 836 | #[test] 837 | fn test_deserialization() { 838 | // JSON input string 839 | let json_str = r#"{ 840 | "jsonrpc": "2.0", 841 | "id": 1, 842 | "result": [ 843 | { 844 | "label": "echo", 845 | "kind": 3, 846 | "data": { 847 | "type": 0 848 | } 849 | }, 850 | { 851 | "documentation": { 852 | "value": "```man\n\"echo\" invocation (bash-language-server)\n\n\n```\n```bash\necho \"${1:message}\"\n```", 853 | "kind": "markdown" 854 | }, 855 | "label": "echo", 856 | "insertText": "echo \"${1:message}\"", 857 | "insertTextFormat": 2, 858 | "data": { 859 | "type": 4 860 | }, 861 | "kind": 15 862 | } 863 | ] 864 | }"#; 865 | 866 | // Deserialize JSON into Rust structs 867 | let completion_response: CompletionResponse2 = serde_json::from_str(json_str).unwrap_or_else(|e| { 868 | panic!("Failed to deserialize JSON: {}", e); 869 | }); 870 | 871 | // Print the deserialized structs 872 | println!("{:#?}", completion_response); 873 | } 874 | } 875 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod code; 2 | mod editor; 3 | mod lsp; 4 | mod process; 5 | mod search; 6 | mod selection; 7 | mod tree; 8 | mod tests; 9 | mod utils; 10 | mod config; 11 | 12 | use editor::Editor; 13 | 14 | use log2::*; 15 | 16 | #[tokio::main] 17 | async fn main() { 18 | let current_dir = utils::current_directory_name().unwrap(); 19 | 20 | let logger = match std::env::var("RED_LOG") { 21 | Ok(p) => Some(log2::open(&p).start()), 22 | Err(_) => None, 23 | }; 24 | 25 | debug!("starting red"); 26 | 27 | let mut editor = Editor::new(current_dir, config::get()); 28 | 29 | editor.handle_panic(); 30 | 31 | match std::env::args().nth(1) { 32 | None => editor.open_left_panel(), 33 | Some(path) if path == "." || path == "./" => 34 | editor.open_left_panel(), 35 | Some(path) => { 36 | editor.close_left_panel(); 37 | editor.load_file(&path); 38 | } 39 | } 40 | 41 | editor.start().await; 42 | 43 | debug!("stopping red"); 44 | } 45 | -------------------------------------------------------------------------------- /src/process.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicBool, Ordering}; 2 | use std::sync::{Arc, Mutex}; 3 | use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; 4 | use tokio::process::Command; 5 | 6 | pub struct Process { 7 | kill_sender: Option>, 8 | process_lines: Arc>>, 9 | upd_process: Arc, 10 | process_started: Arc>, 11 | last_cmd: String 12 | } 13 | 14 | impl Process { 15 | pub fn new() -> Self { 16 | Self { 17 | kill_sender: None, 18 | process_lines: Arc::new(Mutex::new(vec![])), 19 | upd_process: Arc::new(AtomicBool::new(false)), 20 | process_started: Arc::new(Mutex::new(false)), 21 | last_cmd: String::new(), 22 | } 23 | } 24 | 25 | pub fn run_tmux(&mut self, args:&String) { 26 | let red_home = std::env::var("RED_HOME").expect("RED_HOME must be set"); 27 | let tmux_path = std::path::Path::new(&red_home).join("tmux.sh"); 28 | 29 | let cmd = match tmux_path.to_str() { 30 | Some(tmux) => tmux.to_string(), 31 | None => return, 32 | }; 33 | 34 | let args = vec![args.clone()]; 35 | self.last_cmd = args.join(" "); 36 | 37 | tokio::spawn(async move { 38 | Command::new(&cmd).args(args) 39 | .output().await.unwrap(); 40 | }); 41 | } 42 | 43 | pub fn run_last_tmux(&mut self) { 44 | if self.last_cmd.is_empty() { return } 45 | let last_cmd = self.last_cmd.clone(); 46 | self.run_tmux(&last_cmd); 47 | } 48 | 49 | pub fn start(&mut self, cmd: &str, arg: &str) { 50 | let mut is_started = self.process_started.lock().expect("cant get lock"); 51 | if *is_started { 52 | return; 53 | } 54 | 55 | let mut child = tokio::process::Command::new(cmd) 56 | .arg(arg) 57 | .env("PYTHONUNBUFFERED", "false") 58 | .stdout(std::process::Stdio::piped()) 59 | .spawn() 60 | .expect("cant spawn cmd"); 61 | 62 | let (send, mut child_stdout_receiver) = tokio::sync::mpsc::channel::(10); 63 | let (kill_send, mut kill) = tokio::sync::mpsc::channel::(10); 64 | 65 | let child_stdout = child.stdout.take().expect("can not get stdout"); 66 | 67 | self.kill_sender = Some(kill_send); 68 | 69 | let mut lines = self.process_lines.lock().unwrap(); 70 | lines.clear(); 71 | 72 | *is_started = true; 73 | 74 | // prepare data for reading stdout task 75 | let process_lines_data = self.process_lines.clone(); 76 | let upd_process_needed = self.upd_process.clone(); 77 | 78 | tokio::spawn(async move { 79 | // reading stdout task 80 | let reader = BufReader::new(child_stdout); 81 | let mut lines = reader.lines(); 82 | 83 | while let Some(line) = lines.next_line().await.expect("can not read message") { 84 | let mut lines = process_lines_data.lock().unwrap(); 85 | (*lines).push(line.clone()); 86 | upd_process_needed.store(true, Ordering::SeqCst) 87 | } 88 | }); 89 | 90 | // prepare data for kill task 91 | let process_lines_data = self.process_lines.clone(); 92 | let process_started = self.process_started.clone(); 93 | let upd_process_needed = self.upd_process.clone(); 94 | 95 | tokio::spawn(async move { 96 | loop { 97 | tokio::select! { 98 | Some(m) = kill.recv() => { // killing manually 99 | child.kill().await.expect("kill failed"); 100 | 101 | let mut is_started = process_started.lock().expect("cant get lock"); 102 | *is_started = false; 103 | 104 | let mut lines = process_lines_data.lock().unwrap(); 105 | (*lines).push("Killed".to_string()); 106 | 107 | upd_process_needed.store(true, Ordering::SeqCst); 108 | break; 109 | } 110 | _ = child.wait() => { // process ends 111 | let mut is_started = process_started.lock().expect("cant get lock"); 112 | *is_started = false; 113 | 114 | let mut lines = process_lines_data.lock().unwrap(); 115 | (*lines).push("Process ended".to_string()); 116 | 117 | upd_process_needed.store(true, Ordering::SeqCst); 118 | return; 119 | } 120 | } 121 | } 122 | }); 123 | } 124 | 125 | pub fn update_true(&self) { 126 | self.upd_process.store(true, Ordering::SeqCst); 127 | } 128 | pub fn update_false(&self) { 129 | self.upd_process.store(false, Ordering::SeqCst) 130 | } 131 | 132 | pub fn kill_process(&mut self) { 133 | let process_started = self.process_started.lock().expect("cant get lock"); 134 | if *process_started == false { 135 | return; 136 | } 137 | 138 | if let Some(sender) = self.kill_sender.take() { 139 | tokio::spawn(async move { 140 | sender 141 | .send("".to_owned()) 142 | .await 143 | .expect("can not send message") 144 | }); 145 | } 146 | } 147 | 148 | pub fn upd(&self) -> bool { 149 | self.upd_process.load(Ordering::Acquire) 150 | } 151 | 152 | pub fn lines(&self) -> Arc>> { 153 | self.process_lines.clone() 154 | } 155 | 156 | fn lines_range(&self, start_index: usize, end_index: usize) -> Option> { 157 | let lines = self.process_lines.lock().unwrap(); // Lock the Mutex to access the vector 158 | if start_index < end_index && end_index < lines.len() { 159 | Some(lines[start_index..end_index].to_vec()) // Extract elements from start_index to end 160 | } else { 161 | None // Return None if start_index is out of bounds 162 | } 163 | } 164 | } 165 | 166 | 167 | mod process_tests { 168 | use std::sync::Arc; 169 | use tokio::sync::Mutex; 170 | 171 | use super::Process; 172 | 173 | // #[tokio::test] 174 | // async fn test_process_start() { 175 | // let mut process = Process::new(); 176 | // process.start("echo", "Hello, World!"); 177 | 178 | // // Wait for some time to allow process to start and output something 179 | // tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; 180 | // let lines_lock = process.lines().lock(); 181 | // let lines = lines_lock.unwrap(); 182 | // assert!(lines.len() > 0); 183 | // assert_eq!(lines[0], "Hello, World!"); 184 | // } 185 | 186 | // #[tokio::test] 187 | // async fn test_process_kill() { 188 | // let mut process = Process::new(); 189 | // process.start("sleep", "10"); // A long-running process 190 | 191 | // tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; 192 | // process.kill_process().await; 193 | 194 | // // Wait for some time to allow the process to be killed 195 | // tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; 196 | 197 | // let lines = process.lines().lock().await; 198 | // assert!(lines.contains(&"Killed".to_string())); 199 | // } 200 | 201 | // #[tokio::test] 202 | // async fn test_process_update() { 203 | // let process = Process::new(); 204 | // process.start("echo", "Hello, World!"); 205 | 206 | // // Wait for some time to allow process to start and output something 207 | // tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; 208 | 209 | // assert!(process.upd()); 210 | 211 | // process.update_false(); 212 | // assert!(!process.upd()); 213 | // } 214 | 215 | #[tokio::test] 216 | async fn test_lines_range() { 217 | let mut process = Process::new(); 218 | tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; 219 | 220 | process.start("echo", "Line 1"); 221 | process.start("echo", "Line 2"); 222 | 223 | // Wait for some time to allow process to output lines 224 | tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; 225 | 226 | let lines = process.lines_range(0, 2); 227 | // assert!(lines.is_some()); 228 | let lines = lines.unwrap(); 229 | assert_eq!(lines[0], "Line 1"); 230 | assert_eq!(lines[1], "Line 2"); 231 | } 232 | 233 | } -------------------------------------------------------------------------------- /src/search.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct Search { 3 | pub active: bool, 4 | pub pattern: ropey::Rope, 5 | pub results: Vec, 6 | pub index:usize, 7 | } 8 | 9 | #[derive(Debug)] 10 | pub struct SearchResult { 11 | pub line: usize, 12 | pub position: usize 13 | } 14 | 15 | impl Search { 16 | pub fn new() -> Self { 17 | Self { 18 | active: false, 19 | pattern: ropey::Rope::new(), 20 | results: Vec::new(), 21 | index: 0 22 | } 23 | } 24 | } 25 | 26 | 27 | pub mod search { 28 | use std::path::{Path, PathBuf}; 29 | use std::{fs, time}; 30 | use rayon::prelude::*; 31 | 32 | use log2::{debug, info, error}; 33 | use std::sync::atomic::{AtomicUsize, Ordering}; 34 | 35 | pub const IGNORE_EXTS: &[&str] = &[ 36 | "doc", "docx", "pdf", "rtf", "odt", "xlsx", "pptx", "jpg", "png", "gif", "bmp", "svg", 37 | "tiff", "mp3", "wav", "aac", "flac", "ogg", "mp4", "avi", "mov", "wmv", "mkv", "zip", 38 | "rar", "tar.gz", "7z", "exe", "msi", "bat", "sh", "so", "ttf", "otf", 39 | ]; 40 | 41 | pub fn read_directory_recursive(dir_path: &Path) -> Result, io::Error> { 42 | // eprintln!("read_directory_recursive {:?}", dir_path); 43 | 44 | let mut paths = Vec::new(); 45 | 46 | let entries = match fs::read_dir(dir_path) { 47 | Ok(entries) => entries, 48 | Err(e) if e.kind() == io::ErrorKind::PermissionDenied => return Ok(Vec::new()), 49 | Err(e) => return Err(e), 50 | }; 51 | 52 | // Convert entries to Vec to enable parallel processing 53 | let entries: Vec<_> = entries.filter_map(|e| e.ok()).collect(); 54 | 55 | // Process entries in parallel 56 | let mut sub_paths: Vec = entries.par_iter() 57 | .filter_map(|entry| { 58 | let path = entry.path(); 59 | let file_name = path.file_name()?.to_string_lossy().to_lowercase(); 60 | 61 | if file_name.starts_with('.') || crate::utils::IGNORE_DIRS.contains(&file_name.as_str()) { 62 | return None; 63 | } 64 | 65 | if path.is_dir() { 66 | match read_directory_recursive(&path) { 67 | Ok(sub_paths) => Some(sub_paths), 68 | Err(_) => None, 69 | } 70 | } else { 71 | let file_ext = path.extension()?.to_string_lossy().to_lowercase(); 72 | if !IGNORE_EXTS.contains(&file_ext.as_str()) { 73 | Some(vec![path]) 74 | } else { 75 | None 76 | } 77 | } 78 | }) 79 | .flatten() 80 | .collect(); 81 | 82 | paths.append(&mut sub_paths); 83 | Ok(paths) 84 | } 85 | 86 | #[cfg(test)] 87 | mod file_search_tests { 88 | use std::path::Path; 89 | use super::read_directory_recursive; 90 | 91 | #[test] 92 | fn test_file_search() { 93 | let directory_path = Path::new("./"); 94 | 95 | let file_names = read_directory_recursive(&directory_path); 96 | 97 | for name in file_names.unwrap() { 98 | println!("{}", name.display()); 99 | } 100 | } 101 | } 102 | 103 | use std::fs::File; 104 | use std::io::{self, BufRead}; 105 | use std::sync::Arc; 106 | use std::time::Instant; 107 | 108 | #[derive(Debug)] 109 | pub struct SearchResult { 110 | pub line: usize, 111 | pub column: usize, 112 | preview: String, 113 | } 114 | 115 | fn search_on_file(file_path: &str, substring_to_find: &str) -> io::Result> { 116 | let file = File::open(file_path)?; 117 | let reader = io::BufReader::new(file); 118 | 119 | let mut results = Vec::new(); 120 | let mut line_number = 0; 121 | 122 | for line_result in reader.lines() { 123 | line_number += 1; 124 | let line = line_result?; 125 | 126 | if let Some(index) = line.find(substring_to_find) { 127 | let search_result = SearchResult { 128 | line: line_number, 129 | column: index, 130 | preview: line.clone(), 131 | }; 132 | results.push(search_result); 133 | } 134 | } 135 | 136 | Ok(results) 137 | } 138 | 139 | // #[tokio::test] 140 | // async fn test_find_substring() { 141 | // let file_path = "./src/search.rs"; 142 | // let substring_to_find = "test_find_substring"; 143 | 144 | // match search_on_file(file_path, substring_to_find) { 145 | // Ok(results) => { 146 | // for result in results { 147 | // println!("{:?}", result); 148 | // } 149 | // } 150 | // Err(..) => { 151 | // panic!("Test failed"); 152 | // } 153 | // } 154 | // } 155 | 156 | #[derive(Debug)] 157 | pub struct FileSearchResult { 158 | pub file_path: String, 159 | pub search_results: Vec, 160 | } 161 | 162 | pub fn search_in_directory( 163 | directory_path: &Path, 164 | substring_to_find: &str, 165 | ) -> io::Result> { 166 | use rayon::prelude::*; 167 | 168 | let file_paths = read_directory_recursive(directory_path) 169 | .expect("cant get files recursively"); 170 | 171 | // eprintln!("[]Found {} files", file_paths.len()); 172 | 173 | let start = Instant::now(); 174 | let files_processed = Arc::new(std::sync::atomic::AtomicUsize::new(0)); 175 | let matches_found = Arc::new(std::sync::atomic::AtomicUsize::new(0)); 176 | 177 | let results = file_paths 178 | .par_iter() 179 | .filter_map(|file_path| { 180 | let files = files_processed.fetch_add(1, Ordering::Relaxed); 181 | // if files % 1000 == 0 { 182 | // eprintln!( 183 | // "Processed {} files, found {} matches ({:?} elapsed)", 184 | // files, 185 | // matches_found.load(Ordering::Relaxed), 186 | // start.elapsed() 187 | // ); 188 | // } 189 | 190 | let path = file_path.to_str().expect("Invalid file path"); 191 | let search_results = search_on_file(path, substring_to_find) 192 | .ok()?; 193 | 194 | matches_found.fetch_add(search_results.len(), Ordering::Relaxed); 195 | 196 | if !search_results.is_empty() { 197 | Some(FileSearchResult { 198 | file_path: file_path.to_string_lossy().to_string(), 199 | search_results, 200 | }) 201 | } else { 202 | None 203 | } 204 | }) 205 | .collect(); 206 | 207 | // eprintln!( 208 | // "Search complete: processed {} files, found {} matches in {:?}", 209 | // files_processed.load(Ordering::Relaxed), 210 | // matches_found.load(Ordering::Relaxed), 211 | // start.elapsed() 212 | // ); 213 | 214 | Ok(results) 215 | } 216 | 217 | 218 | #[tokio::test] 219 | async fn test_find_substring_on_dir() { 220 | use std::path::Path; 221 | println!("hi"); 222 | let directory_path = Path::new("./"); 223 | let substring_to_find = "red"; 224 | 225 | let start = Instant::now(); 226 | let search_results = search_in_directory(directory_path, substring_to_find); 227 | let elapsed = Instant::now() - start; 228 | 229 | match search_results { 230 | Ok(search_results) => { 231 | let results_count: usize = 232 | search_results.iter().map(|s| s.search_results.len()).sum(); 233 | println!( 234 | "search_in_directory done, elapsed {:?} ms", 235 | elapsed.as_millis() 236 | ); 237 | println!("found {:?} results", results_count); 238 | // for result in search_results { 239 | // println!("filename {:?}", result.file_path); 240 | // for sr in result.search_results { 241 | // println!("{:?}", sr); 242 | // } 243 | // } 244 | } 245 | Err(e) => { 246 | println!("error {}", e); 247 | } 248 | } 249 | } 250 | 251 | } 252 | 253 | mod tokio_tests { 254 | use std::path::{Display, Path}; 255 | use tokio::fs::File; 256 | use tokio::io::{self, AsyncBufReadExt, BufReader}; 257 | 258 | #[tokio::test] 259 | async fn test_async_search() -> io::Result<()> { 260 | let search_results = search("src/search.rs", "test").await?; 261 | println!("Found {:?} matches", search_results.len()); 262 | search_results.iter().for_each(|r| println!("{:?}", r)); 263 | Ok(()) 264 | } 265 | 266 | #[derive(Debug)] 267 | pub struct SearchResult { 268 | line_number: usize, 269 | column: usize, 270 | line: String, 271 | } 272 | 273 | pub async fn search(path: &str, term: &str) -> tokio::io::Result> { 274 | let file = File::open(path).await?; 275 | let reader = BufReader::new(file); 276 | let mut lines = reader.lines(); 277 | 278 | let mut ln = 0; 279 | let mut result_lines = vec![]; 280 | 281 | while let Some(line) = lines.next_line().await? { 282 | let found = line.find(term); 283 | match found { 284 | None => {} 285 | Some(f) => result_lines.push(SearchResult { 286 | line_number: ln, 287 | column: f, 288 | line: line.clone(), 289 | }), 290 | } 291 | ln += 1; 292 | } 293 | 294 | Ok(result_lines) 295 | } 296 | 297 | #[tokio::test(flavor = "multi_thread", worker_threads = 10)] 298 | async fn test_async() -> io::Result<()> { 299 | use futures::future::ready; 300 | use futures::stream::{StreamExt, TryStreamExt}; 301 | 302 | let tasks = vec![ 303 | search("src/search.rs", "test"), 304 | search("src/tests.rs", "test"), 305 | // Add more search tasks as needed 306 | ]; 307 | 308 | let results: Vec<_> = futures::stream::iter(tasks) 309 | .buffer_unordered(50) 310 | .try_collect() 311 | .await?; 312 | 313 | for result in results { 314 | for res in result { 315 | println!("{:?}", res); 316 | } 317 | } 318 | 319 | Ok(()) 320 | } 321 | 322 | use std::path::PathBuf; 323 | use tokio::fs; 324 | 325 | #[tokio::test(flavor = "multi_thread", worker_threads = 2)] 326 | async fn test_async_listFiles() -> io::Result<()> { 327 | let path = Path::new("./"); 328 | 329 | let files = 330 | crate::search::search::read_directory_recursive(path).expect("can not read dir"); 331 | println!("Len files {}", files.len()); 332 | 333 | for f in files { 334 | let filename = f.to_str().unwrap(); 335 | let search_results = search(filename, "test").await?; 336 | println!( 337 | "Found {:?} matches in file={}", 338 | search_results.len(), 339 | filename 340 | ); 341 | search_results.iter().for_each(|r| println!("{:?}", r)); 342 | } 343 | 344 | // let search_results = list_files_recursive("src").await?; 345 | // println!("Found {:?} files", search_results.len()); 346 | // search_results.iter().for_each(|r| println!("{:?}", r)); 347 | Ok(()) 348 | } 349 | 350 | #[tokio::test(flavor = "multi_thread", worker_threads = 16)] 351 | async fn test_tokio_spawn_tasks() { 352 | use tokio::time::{sleep, Duration}; 353 | 354 | let mut tasks = vec![]; 355 | let n = 10; 356 | for id in 0..n { 357 | let t = tokio::spawn(async move { 358 | let thread = std::thread::current(); 359 | let mut thread_name = thread.id(); 360 | println!("Async task {} started in thread={:?}", id, thread_name); 361 | sleep(Duration::from_millis((n - id) * 100)).await; 362 | println!("Async task {} done in thread={:?}", id, thread_name); 363 | let result = id * id; 364 | (id, result) 365 | }); 366 | 367 | tasks.push(t); 368 | } 369 | 370 | println!("Launched {} tasks...", tasks.len()); 371 | for task in tasks { 372 | let (id, result) = task.await.expect("task failed"); 373 | println!("Task {} completed with result: {}", id, result); 374 | } 375 | println!("Ready!"); 376 | } 377 | 378 | use crate::search; 379 | use tokio::sync::mpsc; 380 | 381 | #[tokio::test] 382 | async fn test_channels() { 383 | let (tx, mut rx) = mpsc::channel(32); 384 | let tx2 = tx.clone(); 385 | 386 | tokio::spawn(async move { 387 | tx.send("sending from first handle").await; 388 | }); 389 | 390 | tokio::spawn(async move { 391 | tx2.send("sending from second handle").await; 392 | }); 393 | 394 | println!("Launched tasks...",); 395 | while let Some(message) = rx.recv().await { 396 | println!("GOT = {}", message); 397 | } 398 | println!("Ready!"); 399 | } 400 | } 401 | 402 | mod mpsc_test { 403 | use std::sync::Arc; 404 | use tokio::io::AsyncBufReadExt; 405 | use tokio::sync::{mpsc, Mutex}; 406 | 407 | #[derive(Debug)] 408 | pub struct SearchResult { 409 | line_number: usize, 410 | column: usize, 411 | line: String, 412 | } 413 | 414 | pub async fn search( 415 | path: &str, 416 | term: &str, 417 | sender: mpsc::Sender, 418 | ) -> tokio::io::Result<()> { 419 | let file = tokio::fs::File::open(path).await?; 420 | let reader = tokio::io::BufReader::new(file); 421 | let mut lines = reader.lines(); 422 | 423 | let mut ln = 0; 424 | 425 | while let Some(line) = lines.next_line().await? { 426 | if let Some(column) = line.find(term) { 427 | let search_result = SearchResult { 428 | line_number: ln, 429 | column, 430 | line, 431 | }; 432 | // Send search result through the channel 433 | sender 434 | .send(search_result) 435 | .await 436 | .expect("Channel send error"); 437 | } 438 | ln += 1; 439 | } 440 | 441 | Ok(()) 442 | } 443 | 444 | #[tokio::test(flavor = "multi_thread", worker_threads = 10)] 445 | async fn test_async() -> std::io::Result<()> { 446 | let (sender, mut receiver) = mpsc::channel::(100); 447 | 448 | // Spawn search tasks 449 | let tasks = vec![ 450 | search("src/search.rs", "test", sender.clone()), 451 | search("src/tests.rs", "test", sender.clone()), 452 | // Add more search tasks as needed 453 | ]; 454 | 455 | for t in tasks { 456 | let sender = sender.clone(); 457 | tokio::spawn(async move { 458 | t.await; 459 | }); 460 | } 461 | 462 | while let Some(message) = receiver.recv().await { 463 | println!("GOT = {:?}", message); 464 | } 465 | 466 | Ok(()) 467 | } 468 | 469 | #[cfg(test)] 470 | mod insert_russian_character_tests { 471 | #[test] 472 | fn test_insert_russian_character() { 473 | // Create a new empty String 474 | let mut s = String::new(); 475 | 476 | // Insert a Russian character into the String 477 | s.insert_str(0, "п"); 478 | s.insert_str(1, "р"); 479 | 480 | println!("{}", s); 481 | assert_eq!(s, "пр"); 482 | } 483 | 484 | #[test] 485 | fn test_insert_russian_character_to_rope() { 486 | // Create a new empty String 487 | let mut s = ropey::Rope::new(); 488 | 489 | // Insert a Russian character into the String 490 | s.insert_char(0, 'п'); 491 | s.insert_char(1, 'р'); 492 | 493 | println!("{}", s); 494 | assert_eq!(s.to_string(), "пр"); 495 | } 496 | } 497 | } 498 | -------------------------------------------------------------------------------- /src/selection.rs: -------------------------------------------------------------------------------- 1 | pub struct Point { 2 | pub y: i32, 3 | pub x: i32, 4 | } 5 | 6 | impl Point { 7 | pub fn greater_than(&self, other: &Self) -> bool { 8 | if self.y > other.y { return true } 9 | self.y == other.y && self.x > other.x 10 | } 11 | pub fn less_than(&self, other: &Self) -> bool { 12 | if self.y < other.y { return true } 13 | self.y == other.y && self.x < other.x 14 | } 15 | pub fn greater_equal(&self, other: &Self) -> bool { 16 | if self.y > other.y { 17 | return true 18 | } 19 | if self.y == other.y && self.x >= other.x { 20 | return true 21 | } 22 | return false 23 | } 24 | pub fn equal(&self, other: &Self) -> bool { 25 | self.y == other.y && self.x == other.x 26 | } 27 | 28 | } 29 | 30 | 31 | pub struct Selection { 32 | pub start: Point, 33 | pub end: Point, 34 | pub active: bool, 35 | pub keep_once: bool 36 | } 37 | 38 | impl Selection { 39 | pub fn new() -> Self { 40 | Self { 41 | start: Point { y: -1, x: -1 }, 42 | end: Point { y: -1, x: -1 }, 43 | active: false, 44 | keep_once: false, 45 | } 46 | } 47 | pub fn clean(&mut self) { 48 | self.start.y = -1; 49 | self.start.x = -1; 50 | self.end.y = -1; 51 | self.end.x = -1; 52 | self.active = false; 53 | } 54 | 55 | pub fn activate(&mut self) { 56 | self.active = true; 57 | } 58 | 59 | pub fn empty(&mut self) -> bool { 60 | if self.start.x == -1 || self.start.y == -1 || self.end.x == -1 || self.end.y == -1 { return true } 61 | let equal = self.start.equal(&self.end); 62 | equal 63 | } 64 | pub fn non_empty(&mut self) -> bool { 65 | if self.start.x == -1 || self.start.y == -1 || self.end.x == -1 || self.end.y == -1 { return false } 66 | !self.start.equal(&self.end) 67 | } 68 | pub fn non_empty_and_active(&mut self) -> bool { 69 | self.non_empty() && (self.active || self.keep_once) 70 | } 71 | pub fn set_start(&mut self, y: usize, x: usize) { 72 | self.start.x = x as i32; 73 | self.start.y = y as i32; 74 | } 75 | pub fn set_end(&mut self, y: usize, x: usize) { 76 | self.end.x = x as i32; 77 | self.end.y = y as i32; 78 | } 79 | 80 | pub fn contains(&mut self, y: usize, x: usize) -> bool { 81 | if self.empty() { return false } 82 | 83 | let p = Point {x: x as i32, y: y as i32}; 84 | 85 | let result = if self.start.greater_than(&self.end) { 86 | p.greater_equal(&self.end) && p.less_than(&self.start) 87 | } else { 88 | p.greater_equal(&self.start) && p.less_than(&self.end) 89 | }; 90 | 91 | result 92 | } 93 | 94 | pub fn is_selected(&mut self, y: usize, x: usize) -> bool { 95 | let allowed = self.active || self.keep_once; 96 | let contains = self.contains(y, x); 97 | allowed && contains 98 | } 99 | 100 | pub fn from(&mut self) -> (usize, usize) { 101 | if self.start.greater_than(&self.end) { (self.end.y as usize, self.end.x as usize) } 102 | else { (self.start.y as usize, self.start.x as usize) } 103 | } 104 | pub fn to(&mut self) -> (usize, usize) { 105 | if self.start.greater_than(&self.end) { (self.start.y as usize, self.start.x as usize) } 106 | else { (self.end.y as usize, self.end.x as usize) } 107 | } 108 | 109 | pub fn swap(&mut self) { 110 | if self.start.greater_than(&self.end) { 111 | std::mem::swap(&mut self.start, &mut self.end); 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests_tree_sitter { 3 | use std::time; 4 | use tree_sitter::{Parser, Point, Query, QueryCursor, QueryMatches, Range, TreeCursor}; 5 | 6 | fn walk_tree(cursor: &mut TreeCursor, source_code: &str) { 7 | let node = cursor.node(); 8 | println!("Node kind: {:?}", node.kind()); 9 | 10 | let start_byte = node.start_byte(); 11 | let end_byte = node.end_byte(); 12 | let node_text = &source_code[start_byte..end_byte]; 13 | println!("Node text: {:?}", node_text); 14 | 15 | if cursor.goto_first_child() { 16 | walk_tree(cursor, source_code); 17 | cursor.goto_parent(); 18 | } 19 | 20 | while cursor.goto_next_sibling() { 21 | walk_tree(cursor, source_code); 22 | } 23 | } 24 | 25 | // #[test] 26 | // fn test_tree_sitter_walk() { 27 | // let mut parser = Parser::new(); 28 | 29 | // let language = tree_sitter_rust::language(); 30 | // parser 31 | // .set_language(language) 32 | // .expect("Error loading Rust grammar"); 33 | 34 | // let source_code = r" 35 | // fn test() { 36 | // let a = 5; 37 | // } 38 | // "; 39 | 40 | // let start = time::Instant::now(); 41 | // let tree = parser.parse(source_code, None).unwrap(); 42 | // let elapsed = time::Instant::now() - start; 43 | 44 | // println!("Elapsed time: {:?} ms", elapsed.as_millis()); 45 | 46 | // let mut cursor = tree.walk(); 47 | // walk_tree(&mut cursor, source_code); 48 | // } 49 | 50 | use tree_sitter::{Node, QueryMatch}; 51 | use streaming_iterator::StreamingIterator; 52 | 53 | #[test] 54 | fn test_tree_sitter_query() { 55 | let mut parser = Parser::new(); 56 | 57 | let language = tree_sitter_rust::LANGUAGE.into(); 58 | parser 59 | .set_language(&language) 60 | .expect("Error loading Rust grammar"); 61 | 62 | let source_code = r#" 63 | fn foo() { 64 | let x = 42; 65 | println!("Hello, world!"); 66 | } 67 | "#; 68 | 69 | let start = time::Instant::now(); 70 | let tree = parser.parse(source_code, None).unwrap(); 71 | let elapsed = time::Instant::now() - start; 72 | 73 | println!("Elapsed time: {:?} ms", elapsed.as_millis()); 74 | 75 | let query_pattern = r#" 76 | [ 77 | (string_literal) 78 | (raw_string_literal) 79 | ] @string 80 | 81 | (function_item 82 | name: (identifier) @function) 83 | 84 | "fn" @keyword.function 85 | "#; 86 | 87 | let query = Query::new(&language, query_pattern).unwrap(); 88 | let mut query_cursor = QueryCursor::new(); 89 | // query_cursor.set_byte_range(0..source_code.len()); 90 | query_cursor.set_byte_range(0..38); 91 | 92 | let text_provider = |node: tree_sitter::Node| { 93 | println!("text_provider"); 94 | let range = node.byte_range(); 95 | let text_slice = &source_code.as_bytes()[range.start..range.end]; 96 | let iter = vec![text_slice].into_iter(); 97 | iter 98 | }; 99 | 100 | // let dummy = |node: tree_sitter::Node| vec![].into_iter(); 101 | let source_code_bytes = &source_code.as_bytes(); 102 | let start = time::Instant::now(); 103 | 104 | let mut query_matches = query_cursor.matches(&query, tree.root_node(), source_code.as_bytes()); 105 | let mut matches: Vec = Vec::new(); 106 | while let Some(m) = query_matches.next() { 107 | matches.push(m.captures[0].node); 108 | println!("{:?}", m); 109 | } 110 | 111 | 112 | let matches = query_cursor.matches(&query, tree.root_node(), source_code.as_bytes()); 113 | 114 | 115 | // for qmatch in matches { 116 | // for capture in qmatch.captures { 117 | // match capture.node.utf8_text(source_code_bytes) { 118 | // Ok(text) => { 119 | // let i = capture.index as usize; 120 | // let capture_name = &query.capture_names()[i]; 121 | // let text = format!("\x1b[{}m{}\x1b[0m", i + 100, text); 122 | // println!("{:20} {}", capture_name, text); 123 | // } 124 | // _ => {} 125 | // }; 126 | // } 127 | // } 128 | 129 | let elapsed = time::Instant::now() - start; 130 | println!("Elapsed time: {:?} ms", elapsed.as_millis()); 131 | } 132 | 133 | // #[test] 134 | // fn test_tree_sitter_colors_ranges() { 135 | // let mut parser = Parser::new(); 136 | 137 | // let language = tree_sitter_rust::language(); 138 | // parser 139 | // .set_language(language) 140 | // .expect("Error loading Rust grammar"); 141 | 142 | // let source_code = r#" 143 | // fn foo() { 144 | // let x = 42; 145 | // println!("Hello, world!"); 146 | // } 147 | // "#; 148 | 149 | // let tree = parser.parse(source_code, None).unwrap(); 150 | 151 | // let query_pattern = r#" 152 | // [ 153 | // (string_literal) 154 | // (raw_string_literal) 155 | // ] @string 156 | 157 | // (function_item 158 | // name: (identifier) @function) 159 | 160 | // "fn" @keyword.function 161 | // "#; 162 | 163 | // let query = Query::new(language, query_pattern).unwrap(); 164 | // let mut query_cursor = QueryCursor::new(); 165 | // query_cursor.set_byte_range(0..source_code.len()); 166 | // // query_cursor.set_byte_range(0..38); 167 | // // query_cursor.set_byte_range(0..3); 168 | 169 | // let dummy = |node: tree_sitter::Node| vec![].into_iter(); 170 | // let source_code_bytes = &source_code.as_bytes(); 171 | // let start = time::Instant::now(); 172 | 173 | // let matches = query_cursor.matches(&query, tree.root_node(), dummy); 174 | 175 | // let mut color_ranges: Vec<(Point, Point, usize)> = vec![]; 176 | 177 | // for qmatch in matches { 178 | // for capture in qmatch.captures { 179 | // let i = capture.index as usize; 180 | // let capture_name = &query.capture_names()[i]; 181 | 182 | // let color_range = ( 183 | // capture.node.start_position(), 184 | // capture.node.end_position(), 185 | // i, 186 | // ); 187 | // color_ranges.push(color_range); 188 | // } 189 | // } 190 | 191 | // let elapsed = time::Instant::now() - start; 192 | // println!("Elapsed time: {:?} ns", elapsed.as_nanos()); 193 | 194 | // color_ranges.iter().for_each(|cr| println!("{:?}", cr)); 195 | // } 196 | } 197 | 198 | #[cfg(test)] 199 | mod tests_text { 200 | use crate::code::Code; 201 | 202 | #[test] 203 | fn test_new_text_buffer_is_empty() { 204 | let text_buffer = Code::new(); 205 | assert!(text_buffer.is_empty()); 206 | } 207 | 208 | #[test] 209 | fn test_new_text_buffer_is_zero_len() { 210 | let text_buffer = Code::new(); 211 | assert_eq!(text_buffer.len_lines(), 1); 212 | } 213 | 214 | #[test] 215 | fn test_insert_char() { 216 | let mut text_buffer = Code::new(); 217 | text_buffer.insert_char('a', 0, 0); 218 | assert_eq!(text_buffer.len_lines(), 1); 219 | assert_eq!(text_buffer.line_len(0), 1); 220 | } 221 | 222 | #[test] 223 | fn test_remove_char() { 224 | let mut text_buffer = Code::new(); 225 | text_buffer.insert_char('a', 0, 0); 226 | text_buffer.remove_char(0, 1); 227 | assert_eq!(text_buffer.len_lines(), 1); 228 | } 229 | } 230 | 231 | #[cfg(test)] 232 | mod tests_selection { 233 | use crate::selection::{Point, Selection}; 234 | 235 | #[test] 236 | fn test_is_selected_when_active_and_inside_selection() { 237 | let mut selection = Selection::new(); 238 | selection.start = Point { y: 1, x: 1 }; 239 | selection.end = Point { y: 3, x: 3 }; 240 | selection.active = true; 241 | 242 | assert!(selection.contains(2, 2)); 243 | assert!(selection.contains(1, 1)); 244 | assert!(selection.contains(3, 2)); 245 | } 246 | 247 | #[test] 248 | fn test_is_selected_when_active_and_outside_selection() { 249 | let mut selection = Selection::new(); 250 | selection.start = Point { y: 1, x: 1 }; 251 | selection.end = Point { y: 3, x: 3 }; 252 | selection.active = true; 253 | 254 | assert!(!selection.is_selected(0, 0)); 255 | assert!(!selection.is_selected(4, 4)); 256 | assert!(selection.is_selected(1, 4)); 257 | assert!(!selection.is_selected(4, 1)); 258 | } 259 | 260 | #[test] 261 | fn test_is_selected_when_inactive() { 262 | let mut selection = Selection::new(); 263 | 264 | assert!(!selection.contains(2, 2)); 265 | assert!(!selection.contains(1, 1)); 266 | assert!(!selection.contains(3, 3)); 267 | } 268 | } 269 | 270 | #[cfg(test)] 271 | mod tokio_test { 272 | 273 | use std::process::Stdio; 274 | use tokio::io; 275 | use tokio::io::{AsyncBufReadExt, BufReader}; 276 | use tokio::process::Command; 277 | 278 | #[tokio::test] 279 | async fn test_process() -> io::Result<()> { 280 | let mut cmd = Command::new("ls"); 281 | 282 | cmd.stdout(Stdio::piped()); 283 | 284 | let mut child = cmd.spawn().expect("failed to spawn command"); 285 | 286 | let stdout = child.stdout.take().expect("can not get stdout"); 287 | 288 | let mut reader = BufReader::new(stdout).lines(); 289 | 290 | let status = child.wait().await?; 291 | 292 | println!("child status was: {}", status); 293 | 294 | while let Some(line) = reader.next_line().await? { 295 | println!("Line: {}", line); 296 | } 297 | 298 | Ok(()) 299 | } 300 | } 301 | 302 | 303 | #[cfg(test)] 304 | mod color_test { 305 | 306 | #[test] 307 | fn test_colored_output(){ 308 | let (r, g, b) = (100, 200, 200); 309 | let line = "hello colored world"; 310 | println!("\u{1b}[38;2;{r};{g};{b}m{line} \u{1b}[0m", r=r, g=g, b=b, line=line); 311 | 312 | } 313 | 314 | #[test] 315 | fn test_strfmt() { 316 | use strfmt::strfmt; 317 | let template = "python3 {file}"; 318 | 319 | let mut vars = std::collections::HashMap::new(); 320 | vars.insert("file".to_string(), "test.py"); 321 | vars.insert("job".to_string(), "python developer"); 322 | 323 | let res = strfmt(&template, &vars).unwrap(); 324 | println!("res {}", res) 325 | } 326 | } 327 | 328 | 329 | -------------------------------------------------------------------------------- /src/tree.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::fs::File; 3 | use std::path::{Path, PathBuf}; 4 | use std::io::{self, Write}; 5 | use crossterm::{cursor, queue, QueueableCommand, style::Print}; 6 | use log2::debug; 7 | use serde::de; 8 | use tokio::sync::watch::error; 9 | 10 | use crate::utils; 11 | use crate::utils::{IGNORE_DIRS, IGNORE_FILES}; 12 | use crossterm::style::{Color, SetBackgroundColor as BColor, SetForegroundColor as FColor}; 13 | 14 | #[derive(Debug)] 15 | pub struct TreeNode { 16 | name: String, 17 | fullpath: String, 18 | is_file: bool, 19 | children: Option>, 20 | } 21 | 22 | impl TreeNode { 23 | pub fn new(name:String, fullpath:String, is_file: bool) -> Self { 24 | Self { name, fullpath, is_file, children: None } 25 | } 26 | pub fn print(&self) { println!("node {:?}", self); } 27 | pub fn is_file(&mut self) -> bool { self.is_file } 28 | pub fn fullpath(&mut self) -> String { self.fullpath.clone() } 29 | pub fn collapse(&mut self) { self.children = None; } 30 | 31 | pub fn expand(&mut self) -> io::Result<()> { 32 | if !Path::new(&self.fullpath).is_dir() { return Ok(()); } 33 | 34 | let mut children = Vec::new(); 35 | 36 | let mut directories = Vec::new(); 37 | let mut files = Vec::new(); 38 | 39 | for entry in fs::read_dir(&self.fullpath)? { 40 | let entry = entry?; 41 | let name = entry.file_name().into_string().unwrap(); 42 | let is_file = entry.file_type().unwrap().is_file(); 43 | let abs_path = match entry.path().canonicalize() { 44 | Ok(abs) => abs, 45 | Err(e) => { debug!("cant get abs_path for {}", name); continue;}, 46 | }; 47 | let fullpath = abs_path.to_string_lossy().to_string(); 48 | 49 | if !is_file && IGNORE_DIRS.contains(&name.as_str()) { continue; } 50 | if is_file && IGNORE_FILES.contains(&name.as_str()) { continue; } 51 | 52 | if is_file { files.push(TreeNode::new(name, fullpath, is_file)); } 53 | else { directories.push(TreeNode::new(name, fullpath, is_file)); } 54 | } 55 | 56 | directories.sort_by(|a, b| a.name.cmp(&b.name)); 57 | files.sort_by(|a, b| a.name.cmp(&b.name)); 58 | 59 | children.extend(directories); 60 | children.extend(files); 61 | 62 | self.children = Some(children); 63 | Ok(()) 64 | } 65 | 66 | pub fn toggle(&mut self) -> io::Result<()> { 67 | if self.children.is_none() { 68 | self.expand()?; 69 | } else { 70 | self.collapse(); 71 | } 72 | Ok(()) 73 | } 74 | 75 | pub fn len(&self) -> usize { 76 | match &self.children { 77 | Some(children) => { 78 | let mut count = children.len(); 79 | for child in children { 80 | count += child.len(); 81 | } 82 | count 83 | } 84 | None => 0, 85 | } 86 | } 87 | 88 | fn filter_files_mutate(&mut self, pattern: &str) -> bool { 89 | let mut found = false; 90 | if let Some(children) = &mut self.children { 91 | let mut filtered_children = Vec::new(); 92 | for mut child in children.drain(..) { 93 | if child.is_file && child.name.contains(pattern) { 94 | found = true; 95 | filtered_children.push(child); 96 | } else if !child.is_file { 97 | child.expand(); 98 | // Recursive call for directories 99 | let is_any_found = child.filter_files_mutate(pattern); 100 | if is_any_found { 101 | filtered_children.push(child); 102 | found = true; 103 | } 104 | } 105 | } 106 | self.children = Some(filtered_children); 107 | } 108 | found 109 | } 110 | } 111 | 112 | pub struct TreeNodeIterator<'a> { 113 | stack: Vec<(&'a TreeNode, usize)>, // Tuple of node reference and depth 114 | } 115 | 116 | impl<'a> TreeNodeIterator<'a> { 117 | pub fn new(root: &'a TreeNode) -> Self { 118 | let mut stack = Vec::new(); 119 | stack.push((root, 0)); // Start with depth 0 for the root node 120 | Self { stack } 121 | } 122 | } 123 | 124 | impl<'a> Iterator for TreeNodeIterator<'a> { 125 | type Item = (&'a TreeNode, usize); // Tuple of node reference and depth 126 | 127 | fn next(&mut self) -> Option { 128 | // Pop the last node-depth tuple from the stack 129 | let (node, depth) = self.stack.pop()?; 130 | 131 | // Push children onto the stack with incremented depth 132 | if let Some(children) = &node.children { 133 | let new_depth = depth + 1; 134 | for child in children.iter().rev() { 135 | self.stack.push((child, new_depth)); 136 | } 137 | } 138 | 139 | Some((node, depth)) 140 | } 141 | } 142 | 143 | pub struct TreeView { 144 | width: usize, 145 | height: usize, 146 | dir: String, 147 | pub upd: bool, 148 | root: TreeNode, 149 | 150 | selected: usize, 151 | x: usize, 152 | moving: bool, 153 | /// Color for tree dir. 154 | dir_color: Color, 155 | /// Color for tree dir. 156 | file_color: Color, 157 | 158 | active_file: String, 159 | /// Color for active file. 160 | active_file_color: Color, 161 | 162 | search: FileSearch, 163 | } 164 | 165 | impl TreeView { 166 | pub fn new(dir:String) -> Self { 167 | let name = if dir == "." || dir == "./" { 168 | utils::current_directory_name().unwrap() 169 | } else { dir.to_string() }; 170 | 171 | let mut root = TreeNode { 172 | name, 173 | fullpath: utils::abs_file(&dir), 174 | is_file: false, 175 | children: None, 176 | 177 | }; 178 | 179 | root.expand(); 180 | 181 | Self { width: 25, height: 30, dir, upd: true, root, selected:0, x: 0, 182 | moving: false, dir_color: Color::Reset, file_color: Color::Reset, 183 | active_file: String::new(), active_file_color: Color::Reset, 184 | search: FileSearch::new(), 185 | } 186 | } 187 | 188 | pub fn set_width(&mut self, width: usize) { self.width = width; self.upd = true; } 189 | pub fn set_height(&mut self, height: usize) { self.height = height; self.upd = true; } 190 | pub fn set_dir_color(&mut self, c: Color) { self.dir_color = c; self.upd = true; } 191 | pub fn set_file_color(&mut self, c: Color) { self.file_color = c; self.upd = true; } 192 | pub fn set_active_file_color(&mut self, c: Color) { self.active_file_color = c; self.upd = true; } 193 | pub fn set_moving(&mut self, m: bool) { self.moving = m; self.upd = true; } 194 | pub fn set_selected(&mut self, i: usize) { self.selected = i + self.x; self.upd = true; } 195 | pub fn is_moving(&mut self) -> bool { self.moving } 196 | pub fn is_search(&mut self) -> bool { self.search.active } 197 | 198 | pub(crate) fn handle_up(&mut self) { 199 | if self.selected == 0 { return; } 200 | self.selected -= 1; 201 | self.upd = true; 202 | } 203 | pub(crate) fn handle_down(&mut self) { 204 | if self.selected >= self.root.len() { 205 | return; 206 | } 207 | self.selected += 1; 208 | self.upd = true; 209 | } 210 | 211 | pub fn scroll_down(&mut self) { 212 | if self.x + self.height > self.root.len() { 213 | return; 214 | } 215 | 216 | self.x += 1; 217 | self.upd = true; 218 | } 219 | pub fn scroll_up(&mut self) { 220 | if self.x == 0 { return; } 221 | 222 | self.x -= 1; 223 | self.upd = true; 224 | } 225 | 226 | pub fn expand_root(&mut self) { 227 | let root = &mut self.root; 228 | root.expand(); 229 | 230 | } 231 | 232 | pub fn filter_files_by_pattern(&mut self, pattern: &str) { 233 | let root = &mut self.root; 234 | root.expand(); 235 | root.filter_files_mutate(pattern); 236 | 237 | let mut index = 0; 238 | Self::find_first_file_index(root, &mut index); 239 | self.selected = index; 240 | } 241 | 242 | 243 | pub fn draw(&mut self) { 244 | if !self.upd { return; } 245 | if self.width == 0 { return; } 246 | 247 | let mut stdout = std::io::stdout(); 248 | 249 | let padding_left = 1; 250 | 251 | let iter = TreeNodeIterator::new(&self.root); 252 | let iter = iter.skip(self.x).take(self.height); 253 | let mut count = 0; 254 | 255 | queue!(stdout, cursor::Hide); 256 | 257 | for (i, (node, depth)) in iter.enumerate() { 258 | // if i > self.height { break; } 259 | 260 | queue!(stdout, cursor::MoveTo(0, i as u16)); 261 | 262 | let mut col = 0; 263 | 264 | let mut color = if node.is_file { 265 | if node.fullpath.eq(&self.active_file) { self.active_file_color } 266 | else { self.file_color } 267 | } else { self.dir_color }; 268 | 269 | if self.selected == i+ self.x { color = self.active_file_color } 270 | 271 | for i in 0..padding_left { 272 | if col >= self.width-1 { break; } 273 | queue!(stdout, Print(' ')); 274 | col += 1; 275 | } 276 | for i in 0..depth { 277 | if col >= self.width-1 { break; } 278 | queue!(stdout, Print(' ')); 279 | col += 1; 280 | } 281 | for ch in node.name.chars().take(self.width-padding_left-depth-1) { 282 | if col >= self.width-1 { break; } 283 | queue!(stdout, FColor(color), Print(ch)); 284 | col += 1; 285 | } 286 | 287 | if col < self.width { 288 | for i in 0..self.width-col-1 { 289 | queue!(stdout, Print(' ')); 290 | } 291 | } 292 | queue!(stdout, FColor(Color::DarkGrey), Print('│')); 293 | 294 | count += 1; 295 | } 296 | 297 | while count < self.height { // fill empty space 298 | queue!(stdout, cursor::MoveTo(0, count as u16)); 299 | queue!(stdout, Print(" ".repeat(self.width-1))); 300 | queue!(stdout, FColor(Color::DarkGrey), Print('│')); 301 | count += 1; 302 | } 303 | 304 | self.draw_search(); 305 | 306 | self.upd = false; 307 | } 308 | 309 | pub fn draw_search(&self) { 310 | if !self.search.active || self.width == 0 { return } 311 | 312 | let mut stdout = std::io::stdout(); 313 | let prefix = " search: "; 314 | let search = format!("{}{}", prefix, self.search.pattern.to_string()); 315 | if search.len() >= self.width { return; } // not enought space 316 | queue!(stdout,cursor::Show, cursor::MoveTo(0, (self.height -1) as u16)); 317 | queue!(stdout, Print(&search)); 318 | queue!(stdout, Print(" ".repeat(self.width-search.len()-1))); 319 | queue!(stdout, FColor(Color::DarkGrey), Print('│')); 320 | queue!(stdout, cursor::MoveTo((prefix.len() + self.search.index) as u16, (self.height -1) as u16)); 321 | // stdout.flush(); 322 | } 323 | pub fn print(&self) { 324 | self.print_node(&self.root, 0, &mut 0); 325 | } 326 | 327 | fn print_node(&self, node: &TreeNode, depth: usize, count: &mut usize) { 328 | println!("{}{}: {}", " ".repeat(depth), count, node.name); 329 | 330 | // Recursively print children 331 | if let Some(children) = &node.children { 332 | for child in children { 333 | *count += 1; 334 | self.print_node(child, depth + 1, count); 335 | } 336 | } 337 | } 338 | 339 | 340 | pub fn find<'a>(&'a mut self, index: usize) -> Option<&'a mut TreeNode> { 341 | let mut count = 0; 342 | let root = &mut self.root; 343 | let maybe_node = Self::find_by_index(root, index + self.x, &mut count); 344 | maybe_node 345 | } 346 | 347 | pub fn get_selected<'a>(&'a mut self) -> Option<&'a mut TreeNode> { 348 | let mut count = 0; 349 | let root = &mut self.root; 350 | let maybe_node = Self::find_by_index(root, self.selected, &mut count); 351 | maybe_node 352 | } 353 | 354 | pub fn find_and_expand(&mut self, index: usize) { 355 | let mut count = 0; 356 | let root = &mut self.root; 357 | let maybe_node = Self::find_by_index(root, index, &mut count); 358 | maybe_node.map(|node| node.expand()); 359 | } 360 | 361 | pub fn find_expand_by_fullpath(&mut self, fullpath: &str) { 362 | let root = &mut self.root; 363 | Self::find_by_fullpath_and_expand(root, fullpath); 364 | } 365 | 366 | pub fn find_and_toggle(&mut self, index: usize) { 367 | let mut count = 0; 368 | let root = &mut self.root; 369 | let maybe_node = Self::find_by_index(root, index, &mut count); 370 | maybe_node.map(|node| node.toggle()); 371 | } 372 | 373 | fn find_by_index<'a>(node: &'a mut TreeNode, index: usize, count: &mut usize) -> Option<&'a mut TreeNode>{ 374 | if *count == index { 375 | // println!("Found {}: {}", index, node.name); 376 | // node.expand(); 377 | return Some(node); 378 | } 379 | 380 | // Recursively search children 381 | if let Some(children) = &mut node.children { 382 | for child in children { 383 | *count += 1; 384 | let found_child = Self::find_by_index(child, index, count); 385 | if found_child.is_some() { return found_child;} 386 | } 387 | } 388 | None 389 | } 390 | 391 | fn find_by_index_expand(node: &mut TreeNode, index: usize, count: &mut usize) -> bool { 392 | if *count == index { 393 | // println!("Found {}: {}", index, node.name); 394 | node.expand(); 395 | return true; 396 | } 397 | 398 | // Recursively search children 399 | if let Some(children) = &mut node.children { 400 | for child in children { 401 | *count += 1; 402 | let found = Self::find_by_index_expand(child, index, count); 403 | if found { return true; } 404 | } 405 | } 406 | return false; 407 | } 408 | 409 | fn find_first_file_index(node: &mut TreeNode, index: &mut usize) -> bool { 410 | if node.is_file { 411 | // println!("Found {}: {}", node.name, index); 412 | node.expand(); 413 | return true; 414 | } 415 | 416 | // Recursively search children 417 | if let Some(children) = &mut node.children { 418 | for child in children { 419 | *index += 1; 420 | let found = Self::find_first_file_index(child, index); 421 | if found { return true; } 422 | } 423 | } 424 | return false; 425 | } 426 | 427 | pub fn find_by_fullpath_and_expand(node: &mut TreeNode, fullpath: &str) -> bool { 428 | if fullpath.starts_with(&node.fullpath) { 429 | node.expand(); 430 | } 431 | // Recursively search children 432 | if let Some(children) = &mut node.children { 433 | for child in children { 434 | if fullpath.starts_with(&child.fullpath) { 435 | child.expand(); 436 | // return true; 437 | } 438 | let found = Self::find_by_fullpath_and_expand(child, fullpath); 439 | if found { 440 | // node.expand(); 441 | return true; 442 | } 443 | } 444 | } 445 | false 446 | } 447 | 448 | pub fn set_active(&mut self, fullpath: String) { 449 | self.active_file = fullpath; 450 | // todo: expand all nodes 451 | } 452 | 453 | pub async fn handle_mouse(&mut self, e: crossterm::event::MouseEvent) { 454 | match e { 455 | crossterm::event::MouseEvent { row, column, kind, modifiers } => { 456 | match kind { 457 | crossterm::event::MouseEventKind::ScrollUp => self.scroll_up(), 458 | crossterm::event::MouseEventKind::ScrollDown => self.scroll_down(), 459 | crossterm::event::MouseEventKind::Down(button) => {} 460 | _ => {} 461 | 462 | } 463 | } 464 | _ => {} 465 | } 466 | } 467 | 468 | pub fn insert_filter_char(&mut self, c: char) { 469 | self.search.active = true; 470 | self.search.pattern.insert_char(self.search.index, c); 471 | self.search.index += 1; 472 | 473 | self.filter_files_by_pattern(&self.search.pattern.to_string()); 474 | self.upd = true; 475 | } 476 | 477 | pub fn remove_filter_char(&mut self) { 478 | if self.search.index > 0 { 479 | self.search.index -= 1; 480 | let x = self.search.index; 481 | self.search.active = true; 482 | self.search.pattern.remove(x..x+1); 483 | 484 | let pattern = self.search.pattern.to_string(); 485 | if pattern.is_empty() { 486 | self.expand_root(); 487 | } else { 488 | self.filter_files_by_pattern(&pattern); 489 | } 490 | 491 | self.upd = true; 492 | } 493 | } 494 | 495 | pub fn handle_left(&mut self) { 496 | if self.search.index > 0 { 497 | self.search.index -= 1; 498 | self.upd = true; 499 | }; 500 | } 501 | 502 | pub fn handle_right(&mut self) { 503 | if self.search.index < self.search.pattern.len_chars() { 504 | self.search.index += 1; 505 | self.upd = true; 506 | }; 507 | } 508 | pub fn clear_search(&mut self) { 509 | self.search = FileSearch::new(); 510 | self.upd = true; 511 | self.expand_root(); 512 | } 513 | } 514 | 515 | fn list_files_and_directories(path: &str) -> io::Result> { 516 | let entries = fs::read_dir(path)?; 517 | let mut names = Vec::new(); 518 | 519 | for entry in entries { 520 | let file_name = entry?.file_name().into_string().unwrap(); 521 | names.push(file_name); 522 | } 523 | 524 | Ok(names) 525 | } 526 | 527 | #[cfg(test)] 528 | mod tree_tests { 529 | use super::{list_files_and_directories, TreeNode, TreeNodeIterator}; 530 | use crate::tree::TreeView; 531 | 532 | #[test] 533 | fn test_list_files_and_directories() { 534 | match list_files_and_directories(".") { 535 | Ok(names) => { 536 | for name in names { 537 | println!("{}", name); 538 | } 539 | } 540 | Err(err) => eprintln!("Error: {}", err), 541 | } 542 | } 543 | 544 | #[test] 545 | fn test_load() { 546 | let root = ".".to_string(); 547 | let mut tree = TreeView::new(root); 548 | 549 | tree.expand_root(); 550 | tree.print(); 551 | 552 | println!("find 5"); 553 | let maybe_node = tree.find(5); 554 | maybe_node.map(|node| node.print()); 555 | 556 | println!("expanding 5"); 557 | tree.find_and_expand(5); 558 | 559 | tree.print(); 560 | } 561 | 562 | #[test] 563 | fn test_expand_search() { 564 | let root = ".".to_string(); 565 | let mut tree = TreeView::new(root); 566 | 567 | // tree.load_root(); 568 | // tree.print(); 569 | 570 | println!("find rs"); 571 | 572 | tree.filter_files_by_pattern("rs"); 573 | 574 | tree.print(); 575 | 576 | // println!("find 16"); 577 | // let maybe_node = tree.find(16); 578 | // maybe_node.map(|node| node.print()); 579 | } 580 | 581 | 582 | 583 | #[test] 584 | fn test_iter() { 585 | // let root_node = TreeNode { 586 | // name: "Root".to_string(), 587 | // fullpath: "/path/to/root".to_string(), 588 | // is_file: false, 589 | // children: Some(vec![ 590 | // TreeNode { 591 | // name: "Child1".to_string(), 592 | // fullpath: "/path/to/root/child1".to_string(), 593 | // is_file: true, 594 | // children: None, 595 | // }, 596 | // TreeNode { 597 | // name: "Child2".to_string(), 598 | // fullpath: "/path/to/root/child2".to_string(), 599 | // is_file: false, 600 | // children: Some(vec![ 601 | // TreeNode { 602 | // name: "Grandchild1".to_string(), 603 | // fullpath: "/path/to/root/child2/grandchild1".to_string(), 604 | // is_file: true, 605 | // children: None, 606 | // }, 607 | // ]), 608 | // }, 609 | // ]), 610 | // file_search: FileSearch {}, 611 | // }; 612 | // 613 | // 614 | // for (node, depth) in TreeNodeIterator::new(&root_node).take(2) { 615 | // println!("take depth: {}, Name: {}, Fullpath: {}, Is File: {}", depth, node.name, node.fullpath, node.is_file); 616 | // } 617 | // 618 | // for (node, depth) in TreeNodeIterator::new(&root_node).skip(2) { 619 | // println!("skip depth: {}, Name: {}, Fullpath: {}, Is File: {}", depth, node.name, node.fullpath, node.is_file); 620 | // } 621 | } 622 | } 623 | 624 | 625 | #[derive(Debug)] 626 | pub struct FileSearch { 627 | pub active: bool, 628 | pub pattern: ropey::Rope, 629 | pub index:usize, 630 | } 631 | 632 | impl FileSearch { 633 | pub fn new() -> Self { 634 | Self { 635 | active: false, 636 | pattern: ropey::Rope::new(), 637 | index: 0, 638 | } 639 | } 640 | } -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use crossterm::style::Color; 3 | use serde_yaml::Value; 4 | 5 | pub fn hex_to_color(hex_color: &str) -> Color { 6 | let hex = hex_color.trim_start_matches('#'); 7 | let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0); 8 | let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0); 9 | let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0); 10 | Color::Rgb { r, g, b } 11 | } 12 | 13 | pub fn hex_to_rgb(hex_color: &str) -> (u8, u8, u8) { 14 | let hex = hex_color.trim_start_matches('#'); 15 | let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0); 16 | let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0); 17 | let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0); 18 | (r, g, b) 19 | } 20 | 21 | pub fn yaml_to_map(yaml: Value) -> HashMap { 22 | yaml.as_mapping() 23 | .map(|mapping| { 24 | mapping.into_iter() 25 | .filter_map(|(key, value)| { 26 | key.as_str().and_then(|k| { 27 | value.as_str().map(|v| (k.to_string(), v.to_string())) 28 | }) 29 | }) 30 | .collect() 31 | }) 32 | .unwrap_or_else(HashMap::new) 33 | } 34 | 35 | pub fn abs_file(input: &str) -> String { 36 | let srcdir = std::path::PathBuf::from(input); 37 | let c = std::fs::canonicalize(&srcdir).unwrap(); 38 | c.to_string_lossy().to_string() 39 | } 40 | pub fn get_file_name(input: &str) -> String { 41 | let path_buf = std::path::PathBuf::from(input); 42 | let file_name = path_buf.file_name().unwrap().to_string_lossy().into_owned(); 43 | file_name 44 | } 45 | 46 | const WORD_BREAK_CHARS: [char; 23] = [ 47 | ' ', '.', ',', '=', '+', '-', '[', '(', '{', ']', ')', '}', 48 | '"', ':', '&', '?', '!', ';', '\t', '/', '<', '>', '\n' 49 | ]; 50 | 51 | pub fn find_next_word(line: &str, from: usize) -> usize { 52 | // Find the next word index after the specified index 53 | let chars: Vec = line.chars().collect(); 54 | for i in from..chars.len() { 55 | if WORD_BREAK_CHARS.contains(&chars[i]) { 56 | return i; 57 | } 58 | } 59 | chars.len()-1 60 | } 61 | 62 | pub fn find_prev_word(line: &str, from: usize) -> usize { 63 | // Find the previous word index before the specified index 64 | let chars: Vec = line.chars().collect(); 65 | for i in (0..from).rev() { 66 | let ch = match chars.get(i) { 67 | Some(ch) => ch, 68 | None => { return 0; }, 69 | }; 70 | 71 | if WORD_BREAK_CHARS.contains(ch) { 72 | return i + 1; 73 | } 74 | } 75 | 0 76 | } 77 | 78 | pub fn pad_left(str: &str, length: usize) -> String { 79 | format!("{:1$}", str, length) 80 | } 81 | 82 | pub const IGNORE_DIRS: &[&str] = &[ 83 | ".git", 84 | ".idea", 85 | ".vscode", 86 | "node_modules", 87 | "dist", 88 | "target", 89 | "__pycache__", 90 | ".pytest_cache", 91 | "build", 92 | ".DS_Store", 93 | ".venv", 94 | "venv", 95 | ]; 96 | 97 | pub const IGNORE_FILES: &[&str] = &[ 98 | ".DS_Store", 99 | ]; 100 | 101 | pub fn current_dir() -> String { 102 | std::env::current_dir().unwrap() 103 | .to_string_lossy().into_owned() 104 | } 105 | 106 | pub fn current_directory_name() -> Option { 107 | if let Ok(current_dir) = std::env::current_dir() { 108 | if let Some(dir_name) = current_dir.file_name() { 109 | return dir_name.to_str().map(String::from); 110 | } 111 | } 112 | None 113 | } 114 | 115 | 116 | pub struct CursorPosition { 117 | pub filename: String, 118 | pub row: usize, 119 | pub col: usize, 120 | pub y: usize, 121 | pub x: usize, 122 | } 123 | pub struct CursorHistory { 124 | positions: Vec 125 | } 126 | 127 | impl CursorHistory { 128 | pub fn new() -> Self { Self { positions: Vec::new()} } 129 | 130 | pub fn push(&mut self, cp: CursorPosition) { 131 | self.positions.push(cp); 132 | } 133 | 134 | pub fn pop(&mut self) -> Option { 135 | self.positions.pop() 136 | } 137 | pub fn clear(&mut self) { 138 | self.positions.clear(); 139 | } 140 | } -------------------------------------------------------------------------------- /test/ptest.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | def add_numbers(a, b): 4 | return a + b 5 | 6 | 7 | class TestAddNumbers(unittest.TestCase): 8 | 9 | def test_add_positive_numbers(self): 10 | print("test_add_positive_numbers") 11 | result = add_numbers(1, 7) 12 | self.assertEqual(result, 8) 13 | 14 | def test_add_negative_numbers(self): 15 | result = add_numbers(-2, -3) 16 | self.assertEqual(result, -5) 17 | 18 | 19 | def test_add_fail(self): 20 | result = add_numbers(0, 7) 21 | self.assertEqual(result, 8) 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | { 5 | int num1, num2, sum; 6 | 7 | // Prompt user to enter the first number 8 | printf("Enter the first number: "); 9 | scanf("%d", &num1); 10 | 11 | // Prompt user to enter the second number 12 | printf("Enter the second number: "); 13 | scanf("%d", &num2); 14 | 15 | // Calculate the sum 16 | sum = num1 + num2; 17 | 18 | // Display the result 19 | printf("The sum of %d and %d is: %d\n", num1, num2, sum); 20 | 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() { 6 | auto start_time = std::chrono::high_resolution_clock::now(); 7 | int count = 0; 8 | 9 | for (int i = 0; i < 3; ++i) { 10 | count += i; 11 | std::cout << "hi " << i << std::endl; 12 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 13 | } 14 | 15 | std::cout << "done " << count << std::endl; 16 | auto elapsed_time = std::chrono::high_resolution_clock::now() - start_time; 17 | auto elapsed_seconds = std::chrono::duration_cast(elapsed_time); 18 | std::cout << "elapsed time: " << elapsed_seconds.count() / 1000.0 << " seconds" << std::endl; 19 | 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /test/test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | start := time.Now() 10 | var count = 0 11 | for i := 0; i <= 100000000; i++ { 12 | count += i 13 | } 14 | 15 | fmt.Println(count, "elapsed", time.Since(start)) 16 | } 17 | -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Simple Text Editor 7 | 25 | 26 | 27 |
28 | 29 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /test/test.java: -------------------------------------------------------------------------------- 1 | public class Factorial { 2 | public static void main(String[] args) { 3 | final int NUM_FACTS = 100; 4 | for (int i = 0; i < NUM_FACTS; i++) 5 | System.out.print(i + "! is " + factorial(i)); 6 | } 7 | 8 | public static int factorial(int n) { 9 | int result = 1; 10 | for (int i = 2; i <= n; i++) 11 | result *= i; 12 | return result; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | let start = Date.now() 2 | let count = 0 3 | 4 | for (let i = 0; i <= 100000000; i++) { 5 | count += i 6 | } 7 | 8 | let end = Date.now() 9 | let elapsed = end - start 10 | console.log(count, "elapsed", elapsed, "ms") -------------------------------------------------------------------------------- /test/test.json: -------------------------------------------------------------------------------- 1 | {"url":"https://github.com/microsoft/vscode/issues/210849","title":"Nested folders showing up as 1 item after using and removing New File extension (SOLVED)"} 2 | {"url":"https://github.com/microsoft/vscode/issues/210848","title":"[Testing] continuous run integration issues"} 3 | {"url":"https://github.com/microsoft/vscode/issues/210847","title":"No Add Functionality Disable Not Available"} 4 | {"title":"update issue ","url":"https://github.com/microsoft/vscode/issues/210846"} 5 | {"url":"https://github.com/microsoft/vscode/issues/210844","title":"Slow completions in Python + Jupyter: Code process pegged at 100% CPU"} 6 | {"title":"Merge editor","url":"https://github.com/microsoft/vscode/issues/210843"} 7 | {"url":"https://github.com/microsoft/vscode/issues/210842","title":"Accessibility: setting caret position via IAccessible2 doesn't work"} 8 | {"url":"https://github.com/microsoft/vscode/issues/210841","title":"Unable To RUN my JavaScript File"} 9 | {"title":"Re-enable command guide for large commands","url":"https://github.com/microsoft/vscode/issues/210840"} 10 | {"title":"sites do not appear","url":"https://github.com/microsoft/vscode/issues/210838"} 11 | {"url":"https://github.com/microsoft/vscode/issues/210837","title":"Extension updating issue"} 12 | {"url":"https://github.com/microsoft/vscode/issues/210836","title":"[Accessibility] warning audio cue is not working as expected"} 13 | {"url":"https://github.com/microsoft/vscode/issues/210833","title":"_smoothScroll in terminal seems to occur more than once per frame"} 14 | -------------------------------------------------------------------------------- /test/test.kt: -------------------------------------------------------------------------------- 1 | fun main() { 2 | println("What's your name?") 3 | val name = readln() 4 | println("Hello, $name!") 5 | } -------------------------------------------------------------------------------- /test/test.lua: -------------------------------------------------------------------------------- 1 | -- Define a function to print "Hello, world!" 2 | function sayHello() 3 | print("Hello, world!") 4 | end 5 | 6 | -- Call the function to execute it 7 | sayHello() 8 | -------------------------------------------------------------------------------- /test/test.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | start_time = time.time() 4 | count = 0 5 | 6 | for i in range(10): 7 | count += i 8 | print("hi", i) 9 | time.sleep(1) 10 | 11 | print("done", count) 12 | elapsed_time = time.time() - start_time 13 | print("elapsed time:", elapsed_time, "seconds") 14 | -------------------------------------------------------------------------------- /test/test.rs: -------------------------------------------------------------------------------- 1 | use std::thread; 2 | use std::time::{Duration, Instant}; 3 | 4 | fn main() { 5 | let start_time = Instant::now(); 6 | let mut count = 0; 7 | 8 | for i in 0..3 { 9 | count += i; 10 | println!("hi {}", i); 11 | thread::sleep(Duration::from_millis(1)); 12 | } 13 | 14 | println!("done {}", count); 15 | let elapsed_time = start_time.elapsed(); 16 | println!("elapsed time: {:?}", elapsed_time); 17 | } -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | echo "hello bash" -------------------------------------------------------------------------------- /test/test.ts: -------------------------------------------------------------------------------- 1 | class Person { 2 | name: string; 3 | age: number; 4 | 5 | constructor(name: string, age: number) { 6 | this.name = name; 7 | this.age = age; 8 | } 9 | 10 | greet(): string { 11 | return `Hello, my name is ${this.name} and I am ${this.age} years old.`; 12 | } 13 | } 14 | 15 | // Usage 16 | const person = new Person("Alice", 25); 17 | console.log(person.greet()); // Output: Hello, my name is Alice and I am 25 years old. 18 | -------------------------------------------------------------------------------- /test/test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface GreetingProps { 4 | name: string; 5 | } 6 | 7 | const Greeting: React.FC = ({ name }) => { 8 | return ( 9 |
10 |

Hello, {name}!

11 |

Welcome to the TypeScript and React world.

12 |
13 | ); 14 | }; 15 | 16 | export default Greeting; 17 | -------------------------------------------------------------------------------- /test/test.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn fib(n: usize) -> u64 { 4 | var a: u64 = 0; 5 | var b: u64 = 1; 6 | var temp: u64; 7 | for (i := 0; i < n; i += 1) { 8 | temp = a + b; 9 | a = b; 10 | b = temp; 11 | } 12 | return a; 13 | } 14 | 15 | pub fn main() void { 16 | const n: usize = 10; // Change this value to generate Fibonacci sequence up to a different number. 17 | for (i := 0; i < n; i += 1) { 18 | const fib_number = fib(i); 19 | std.debug.print("{} ", .{fib_number}); 20 | } 21 | std.debug.print("\n"); 22 | } 23 | -------------------------------------------------------------------------------- /test/test/test/test.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/red-rs/red/84639fb1a020a3387d7b5d4d0acc0dbd53baf1e9/test/test/test/test.rs -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | let start = Date.now() 2 | let count = 0 3 | 4 | for (let i = 0; i <= 100000000; i++) { 5 | count += i 6 | } 7 | 8 | let end = Date.now() 9 | let elapsed = end - start 10 | console.log(count, "elapsed", elapsed, "ms") 11 | 12 | start = Date.now() 13 | count = 0 14 | 15 | for (let i = 0; i <= 100000000; i++) { 16 | count += i 17 | } 18 | 19 | end = Date.now() 20 | elapsed = end - start 21 | console.log(count, "elapsed", elapsed, "ms") -------------------------------------------------------------------------------- /themes/darcula.yml: -------------------------------------------------------------------------------- 1 | identifier: "#abb7c4" 2 | field_identifier: "#abb7c4" 3 | property_identifier: "#abb7c4" 4 | property: "#abb7c4" 5 | string: "#6A8759" 6 | keyword: "#CC8242" 7 | constant: "#83d2fa" 8 | number: "#83d2fa" 9 | integer: "#83d2fa" 10 | float: "#83d2fa" 11 | variable.builtin: "#CC8242" 12 | function: "#F6C87B" 13 | function.call: "#afaff9" 14 | method: "#F6C87B" 15 | comment: "#707070" 16 | namespace: "#abb7c4" 17 | type: "#CC8242" 18 | tag.attribute: "#c6a5fc" 19 | accent_color: "#C07C41" 20 | accent_color2: "#CC8242" 21 | -------------------------------------------------------------------------------- /themes/material.yml: -------------------------------------------------------------------------------- 1 | identifier: "#F1FEFF" 2 | field_identifier: "#F1FEFF" 3 | property_identifier: "#F1FEFF" 4 | property: "#F1FEFF" 5 | string: "#BDF5A0" 6 | keyword: "#83d2fa" 7 | constant: "#f78c6c" 8 | number: "#f78c6c" 9 | integer: "#f78c6c" 10 | float: "#f78c6c" 11 | variable: "#F1FEFF" 12 | variable.builtin: "#83d2fa" 13 | function: "#8AA9F9" 14 | function.call: "#8AA9F9" 15 | function.macro: "#8AA9F9" 16 | method: "#8AA9F9" 17 | comment: "#767676" 18 | namespace: "#ffcb6b" 19 | type: "#ffcb6b" 20 | tag.attribute: "#F1FEFF" 21 | accent_color: "#83d2fa" 22 | accent_color2: "#83d2fa" 23 | accent_color3: "#F1FEFF" 24 | 25 | # more: https://material-theme.com/docs/reference/color-palette -------------------------------------------------------------------------------- /themes/red.yml: -------------------------------------------------------------------------------- 1 | identifier: "#A5FCB6" 2 | field_identifier: "#A5FCB6" 3 | property_identifier: "#A5FCB6" 4 | property: "#A5FCB6" 5 | string: "#F9D992" 6 | keyword: "#f992e6" 7 | constant: "#f992e6" 8 | number: "#f992e6" 9 | integer: "#f992e6" 10 | float: "#f992e6" 11 | variable: "#A5FCB6" 12 | variable.builtin: "#f992e6" 13 | function: "#afaff9" 14 | function.call: "#afaff9" 15 | method: "#afaff9" 16 | comment: "#767676" 17 | namespace: "#c6a5fc" 18 | type: "#c6a5fc" 19 | tag.attribute: "#c6a5fc" 20 | tag: "#c6a5fc" 21 | error: "#A5FCB6" 22 | 23 | # lncolor: "#A5FCB6" 24 | accent_color: "#f992e6" 25 | accent_color2: "#A5FCB6" 26 | -------------------------------------------------------------------------------- /themes/vesper.yml: -------------------------------------------------------------------------------- 1 | identifier: "#A5FCB6" 2 | field_identifier: "#A5FCB6" 3 | property_identifier: "#A5FCB6" 4 | property: "#A5FCB6" 5 | string: "#b1fce5" 6 | keyword: "#a0a0a0" 7 | constant: "#f6c99f" 8 | number: "#f6c99f" 9 | integer: "#f6c99f" 10 | float: "#f6c99f" 11 | variable: "#ffffff" 12 | variable.builtin: "#f992e6" 13 | function: "#f6c99f" 14 | function.call: "#f6c99f" 15 | method: "#f6c99f" 16 | comment: "#585858" 17 | namespace: "#f6c99f" 18 | type: "#f6c99f" 19 | tag.attribute: "#c6a5fc" 20 | tag: "#c6a5fc" 21 | error: "#A5FCB6" 22 | 23 | # lncolor: "#A5FCB6" 24 | accent_color: "#f992e6" 25 | accent_color2: "#A5FCB6" 26 | ecolor: "#ff3333" 27 | dircolor: "#f6c99f" 28 | filecolor: "#ffffff" 29 | activefilecolor: "#b1fce5" 30 | selcolor: "#8a8a8a" -------------------------------------------------------------------------------- /tmux-last.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PREV_TMUX_COMMAND=`cat /tmp/prev-tmux-command` 4 | echo $PREV_TMUX_COMMAND 5 | tmux send-keys -t 1 $PREV_TMUX_COMMAND Enter -------------------------------------------------------------------------------- /tmux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PANES=$(tmux list-panes | wc -l) 4 | 5 | if [ "$PANES" -le 1 ]; then 6 | tmux split-window -v 7 | fi 8 | 9 | tmux send-keys -t 1 "$@" Enter 10 | echo "$1" > /tmp/prev-tmux-command --------------------------------------------------------------------------------