├── tools └── data-migration │ ├── lite │ ├── colors │ │ ├── core │ │ │ ├── style.lua │ │ │ └── common.lua │ │ ├── README.md │ │ └── lite2ecode.lua │ └── language │ │ ├── core │ │ ├── style.lua │ │ ├── syntax.lua │ │ ├── config.lua │ │ ├── json.lua │ │ └── common.lua │ │ ├── README.md │ │ └── lite2ecode.lua │ └── README.md ├── docs ├── xmltools.md ├── autocomplete.md ├── fileassociations.md ├── git.md ├── formatter.md ├── spellchecker.md ├── linter.md ├── lsp.md ├── uicustomizations.md ├── debugger.md ├── aiassistant.md └── customlanguages.md ├── LICENSE ├── .github └── workflows │ └── ecode-release.yml └── README.md /tools/data-migration/lite/colors/core/style.lua: -------------------------------------------------------------------------------- 1 | local style = {} 2 | style.syntax = {} 3 | style.syntax_fonts = {} 4 | style.log = {} 5 | return style 6 | -------------------------------------------------------------------------------- /tools/data-migration/lite/language/core/style.lua: -------------------------------------------------------------------------------- 1 | local style = {} 2 | style.syntax = {} 3 | style.syntax_fonts = {} 4 | style.log = {} 5 | return style 6 | -------------------------------------------------------------------------------- /tools/data-migration/README.md: -------------------------------------------------------------------------------- 1 | Data Migration tools are tools to facilitate the migration of language definitions and color schemes 2 | from other popular editors. 3 | -------------------------------------------------------------------------------- /docs/xmltools.md: -------------------------------------------------------------------------------- 1 | ## XML Tools 2 | 3 | The XML Tools plugin (disabled by default) provides some nice to have improvements when editing XML 4 | content. 5 | 6 | ### XML Tools config object keys 7 | 8 | * **auto_edit_match**: Automatically edits the open/close tag when the user edits the element tag name (syncs open and close element names). 9 | * **highlight_match**: When the user moves the cursor to an open or close tag it will highlight the matched open/close tag. 10 | -------------------------------------------------------------------------------- /tools/data-migration/lite/colors/README.md: -------------------------------------------------------------------------------- 1 | # Small script to migrate lite and lite-xl color schemes to ecode color schemes. 2 | 3 | ## Usage 4 | 5 | You'll need to have installed lua binary, then run: 6 | 7 | `lua lite2ecode.lua lite_color_scheme.lua > color_scheme_name.conf` 8 | 9 | Then you can copy the language definition to the [ecode color schemes folder](https://github.com/SpartanJ/ecode/?tab=readme-ov-file#custom-editor-color-schemes). 10 | 11 | If you want to ecode support the color schemes that you're porting in the base install please [open an issue](https://github.com/SpartanJ/ecode/issues) and share the color scheme. 12 | -------------------------------------------------------------------------------- /tools/data-migration/lite/language/README.md: -------------------------------------------------------------------------------- 1 | # Small script to migrate lite and lite-xl language definitions to ecode language definitions. 2 | 3 | ## Usage 4 | 5 | You'll need to have installed lua binary, then run: 6 | 7 | `lua lite2ecode.lua lite_language_definition.lua > language_name.json` 8 | 9 | Then you can copy the language definition to the [ecode languages folder](https://github.com/SpartanJ/ecode/#custom-languages-support). 10 | 11 | If you want to ecode support the language that you're porting in the base install please [open an issue](https://github.com/SpartanJ/ecode/issues) 12 | and share the language definition JSON. 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Martín Lucas Golini 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /docs/autocomplete.md: -------------------------------------------------------------------------------- 1 | ## Auto Complete 2 | 3 | The auto-complete plugin is in charge of providing suggestions for code-completion and signature help. 4 | 5 | ### Auto Complete config object keys 6 | 7 | * **max_label_characters**: Maximum characters displayed in the suggestion box. 8 | * **suggestions_syntax_highlight**: Enables/disables syntax highlighting in suggestions. 9 | 10 | ### Auto Complete keybindings object keys 11 | 12 | * **autocomplete-close-signature-help**: Closes the signature help 13 | * **autocomplete-close-suggestion**: Closes the suggestions 14 | * **autocomplete-first-suggestion**: Moves to the first suggestion in the auto complete list 15 | * **autocomplete-last-suggestion**: Moves to the last suggestion in the auto complete list 16 | * **autocomplete-next-signature-help**: Moves to the next signature help 17 | * **autocomplete-next-suggestion**: Moves to the next suggestion 18 | * **autocomplete-next-suggestion-page**: Moves to the next page of suggestions 19 | * **autocomplete-pick-suggestion**: Picks a suggestion 20 | * **autocomplete-pick-suggestion-alt**: Picks a suggestion (alternative keybinding) 21 | * **autocomplete-pick-suggestion-alt-2**: Picks a suggestion (alternative keybinding) 22 | * **autocomplete-prev-signature-help**: Moves to the previous signature help 23 | * **autocomplete-prev-suggestion**: Moves to the previous suggestion 24 | * **autocomplete-prev-suggestion-page**: Moves to the previous suggestion page 25 | * **autocomplete-update-suggestions**: Request to display or update currently displayed suggestions (if possible) 26 | -------------------------------------------------------------------------------- /tools/data-migration/lite/language/core/syntax.lua: -------------------------------------------------------------------------------- 1 | local common = require "core.common" 2 | 3 | local syntax = {} 4 | syntax.items = {} 5 | 6 | local plain_text_syntax = { name = "Plain Text", patterns = {}, symbols = {} } 7 | 8 | 9 | function syntax.add(t) 10 | if type(t.space_handling) ~= "boolean" then t.space_handling = true end 11 | 12 | if t.patterns then 13 | -- the rule %s+ gives us a performance gain for the tokenizer in lines with 14 | -- long amounts of consecutive spaces, can be disabled by plugins where it 15 | -- causes conflicts by declaring the table property: space_handling = false 16 | if t.space_handling then 17 | table.insert(t.patterns, { pattern = "%s+", type = "normal" }) 18 | end 19 | 20 | -- this rule gives us additional performance gain by matching every word 21 | -- that was not matched by the syntax patterns as a single token, preventing 22 | -- the tokenizer from iterating over each character individually which is a 23 | -- lot slower since iteration occurs in lua instead of C and adding to that 24 | -- it will also try to match every pattern to a single char (same as spaces) 25 | table.insert(t.patterns, { pattern = "%w+%f[%s]", type = "normal" }) 26 | end 27 | 28 | table.insert(syntax.items, t) 29 | end 30 | 31 | 32 | local function find(string, field) 33 | local best_match = 0 34 | local best_syntax 35 | for i = #syntax.items, 1, -1 do 36 | local t = syntax.items[i] 37 | local s, e = common.match_pattern(string, t[field] or {}) 38 | if s and e - s > best_match then 39 | best_match = e - s 40 | best_syntax = t 41 | end 42 | end 43 | return best_syntax 44 | end 45 | 46 | function syntax.get(filename, header) 47 | return find(common.basename(filename), "files") 48 | or (header and find(header, "headers")) 49 | or plain_text_syntax 50 | end 51 | 52 | 53 | return syntax 54 | -------------------------------------------------------------------------------- /docs/fileassociations.md: -------------------------------------------------------------------------------- 1 | # File Associations in ecode Projects 2 | 3 | ecode supports project-specific file associations, allowing you to map file patterns or names to specific programming languages for syntax highlighting, autocompletion, and other language-specific features. This is configured via the `.ecode/settings.json` file in your project root, using the same format as VS Code for easy compatibility. 4 | 5 | ## Setup 6 | 7 | 1. **Create the configuration file**: In your project's root directory, create a folder named `.ecode` if it doesn't exist, and inside it, add a `settings.json` file. 8 | 9 | 2. **Add file associations**: In `settings.json`, include a `"files.associations"` object. Keys are glob patterns or file names, and values are the target [language ID](https://github.com/SpartanJ/ecode/?tab=readme-ov-file#language-support-table) (e.g., "cpp" for C++). 10 | 11 | Example ` .ecode/settings.json`: 12 | ```json 13 | { 14 | "files.associations": { 15 | "glob_pattern": "language_name", 16 | "functional": "cpp", 17 | "map": "cpp", 18 | "**/c++/**": "cpp" 19 | } 20 | } 21 | ``` 22 | 23 | - **Simple file name**: `"functional": "cpp"` associates files named "functional" with C++. 24 | - **Glob pattern**: `"**/c++/**": "cpp"` matches any file in a subdirectory named "c++". 25 | - Language IDs follow standard conventions (e.g., "javascript", "python", "json"). Check ecode's [supported languages for exact IDs](https://github.com/SpartanJ/ecode/?tab=readme-ov-file#language-support-table). 26 | 27 | 3. **Reload the project**: Open or reload your project in ecode. The associations will apply automatically to matching files. 28 | 29 | ## VS Code Fallback 30 | 31 | If `.ecode/settings.json` does not exist, ecode will automatically fall back to `.vscode/settings.json` (if present) and read the `"files.associations"` from there. This ensures compatibility with VS Code workspaces. You can migrate by copying the relevant section to `.ecode/settings.json` for ecode-specific overrides. 32 | -------------------------------------------------------------------------------- /tools/data-migration/lite/colors/lite2ecode.lua: -------------------------------------------------------------------------------- 1 | local style = require "core.style" 2 | local common = require "core.common" 3 | 4 | function common.color_to_hex(rgba) 5 | r = math.floor(rgba[1]) % 256 6 | g = math.floor(rgba[2]) % 256 7 | b = math.floor(rgba[3]) % 256 8 | a = math.floor(rgba[4]) % 256 9 | if a < 255 then 10 | return string.format("#%02x%02x%02x%02x", r, g, b, a) 11 | end 12 | return string.format("#%02x%02x%02x", r, g, b) 13 | end 14 | 15 | function print_syntax(prop, eprop) 16 | if style.syntax[prop] then 17 | if eprop then 18 | print(eprop .. "=" .. common.color_to_hex(style.syntax[prop])) 19 | else 20 | print(prop .. "=" .. common.color_to_hex(style.syntax[prop])) 21 | end 22 | end 23 | end 24 | 25 | function print_style(prop) 26 | if style[prop] then 27 | print(prop .. "=" .. common.color_to_hex(style[prop])) 28 | end 29 | end 30 | 31 | function print_lint(prop, eprop) 32 | if style.lint and style.lint[prop] then 33 | if eprop then 34 | print(eprop .. "=" .. common.color_to_hex(style.lint[prop])) 35 | else 36 | print(prop .. "=" .. common.color_to_hex(style.lint[prop])) 37 | end 38 | end 39 | end 40 | if arg[1] == nil then 41 | print("Expected a file name to parse") 42 | return 43 | end 44 | 45 | for i = 1, #arg do 46 | dofile(arg[i]) 47 | end 48 | 49 | local section_name = arg[1]:match("([^/\\]+)%.lua$") 50 | print("[" .. section_name .. "]") 51 | print_style("background") 52 | print_style("text") 53 | print_style("caret") 54 | print_style("selection") 55 | print_style("line_highlight") 56 | print_style("line_number") 57 | print_style("line_number2") 58 | print_lint("error") 59 | print_lint("warning") 60 | print_lint("info", "notice") 61 | print("") 62 | print_syntax("normal") 63 | print_syntax("keyword") 64 | print_syntax("keyword2") 65 | print_syntax("parameter", "keyword3") 66 | print_syntax("number") 67 | print_syntax("literal") 68 | print_syntax("string") 69 | print_syntax("operator") 70 | print_syntax("function") 71 | print_syntax("symbol") 72 | print_syntax("comment") 73 | print_syntax("link") 74 | print_syntax("link_hover") 75 | -------------------------------------------------------------------------------- /docs/git.md: -------------------------------------------------------------------------------- 1 | ## Git integration 2 | 3 | *ecode* provides some basic Git integration (more features will come in the future). Its main purpose 4 | is to help the user to do the most basics operations with Git. Some of the current features supported: 5 | git status and stats visualization (files states), commit, push, checkout, pull, fetch, fast-forward 6 | merge, creating+renaming+deleting branches, managing stashes. All stats will be automatically 7 | updated/refreshed in real time. There's also some basic configuration available. 8 | The plugin requires the user to have a `git` binary installed and available in `PATH` environment variable. 9 | 10 | ### `git.json` format 11 | 12 | The format follows the same pattern that all previous configuration files. Configuration is represented 13 | in a JSON file with three main keys: `config`, `keybindings`, `servers`. 14 | 15 | C and C++ LSP server example (using [clangd](https://clangd.llvm.org/)) 16 | 17 | ```json 18 | { 19 | "config": { 20 | "silent": false, 21 | "status_recurse_submodules": true, 22 | "statusbar_display_branch": true, 23 | "statusbar_display_modifications": true, 24 | "ui_refresh_frequency": "5s" 25 | }, 26 | "keybindings": { 27 | "git-blame": "alt+shift+b" 28 | } 29 | } 30 | ``` 31 | 32 | ### Git config object keys 33 | 34 | * **silent**: Enable/Disable non-critical Git logs. 35 | * **status_recurse_submodules**: Enables/disables recursing sub-modules for the file status report. 36 | * **statusbar_display_branch**: Enables/disables an always visible status on the bottom statusbar. 37 | * **statusbar_display_modifications**: Enables/disables if the number of lines affected is displayed in the statusbar. 38 | * **ui_refresh_frequency**: Indicates the frequency in which the status is updated (it will only trigger updates if changes are detected inside the `.git` directory). 39 | * **filetree_highlight_changes**: Enables/disables the highlighting of changes on the file-tree. 40 | * **filetree_highlight_style_color**: Allows to change the highlight color in the file-tree. 41 | 42 | ### Git keybindings object keys 43 | 44 | * **git-blame**: Keybinding to display the a git blame summary over the current positioned line. 45 | -------------------------------------------------------------------------------- /tools/data-migration/lite/language/core/config.lua: -------------------------------------------------------------------------------- 1 | SCALE = 1 2 | local config = {} 3 | 4 | config.fps = 60 5 | config.max_log_items = 800 6 | config.message_timeout = 5 7 | config.mouse_wheel_scroll = 50 * SCALE 8 | config.animate_drag_scroll = false 9 | config.scroll_past_end = true 10 | ---@type "expanded" | "contracted" | false @Force the scrollbar status of the DocView 11 | config.force_scrollbar_status = false 12 | config.file_size_limit = 10 13 | config.ignore_files = { 14 | -- folders 15 | "^%.svn/", "^%.git/", "^%.hg/", "^CVS/", "^%.Trash/", "^%.Trash%-.*/", 16 | "^node_modules/", "^%.cache/", "^__pycache__/", 17 | -- files 18 | "%.pyc$", "%.pyo$", "%.exe$", "%.dll$", "%.obj$", "%.o$", 19 | "%.a$", "%.lib$", "%.so$", "%.dylib$", "%.ncb$", "%.sdf$", 20 | "%.suo$", "%.pdb$", "%.idb$", "%.class$", "%.psd$", "%.db$", 21 | "^desktop%.ini$", "^%.DS_Store$", "^%.directory$", 22 | } 23 | config.symbol_pattern = "[%a_][%w_]*" 24 | config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-" 25 | config.undo_merge_timeout = 0.3 26 | config.max_undos = 10000 27 | config.max_tabs = 8 28 | config.always_show_tabs = true 29 | -- Possible values: false, true, "no_selection" 30 | config.highlight_current_line = true 31 | config.line_height = 1.2 32 | config.indent_size = 2 33 | config.tab_type = "soft" 34 | config.keep_newline_whitespace = false 35 | config.line_limit = 80 36 | config.max_project_files = 2000 37 | config.transitions = true 38 | config.disabled_transitions = { 39 | scroll = false, 40 | commandview = false, 41 | contextmenu = false, 42 | logview = false, 43 | nagbar = false, 44 | tabs = false, 45 | tab_drag = false, 46 | statusbar = false, 47 | } 48 | config.animation_rate = 1.0 49 | config.blink_period = 0.8 50 | config.disable_blink = false 51 | config.draw_whitespace = false 52 | config.borderless = false 53 | config.tab_close_button = true 54 | config.max_clicks = 3 55 | 56 | -- set as true to be able to test non supported plugins 57 | config.skip_plugins_version = false 58 | 59 | -- virtual representation of plugins config table 60 | config.plugins = {} 61 | 62 | -- Disable these plugins by default. 63 | config.plugins.trimwhitespace = false 64 | config.plugins.drawwhitespace = false 65 | 66 | return config 67 | -------------------------------------------------------------------------------- /docs/formatter.md: -------------------------------------------------------------------------------- 1 | ## Auto Formatter 2 | 3 | The formatter plugin works exactly like the linter plugin, but it will execute tools that auto-format code. 4 | *ecode* provides support for several languages by default with can be extended easily by expanding the 5 | `formatters.json` configuration. `formatters.json` default configuration can be obtained from [here](https://raw.githubusercontent.com/SpartanJ/eepp/develop/bin/assets/plugins/formatters.json). 6 | It also supports some formatters natively, this means that the formatter comes with ecode without requiring any external dependency. 7 | And also supports LSP text document formatting, meaning that if you're running an LSP that supports formatting documents, formatting will be available too. 8 | To configure new formatters you can create a new `formatters.json` file in the [default configuration path](../README.md##plugins-configuration-files-location) of *ecode*. 9 | 10 | ### `formatters.json` format 11 | 12 | ```json 13 | { 14 | "config": { 15 | "auto_format_on_save": false 16 | }, 17 | "keybindings": { 18 | "format-doc": "alt+f" 19 | }, 20 | "formatters": [ 21 | { 22 | "file_patterns": ["%.js$", "%.ts$"], 23 | "command": "prettier $FILENAME" 24 | } 25 | ] 26 | } 27 | ``` 28 | 29 | ### Currently supported formatters 30 | 31 | Please check the [language support table](#language-support-table) 32 | 33 | ### Formatter config object keys 34 | 35 | * **auto_format_on_save**: Indicates if after saving the file it should be auto-formatted 36 | 37 | ### Formatter keybindings object keys 38 | 39 | * **format-doc**: Keybinding to format the doc with the configured language formatter 40 | 41 | ### Formatter JSON object keys 42 | 43 | * **file_patterns**: Array of [Lua Patterns](https://www.lua.org/manual/5.4/manual.html#6.4.1) representing the file extensions that must use the formatter 44 | * **command**: The command to execute to run the formatter. $FILENAME represents the file path 45 | * **type**: Indicates the mode that which the formatter outputs the results. Supported two possible options: "inplace" (file is replaced with the formatted version), "output" (newly formatted file is the stdout of the program, default option) or "native" (uses the formatter provided by ecode) 46 | * **url** (optional): The web page URL of the formatter 47 | -------------------------------------------------------------------------------- /tools/data-migration/lite/language/lite2ecode.lua: -------------------------------------------------------------------------------- 1 | local syntax = require "core.syntax" 2 | local json = require "core.json" 3 | 4 | if arg[1] == nil then 5 | print("Expected a file name to parse") 6 | return 7 | end 8 | 9 | for i = 1, #arg do 10 | dofile(arg[i]) 11 | end 12 | 13 | local function gsplit(text, pattern, plain) 14 | local splitStart, length = 1, #text 15 | return function () 16 | if splitStart then 17 | local sepStart, sepEnd = string.find(text, pattern, splitStart, plain) 18 | local ret 19 | if not sepStart then 20 | ret = string.sub(text, splitStart) 21 | splitStart = nil 22 | elseif sepEnd < sepStart then 23 | -- Empty separator! 24 | ret = string.sub(text, splitStart, sepStart) 25 | if sepStart < length then 26 | splitStart = sepStart + 1 27 | else 28 | splitStart = nil 29 | end 30 | else 31 | ret = sepStart > splitStart and string.sub(text, splitStart, sepStart - 1) or '' 32 | splitStart = sepEnd + 1 33 | end 34 | return ret 35 | end 36 | end 37 | end 38 | 39 | local function split(text, pattern, plain) 40 | local ret = {} 41 | for match in gsplit(text, pattern, plain) do 42 | table.insert(ret, match) 43 | end 44 | return ret 45 | end 46 | 47 | for i = 1, #syntax.items do 48 | syntax.items[i]["space_handling"] = nil 49 | for p = 1, #syntax.items[i].patterns do 50 | if type(syntax.items[i].patterns[p].type) == "table" then 51 | if syntax.items[i].patterns[p].pattern ~= nil then 52 | local ptr = {} 53 | local result = split(syntax.items[i].patterns[p].pattern, "()", true) 54 | for _, v in ipairs(result) do 55 | -- print(v) 56 | table.insert(ptr, "(" .. v .. ")") 57 | end 58 | local new_pattern = "" 59 | for z = 1, #ptr do 60 | new_pattern = new_pattern .. ptr[z] 61 | end 62 | syntax.items[i].patterns[p].pattern = new_pattern 63 | end 64 | 65 | if syntax.items[i].patterns[p].regex ~= nil then 66 | local ptr = {} 67 | local result = split(syntax.items[i].patterns[p].regex, "()", true) 68 | for _, v in ipairs(result) do 69 | -- print(v) 70 | table.insert(ptr, "(" .. v .. ")") 71 | end 72 | local new_regex = "" 73 | for z = 1, #ptr do 74 | new_regex = new_regex .. ptr[z] 75 | end 76 | syntax.items[i].patterns[p].regex = new_regex 77 | end 78 | 79 | local new_type = {} 80 | table.insert(new_type, "normal") 81 | for t = 1, #syntax.items[i].patterns[p].type do 82 | table.insert(new_type, syntax.items[i].patterns[p].type[t]) 83 | end 84 | syntax.items[i].patterns[p].type = new_type 85 | end 86 | end 87 | end 88 | 89 | if #syntax.items == 1 then 90 | print(json.encode(syntax.items[1])) 91 | else 92 | print(json.encode(syntax.items)) 93 | end 94 | -------------------------------------------------------------------------------- /docs/spellchecker.md: -------------------------------------------------------------------------------- 1 | # Spell Checker 2 | 3 | The spell checker provides real-time spell checking for source code across your project, helping you catch typos and maintain consistent spelling in comments, strings, and identifiers. 4 | 5 | ## Installation 6 | 7 | ### Prerequisites 8 | 9 | The spell checker requires the [typos](https://github.com/crate-ci/typos) tool to be installed and available in your system `PATH`. 10 | 11 | **Installation options:** 12 | 13 | - **Cargo:** `cargo install typos-cli` 14 | - **Homebrew (macOS):** `brew install typos-cli` 15 | - **Pre-built binaries:** Download from the [releases page](https://github.com/crate-ci/typos/releases) 16 | 17 | To verify installation, run: 18 | ```bash 19 | typos --version 20 | ``` 21 | 22 | ## Configuration 23 | 24 | Configuration is managed through ecode's settings file (JSON format) using the `config` and `keybindings` sections. 25 | 26 | ### Basic Structure 27 | 28 | ```json 29 | { 30 | "config": { 31 | "delay_time": "500ms" 32 | }, 33 | "keybindings": { 34 | "spellchecker-fix-typo": "alt+shift+return", 35 | "spellchecker-go-to-next-error": "", 36 | "spellchecker-go-to-previous-error": "" 37 | } 38 | } 39 | ``` 40 | 41 | ### Configuration Options 42 | 43 | #### `config` 44 | 45 | | Key | Type | Default | Description | 46 | |-----|------|---------|-------------| 47 | | `delay_time` | integer | 500 | Delay in milliseconds before running spell check after document changes | 48 | 49 | #### Keybindings 50 | 51 | | Command | Description | 52 | |---------|-------------| 53 | | `spellchecker-fix-typo` | Display suggestions for the current typo | 54 | | `spellchecker-go-to-next-error` | Navigate to the next spelling error | 55 | | `spellchecker-go-to-previous-error` | Navigate to the previous spelling error | 56 | 57 | ## Usage 58 | 59 | ### Basic Workflow 60 | 61 | 1. **Automatic Detection**: Spelling errors are highlighted automatically as you type 62 | 2. **Navigate Errors**: Use the navigation keybindings to jump between errors 63 | 3. **Fix Typos**: Position your cursor on a highlighted error and use the fix-typo command to see suggestions or right click the typo -> Spell Checker and pick the replacement word 64 | 65 | ## Troubleshooting 66 | 67 | ### Common Issues 68 | 69 | **Spell checker not working** 70 | - Verify `typos` is installed and in your PATH 71 | - Check the ecode console/logs for error messages 72 | - Ensure your configuration syntax is valid JSON 73 | 74 | **Too many false positives** 75 | - Consider configuring a custom `typos` configuration file (`.typos.toml`) in your project root 76 | - Refer to the [typos documentation](https://github.com/crate-ci/typos/blob/master/docs/reference.md) for advanced filtering options 77 | 78 | **Performance issues** 79 | - Increase the `delay_time` value to reduce frequency of checks 80 | - Consider excluding large files or directories via typos configuration 81 | 82 | ## Integration with typos 83 | 84 | This plugin leverages the `typos` tool's configuration system. You can create a `.typos.toml` file in your project root to: 85 | 86 | - Add custom dictionaries 87 | - Ignore specific files or patterns 88 | - Configure language-specific rules 89 | - Define project-specific corrections 90 | 91 | Example `.typos.toml`: 92 | ```toml 93 | [default.extend-words] 94 | # Add custom corrections 95 | teh = "the" 96 | recieve = "receive" 97 | 98 | [files] 99 | # Ignore certain file types 100 | extend-exclude = ["*.min.js", "*.lock"] 101 | ``` 102 | 103 | For complete configuration options, see the [typos documentation](https://github.com/crate-ci/typos/blob/master/docs/reference.md). 104 | -------------------------------------------------------------------------------- /docs/linter.md: -------------------------------------------------------------------------------- 1 | ## Linter 2 | 3 | Linter support is provided by executing already stablished linters from each language. 4 | *ecode* provides support for several languages by default and can be extended easily by expanding the 5 | `linters.json` configuration. `linters.json` default configuration can be obtained from [here](https://raw.githubusercontent.com/SpartanJ/eepp/develop/bin/assets/plugins/linters.json). 6 | To configure new linters you can create a new `linters.json` file in the [default configuration path](../README.md#plugins-configuration-files-location) of *ecode*. 7 | 8 | ### `linters.json` format 9 | 10 | The format is a very simple JSON object with a config object and array of objects containing the file 11 | formats supported, the [Lua pattern](https://www.lua.org/manual/5.4/manual.html#6.4.1) to find any error printed by the linter to the stdout, the position 12 | of each group of the pattern, and the command to execute. It also supports some optional extra object keys. 13 | 14 | JavaScript linter example (using [eslint](https://eslint.org/)) 15 | 16 | ```json 17 | { 18 | "config": { 19 | "delay_time": "0.5s" 20 | }, 21 | "linters": [ 22 | { 23 | "file_patterns": ["%.js$", "%.ts$"], 24 | "warning_pattern": "[^:]:(%d+):(%d+): ([^%[]+)%[([^\n]+)", 25 | "warning_pattern_order": { "line": 1, "col": 2, "message": 3, "type": 4 }, 26 | "command": "eslint --no-ignore --format unix $FILENAME" 27 | } 28 | ] 29 | } 30 | ``` 31 | 32 | That's all we need to have a working linter in *ecode*. Linters executables must be installed manually 33 | by the user, linters will not come with the editor, and they also need to be visible to the executable. 34 | This means that it must be on `PATH` environment variable or the path to the binary must be absolute. 35 | 36 | ### Currently supported linters 37 | 38 | Please check the [language support table](#language-support-table) 39 | 40 | ### Linter config object keys 41 | 42 | * **delay_time**: Delay to run the linter after editing a document 43 | * **enable_error_lens**: Enables error lens (prints the message inline) 44 | * **enable_lsp_diagnostics**: Boolean that enable/disable LSP diagnostics as part of the linting. Enabled by default. 45 | * **disable_lsp_languages**: Array of LSP languages disabled for LSP diagnostics. For example: `"disable_lsp_languages": ["lua", "python"]`, disables lua and python. 46 | * **disable_languages**: Array of linters disabled from external linter application diagnostics. For example: `"disable_languages": ["lua", "python"]`, disables luacheck and ruff respectively. 47 | * **goto_ignore_warnings**: Defines the behavior of the "linter-go-to-next-error" and "linter-go-to-previous-error" keybindings. If ignore warnings is true it will jump only between errors. 48 | 49 | ### Linter JSON object keys 50 | 51 | * **file_patterns**: Array of [Lua Patterns](https://www.lua.org/manual/5.4/manual.html#6.4.1) representing the file extensions that must use the linter 52 | * **warning_pattern**: [Lua Pattern](https://www.lua.org/manual/5.4/manual.html#6.4.1) to be parsed from the executable stdout 53 | * **warning_pattern_order**: The order where the line, column, error/warning/notice message, and the type of the message (warning, error, notice, info) are read. The pattern must have at least 3 groups (line, message, and type). The error type is auto-detected from its name. 54 | * **command**: The command to execute to run the linter. $FILENAME represents the file path. 55 | * **url** (optional): The web page URL of the linter 56 | * **expected_exitcodes**: Array of integer numbers accepted as parseable exit codes (optional) 57 | * **no_errors_exit_code**: Integer number representing the exit code that means that no errors were found (optional). 58 | * **deduplicate**: In case the linter outputs duplicated errors, this boolean will ignore duplicated errors (optional, boolean true/false) 59 | * **use_tmp_folder**: Temporal files (files representing the current status of the modified file) will be written in the default temporal folder of the operating system, otherwise it will be written in the same folder path of the modified file (optional, boolean true/false). 60 | -------------------------------------------------------------------------------- /docs/lsp.md: -------------------------------------------------------------------------------- 1 | ## LSP Client 2 | 3 | LSP support is provided by executing already stablished LSP from each language. 4 | *ecode* provides support for several languages by default and can be extended easily by expanding the 5 | `lspclient.json` configuration. `lspclient.json` default configuration can be obtained from [here](https://raw.githubusercontent.com/SpartanJ/eepp/develop/bin/assets/plugins/lspclient.json). 6 | To configure new LSPs you can create a new `lspclient.json` file in the [default configuration path](../README.md##plugins-configuration-files-location) of *ecode*. 7 | 8 | Important note: LSP servers can be very resource intensive and might not be always the best option for simple projects. 9 | 10 | Implementation details: LSP servers are only loaded when needed, no process will be opened until a 11 | supported file is opened in the project. 12 | 13 | ### `lspclient.json` format 14 | 15 | The format follows the same pattern that all previous configuration files. Configuration is represented 16 | in a JSON file with three main keys: `config`, `keybindings`, `servers`. 17 | 18 | C and C++ LSP server example (using [clangd](https://clangd.llvm.org/)) 19 | 20 | ```json 21 | { 22 | "config": { 23 | "hover_delay": "0.5s" 24 | }, 25 | "servers": [ 26 | { 27 | "language": "c", 28 | "name": "clangd", 29 | "url": "https://clangd.llvm.org/", 30 | "command": "clangd -log=error --background-index --limit-results=500 --completion-style=bundled", 31 | "file_patterns": ["%.c$", "%.h$", "%.C$", "%.H$", "%.objc$"] 32 | }, 33 | { 34 | "language": "cpp", 35 | "use": "clangd", 36 | "file_patterns": ["%.inl$", "%.cpp$", "%.hpp$", "%.cc$", "%.cxx$", "%.c++$", "%.hh$", "%.hxx$", "%.h++$", "%.objcpp$"] 37 | } 38 | ] 39 | } 40 | ``` 41 | 42 | That's all we need to have a working LSP in *ecode*. LSPs executables must be installed manually 43 | by the user, LSPs will not come with the editor, and they also need to be visible to the executable. 44 | This means that it must be on `PATH` environment variable or the path to the binary must be absolute. 45 | 46 | ### Currently supported LSPs 47 | 48 | Please check the [language support table](#language-support-table) 49 | 50 | ### LSP Client config object keys 51 | 52 | * **hover_delay**: The time the editor must wait to show symbol information when hovering any piece of code. 53 | * **server_close_after_idle_time**: The time the LSP Server will keep alive after all documents that consumes that LSP Server were closed. LSP Servers are spawned and killed on demand. 54 | * **semantic_highlighting**: Enable/Disable semantic highlighting (disabled by default, boolean) 55 | * **disable_semantic_highlighting_lang**: An array of languages where semantic highlighting should be disabled 56 | * **silent**: Enable/Disable non-critical LSP logs 57 | * **trim_logs**: If logs are enabled and trim_logs is enabled it will trim the line log size at maximum 1 KiB per line (useful for debugging) 58 | * **breadcrumb_navigation**: Enable/Disable the breadcrumb (enabled by default) 59 | * **breadcrumb_height**: Defines the height of the breadcrumb in [CSS length](https://eepp.ensoft.dev/page_cssspecification.html#length-data-type) 60 | 61 | ### LSP Client keybindings object keys 62 | 63 | * **lsp-symbol-info**: Keybinding to request symbol information 64 | * **lsp-go-to-definition**: Keybinding to "Go to Definition" 65 | * **lsp-go-to-declaration**: Keybinding to "Go to Declaration" 66 | * **lsp-go-to-implementation**: Keybinding to "Go to Implementation" 67 | * **lsp-go-to-type-definition**: Keybinding to "Go to Type Definition" 68 | * **lsp-symbol-references**: Keybinding to "Find References to Symbol Under Cursor" 69 | * **lsp-symbol-code-action**: Keybinding to "Code Action" 70 | * **lsp-switch-header-source**: Keybinding to "Switch Header/Source" (only available for C and C++) 71 | 72 | ### LSP Client JSON object keys 73 | 74 | * **language**: The LSP language identifier. Some identifiers can be found [here](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem) 75 | * **name**: The name of the language server 76 | * **url** (optional): The web page URL of the language server 77 | * **use** (optional): A server can be inherit the configuration from other server. This must be the name of the server configuration that inherits (useful for LSPs that support several languages like clang and typescript-language-server). 78 | * **file_patterns**: Array of [Lua Patterns](https://www.lua.org/manual/5.4/manual.html#6.4.1) representing the file extensions that must use the LSP client 79 | * **command**: The command to execute to run the LSP. It's possible to override the default LSP command by declaring the server in the `lspclient.json` config. It's also possible to specify a different command for each platform, given that it might change in some occasions per-platform. In that case an object should be used, with each key being a platform, and there's also a wildcard platform "other" to specify any other platform that does not match the platform definition. For example, `sourcekit-lsp` uses: `"command": {"macos": "xcrun sourcekit-lsp","other": "sourcekit-lsp"}` 80 | * **command_parameters** (optional): The command parameters. Parameters can be set from the **command** also, unless the command needs to run a binary with name with spaces. Also command_parameters can be used to add more parameters to the original command. The lsp configuration can be overridden from the lspclient.json in the user configuration. For example: a user trying to append some command line arguments to clang would need to do something like: `{"name": "clangd","command_parameters": "--background-index-priority=background --malloc-trim"}` 81 | * **rootIndicationFileNames** (optional): Some languages need to indicate the project root path to the LSP work correctly. This is an array of files that might indicate where the root path is. Usually this is resolver by the LSP itself, but it might help in some situations. 82 | * **initializationOptions** (optional): These are custom initialization options that can be passed to the LSP. Usually not required, but it will allow the user to configure the LSP. More information can be found [here](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize). 83 | * **host** (optional): It's possible to connect to LSP servers via TCP. This is the host location of the LSP. When using TCP connections *command* can be empty or can be used to initialize the LSP server. And then use the LSP through a TCP connection. 84 | * **port** (optional): It's possible to connect to LSP servers via TCP. This is the post location of the LSP. 85 | * **env** (optional): Array of strings with environment variables added to the process environment. 86 | -------------------------------------------------------------------------------- /docs/uicustomizations.md: -------------------------------------------------------------------------------- 1 | ## UI Customizations 2 | 3 | ### Custom editor color schemes 4 | 5 | Custom editor color schemes can be added in the user color schemes directory found at: 6 | 7 | * *Linux*: uses `XDG_CONFIG_HOME`, usually translates to `~/.config/ecode/editor/colorschemes` 8 | * *macOS*: uses `Application Support` folder in `HOME`, usually translates to `~/Library/Application Support/ecode/editor/colorschemes` 9 | * *Windows*: uses `APPDATA`, usually translates to `C:\Users\{username}\AppData\Roaming\ecode\editor\colorschemes` 10 | 11 | Any file written in the directory will be treated as an editor color scheme file. Each file can contain 12 | any number of color schemes. 13 | 14 | The format of a color scheme can be read from [here](https://github.com/SpartanJ/eepp/blob/develop/bin/assets/colorschemes/colorschemes.conf). 15 | 16 | ### Custom terminal color schemes 17 | 18 | Custom terminal color schemes can be added in the user terminal color schemes directory found at: 19 | 20 | * *Linux*: uses `XDG_CONFIG_HOME`, usually translates to `~/.config/ecode/terminal/colorschemes` 21 | * *macOS*: uses `Application Support` folder in `HOME`, usually translates to `~/Library/Application Support/ecode/terminal/colorschemes` 22 | * *Windows*: uses `APPDATA`, usually translates to `C:\Users\{username}\AppData\Roaming\ecode\terminal\colorschemes` 23 | 24 | Any file written in the directory will be treated as a terminal color scheme file. Each file can contain 25 | any number of color schemes. 26 | 27 | The format of a color scheme can be read from [here](https://github.com/SpartanJ/eepp/blob/develop/bin/assets/colorschemes/terminalcolorschemes.conf). 28 | 29 | ### Custom UI themes 30 | 31 | Custom UI schemes can be added in the user UI themes directory found at: 32 | 33 | * *Linux*: uses `XDG_CONFIG_HOME`, usually translates to `~/.config/ecode/themes` 34 | * *macOS*: uses `Application Support` folder in `HOME`, usually translates to `~/Library/Application Support/ecode/themes` 35 | * *Windows*: uses `APPDATA`, usually translates to `C:\Users\{username}\AppData\Roaming\ecode\themes` 36 | 37 | A custom UI theme file must have the extension `.css`, ecode will look for all the files with `.css` 38 | extension in the directory, the UI theme name is the file name without the extension. The new theme 39 | will appear in `Settings -> Window -> UI Theme`. 40 | 41 | Custom UI themes allow customizing the editor at the user's will. Since ecode uses CSS to style all the 42 | elements of the UI, creating new themes is quite easy. It's possible to customize only the color palette 43 | but it's also possible to customize all the UI elements if desired. Customizing the whole UI theme can 44 | be extensive, but customizing the colors is as simple as changing the values of the CSS variables used 45 | to color the UI. For reference, the complete base UI theme used by ecode can be seen [here](https://github.com/SpartanJ/eepp/blob/develop/bin/assets/ui/breeze.css). 46 | The most important selector would be the `:root` selector, where all the variables are defined. Color 47 | variables can be easily extracted from that file. 48 | 49 | A simple example of a custom UI theme that changes only the tint colors, let's call it `Breeze Light Red.css`: 50 | 51 | ```css 52 | :root { 53 | --inherit-base-theme: true; 54 | --primary: #e93d66; 55 | --scrollbar-button: #a94074; 56 | --item-hover: #502834; 57 | --tab-hover: #5e3347; 58 | } 59 | ``` 60 | 61 | That effectively would create/add a new UI theme with light red colors. 62 | A very important detail is that if the UI theme must inherit the complete definition of the default theme, 63 | we must add `--inherit-base-theme: true` to the `:root` element, otherwise the UI theme must be defined 64 | completely, which means, every widget must be styled from scratch (not recommended given its complexity). 65 | It's also possible to override the style of the different widgets redefining their properties with the 66 | usual rules that apply to the well-known CSS specification (A.K.A. using adequate 67 | [specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity) and probably abusing the 68 | [!important](https://developer.mozilla.org/en-US/docs/Web/CSS/important) flag). 69 | 70 | ### Customizing the User Interface with `style.css` 71 | 72 | The `style.css` file is a powerful tool for customizing the user interface (UI) of ecode, allowing users to tailor the look and feel of the editor to their preferences. Inspired by Firefox's `userChrome.css`, this stylesheet enables you to override nearly all built-in UI style rules, providing extensive flexibility to personalize the editor's appearance. Whether you want to adjust colors, fonts, layouts, or other visual elements, `style.css` gives you fine-grained control over the UI. 73 | 74 | #### CSS Engine in ecode 75 | 76 | ecode's UI customization is powered by a custom CSS engine based on the eepp GUI framework, which closely aligns with the CSS3 specification. While it adheres to standard CSS3 conventions, the eepp CSS engine includes some unique properties and behaviors specific to ecode's rendering system. For a complete reference of supported properties, syntax, and eepp-specific extensions, consult the [eepp CSS specification](https://eepp.ensoft.dev/page_cssspecification.html). 77 | 78 | This CSS3-based engine ensures compatibility with familiar CSS syntax, making it easy for users with web development experience to customize the UI. However, some eepp-specific rules may require additional learning for optimal use. The specification documentation provides detailed guidance on these extensions, including examples and best practices. 79 | 80 | #### Location of `style.css` 81 | 82 | By default, ecode looks for the `style.css` file in a platform-specific configuration directory. If the file does not exist, you can create it in the appropriate location to start customizing the UI. The default paths are: 83 | 84 | - **Linux**: Uses the `XDG_CONFIG_HOME` environment variable, typically resolving to `~/.config/ecode/style.css`. 85 | - **macOS**: Stored in the `Application Support` folder within the user's home directory, typically `~/Library/Application Support/ecode/style.css`. 86 | - **Windows**: Located in the `APPDATA` directory, typically `C:\Users\{username}\AppData\Roaming\ecode\style.css`. 87 | 88 | If you prefer to store `style.css` in a custom location, you can specify an alternative file path using the `--css` command-line parameter when launching ecode. For example: 89 | 90 | ```bash 91 | ecode --css /path/to/custom/style.css 92 | ``` 93 | 94 | This flexibility allows you to maintain multiple stylesheets for different workflows or share them across systems. 95 | 96 | #### How Customization Works 97 | 98 | The `style.css` file operates similarly to Firefox's `userChrome.css`. It is loaded after ecode's default styles, meaning your custom rules take precedence and can override built-in styles for most UI elements. This includes, but is not limited to: 99 | 100 | - **Editor components**: Customize the appearance of the code editor, such as background colors, font sizes, or line spacing. 101 | - **UI elements**: Modify toolbars, menus, side panels, or status bars to match your preferred aesthetic or workflow. 102 | 103 | To get started, create or edit the `style.css` file in the appropriate directory and add standard CSS3 rules. For example, to add some padding to the PopUpMenu buttons, you might add: 104 | 105 | ```css 106 | PopUpMenu > * 107 | { 108 | padding-top: 4dp; 109 | padding-bottom: 4dp; 110 | } 111 | ``` 112 | 113 | For eepp-specific properties or advanced customization, refer to the [eepp CSS specification](https://eepp.ensoft.dev/page_cssspecification.html). Always test your changes to ensure compatibility, as some properties may behave differently due to ecode's custom rendering engine. 114 | 115 | #### Tips for Effective Customization 116 | 117 | - **Start small**: Begin with simple changes, such as adjusting colors or fonts, before tackling complex layout modifications. 118 | - **Use the Inspect Widgets tool**: If you're familiar with web development, you can inspect ecode's UI elements using its built-in widget inspector tool to identify class names or IDs for targeting specific components. Inspector can be found at `Settings -> Tools -> Inspect Widgets` . 119 | - **Backup your styles**: Save a copy of your `style.css` file before making significant changes to avoid losing your customizations. 120 | - **Leverage the community**: Explore online forums or the ecode community for shared `style.css` examples to inspire your customizations. 121 | 122 | #### Troubleshooting 123 | 124 | If your custom styles are not applied as expected: 125 | 126 | - Verify that the `style.css` file is in the correct directory or properly specified via the `--css` parameter. 127 | - Check for syntax errors in your CSS, as invalid rules may prevent the stylesheet from loading correctly. 128 | - Ensure that your selectors are specific enough to override ecode's default styles, as some built-in rules may have higher specificity. You can always rely on [!important](https://developer.mozilla.org/en-US/docs/Web/CSS/important) if needed. 129 | - Consult the [eepp CSS specification](https://eepp.ensoft.dev/page_cssspecification.html) to confirm that your properties are supported by the eepp CSS engine. 130 | 131 | By leveraging `style.css`, you can transform ecode's UI to suit your unique needs, creating a personalized and productive coding environment. 132 | -------------------------------------------------------------------------------- /docs/debugger.md: -------------------------------------------------------------------------------- 1 | ## Debugger 2 | 3 | *ecode* implements the [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol) (DAP) for debugger support. This enables seamless debugging integration with various languages through a common protocol. DAP is used by VS Code and other major editors and has been implemented for a wide range of programming languages. For more information, see the [list of implementations](https://microsoft.github.io/debug-adapter-protocol/implementors/adapters/). 4 | 5 | Initially, *ecode* provides support for some of the most common debug adapter implementations, with plans to add support for more languages in future updates. 6 | 7 | ### How It Works 8 | 9 | There are two ways to use the debugger in *ecode*: 10 | 11 | 1. **Using Default *ecode* Configurations:** These configurations are designed to work seamlessly with your build settings. The executable used in these default options is the `Run Target` of your currently selected `Build Configuration`. 12 | 13 | 2. **Using Custom Launch Configurations:** Users can provide custom launch configurations within the project folder. These configurations should be located at `.ecode/launch.json` or `.vscode/launch.json`. *ecode* uses the same `launch.json` format as VS Code, ensuring compatibility for users migrating from other DAP-based editors. However, *ecode* will only recognize currently supported debuggers, as DAP implementations can vary significantly in configuration details. 14 | 15 | ### Using *ecode* Default Configurations 16 | 17 | Once you have a `Build Configuration` with a selected `Run Target`, this will be used for the default `Launch` and `Attach` configurations for your language. 18 | 19 | - **Launch Configurations:** These configurations start an executable from your local environment, with the process managed by the debugger. This is the most common setup for most users. To use it: 20 | 21 | - Ensure your `Run Target` is ready. 22 | - Go to the `Debugger` tab in the Side Panel. 23 | - Select the debugger based on the language you want to debug. For example, C or C++ binaries can be debugged using `gdb` or `lldb-dap` (also known as `lldb-vscode`), depending on your platform. 24 | - Choose the appropriate `Debugger Configuration`, such as `Launch Binary`. 25 | - Click `Debug` or press `Ctrl/Cmd + F5` to start debugging. 26 | 27 | - **Attach Configurations:** These configurations allow the debugger to attach to an already running process, either locally or remotely. You can attach to a process via its process ID or its binary name/path. Some configurations include a `(wait)` option, which waits for the process to start before attaching. 28 | 29 | The default `Attach to Binary` option will also use the current `Run Target` to execute the binary. Unlike the `Launch` configuration, here the process is managed by *ecode* instead of the debugger. This feature is particularly useful for debugging CLI programs from the terminal if the `Run Target` is configured with `Run in Terminal`. 30 | 31 | ### Using Custom Launch Configurations 32 | 33 | *ecode* supports custom launch configurations through the `launch.json` file, which can be placed in either the `.ecode/` or `.vscode/` folder within your project directory. This format is fully compatible with VS Code, making it easy for users transitioning from other editors. 34 | 35 | #### Creating a `launch.json` File 36 | 37 | The `launch.json` file defines how debugging sessions are launched or attached. A basic configuration looks like this: 38 | 39 | ```json 40 | { 41 | "version": "0.2.0", 42 | "configurations": [ 43 | { 44 | "name": "Launch Program", 45 | "type": "lldb", 46 | "request": "launch", 47 | "program": "${workspaceFolder}/bin/myapp", 48 | "args": ["--verbose"], 49 | "cwd": "${workspaceFolder}", 50 | "stopOnEntry": false 51 | } 52 | ] 53 | } 54 | ``` 55 | 56 | #### Key Fields Explained 57 | 58 | - **`name`**: The display name for the configuration in the *ecode* debugger interface. 59 | - **`type`**: The debugger type, such as `gdb`, `lldb`, or any supported DAP adapter. 60 | - **`request`**: Specifies whether to `launch` a new process or `attach` to an existing one. 61 | - **`program`**: The path to the executable you want to debug. 62 | - **`args`**: Optional. Command-line arguments passed to the program. 63 | - **`cwd`**: The working directory for the program. 64 | - **`stopOnEntry`**: Optional. If `true`, the debugger will pause at the program's entry point. 65 | 66 | #### Advanced Customization 67 | 68 | You can create multiple configurations for different scenarios, such as: 69 | 70 | - Attaching to a remote process: 71 | 72 | ```json 73 | { 74 | "name": "Attach to Remote", 75 | "type": "gdb", 76 | "request": "attach", 77 | "host": "192.168.1.100", 78 | "port": 1234 79 | } 80 | ``` 81 | 82 | - Debugging with environment variables: 83 | 84 | ```json 85 | { 86 | "name": "Launch with Env Vars", 87 | "type": "lldb", 88 | "request": "launch", 89 | "program": "${workspaceFolder}/bin/myapp", 90 | "env": { 91 | "DEBUG": "1", 92 | "LOG_LEVEL": "verbose" 93 | } 94 | } 95 | ``` 96 | 97 | *ecode* will automatically detect and load configurations from `launch.json`, prioritizing `.ecode/launch.json` if both are present. This approach ensures flexibility and consistency across projects, whether you're starting fresh or migrating from VS Code. 98 | 99 | *ecode* supports the same `launch.json` schema as VS Code, including standard input types such as: 100 | 101 | - **`pickProcess`**: Prompts the user to select a running process from a list. This is useful for attaching the debugger to an already running application. 102 | - **`pickString`**: Allows the user to select from a predefined list of string options. 103 | - **`promptString`**: Prompts the user to manually enter a string value, useful for dynamic input during debugging sessions. 104 | 105 | In addition to these standard inputs, *ecode* extends functionality with an extra input type: 106 | 107 | - **`pickFile`**: This *ecode*-specific input allows users to select a binary file directly from the file system. It is particularly useful when you need to choose an executable to be run and debugged without hardcoding the path in your `launch.json`. 108 | 109 | These input options make custom configurations flexible and dynamic, adapting to different debugging workflows and environments. 110 | 111 | #### Variables Reference 112 | 113 | ecode supports all vscode variable substitution in debugger configuration files. Variable substitution is supported inside some key and value strings in `launch.json` files using **${variableName}** syntax. 114 | 115 | ##### Predefined variables 116 | 117 | The following predefined variables are supported: 118 | 119 | - **${userHome}** - the path of the user's home folder 120 | - **${workspaceFolder}** - the path of the folder opened in VS Code 121 | - **${workspaceFolderBasename}** - the name of the folder opened in VS Code without any slashes (/) 122 | - **${file}** - the current opened file 123 | - **${fileWorkspaceFolder}** - the current opened file's workspace folder 124 | - **${relativeFile}** - the current opened file relative to `workspaceFolder` 125 | - **${relativeFileDirname}** - the current opened file's dirname relative to `workspaceFolder` 126 | - **${fileBasename}** - the current opened file's basename 127 | - **${fileBasenameNoExtension}** - the current opened file's basename with no file extension 128 | - **${fileExtname}** - the current opened file's extension 129 | - **${fileDirname}** - the current opened file's folder path 130 | - **${fileDirnameBasename}** - the current opened file's folder name 131 | - **${cwd}** - the task runner's current working directory upon the startup of VS Code 132 | - **${lineNumber}** - the current selected line number in the active file 133 | - **${selectedText}** - the current selected text in the active file 134 | - **${execPath}** - the path to the running VS Code executable 135 | - **${defaultBuildTask}** - the name of the default build task 136 | - **${pathSeparator}** - the character used by the operating system to separate components in file paths 137 | - **${/}** - shorthand for **${pathSeparator}** 138 | 139 | ### Language-Specific Configurations 140 | 141 | In addition to general configurations, *ecode* offers language-specific settings, such as loading core dumps. Each debugger and language may provide different configurations based on the debugger's capabilities and the language's characteristics. 142 | 143 | ### Debugger keybindings object keys 144 | 145 | * **debugger-breakpoint-enable-toggle**: Toggles enable/disable current line breakpoint (if any) 146 | * **debugger-breakpoint-toggle**: Toggles breakpoint in current line 147 | * **debugger-continue-interrupt**: If debugger is running: continues or interrupt the current execution. If debugger is not running builds and run the debugger. 148 | * **debugger-start**: Starts the debugger 149 | * **debugger-start-stop**: Starts or stops the debugger (depending on its current status) 150 | * **debugger-step-into**: Steps into the current debugged line 151 | * **debugger-step-out**: Steps out the current debugged line 152 | * **debugger-step-over**: Steps over the current debugged line 153 | * **debugger-stop**: Stops the debugger 154 | * **toggle-status-app-debugger**: Opens/hides the debugger status bar panel 155 | 156 | ### Debugger config object keys 157 | 158 | * **fetch_globals**: Enable/Disable if global variables should be fetched automatically (when available) 159 | * **fetch_registers**: Enable/Disable if registers should be fetched automatically (when available) 160 | * **silent**: Enable/Disable non-critical Debugger logs 161 | -------------------------------------------------------------------------------- /docs/aiassistant.md: -------------------------------------------------------------------------------- 1 | # AI Assistant 2 | 3 | This feature allows you to interact directly with various AI models from different providers within the editor. 4 | 5 | ## Overview 6 | 7 | The LLM Chat UI provides a dedicated panel where you can: 8 | 9 | * Start multiple chat conversations. 10 | * Interact with different LLM providers and models (like OpenAI, Anthropic, Google AI, Mistral, DeepSeek, XAI, and local models via Ollama/LMStudio). 11 | * Manage chat history (save, rename, clone, lock). 12 | * Use AI assistance directly related to your code or general queries without leaving the editor. 13 | 14 | ## Configuration 15 | 16 | Configuration is managed through `ecode`'s settings file (a JSON file). The relevant sections are `config`, `keybindings`, and `providers`. 17 | 18 | ```json 19 | // Example structure in your ecode settings file 20 | { 21 | "config": { 22 | // API Keys go here 23 | }, 24 | "keybindings": { 25 | // Chat UI keybindings 26 | }, 27 | "providers": { 28 | // LLM Provider definitions 29 | } 30 | } 31 | ``` 32 | 33 | ### API Keys (`config` section) 34 | 35 | To use cloud-based LLM providers, you need to add your API keys. You can do this in two ways: 36 | 37 | 1. **Via the `config` object in settings:** 38 | 39 | Add your keys directly into the `config` section of your `ecode` settings file. 40 | 41 | ```json 42 | { 43 | "config": { 44 | "anthropic_api_key": "YOUR_ANTHROPIC_API_KEY", 45 | "deepseek_api_key": "YOUR_DEEPSEEK_API_KEY", 46 | "google_ai_api_key": "YOUR_GOOGLE_AI_API_KEY", // For Google AI / Gemini models 47 | "mistral_api_key": "YOUR_MISTRAL_API_KEY", 48 | "openai_api_key": "YOUR_OPENAI_API_KEY", 49 | "xai_api_key": "YOUR_XAI_API_KEY" // For xAI / Grok models 50 | } 51 | } 52 | ``` 53 | Leave the string empty (`""`) for services you don't intend to use. 54 | 55 | 2. **Via Environment Variables:** 56 | 57 | The application can also read API keys from environment variables. This is often a more secure method, especially in shared environments or when committing configuration files. If an environment variable is set, it will override the corresponding key in the `config` object. 58 | 59 | The supported environment variables are: 60 | 61 | * `ANTHROPIC_API_KEY` 62 | * `DEEPSEEK_API_KEY` 63 | * `GOOGLE_AI_API_KEY` (or `GEMINI_API_KEY`) 64 | * `MISTRAL_API_KEY` 65 | * `OPENAI_API_KEY` 66 | * `XAI_API_KEY` (or `GROK_API_KEY`) 67 | 68 | ### Keybindings (`keybindings` section) 69 | 70 | The following default keybindings are provided for interacting with the LLM Chat UI. You can customize these in the `keybindings` section of your settings file. 71 | 72 | *(Note: `mod` typically refers to `Ctrl` on Windows/Linux and `Cmd` on macOS)* 73 | 74 | | Action | Default Keybinding | Description | 75 | | :--------------------------- | :----------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 76 | | Add New Chat | `mod+shift+return` | Adds a new, empty chat tab to the UI. | 77 | | Show Chat History | `mod+h` | Displays a chat history panel. | 78 | | Toggle Message Role | `mod+shift+r` | Changes the role [user/assistant] of a selected message | 79 | | Clone Current Chat | `mod+shift+c` | Creates a duplicate of the current chat conversation in a new tab. | 80 | | Send Prompt / Submit Message | `mod+return` | Sends the message currently typed in the input box to the selected LLM. | 81 | | Refresh Local Models | `mod+shift+l` | Re-fetches the list of available models from local providers like Ollama or LMStudio. | 82 | | Rename Current Chat | `f2` | Allows you to rename the currently active chat tab. | 83 | | Save Current Chat | `mod+s` | Saves the current chat conversation state. | 84 | | Open AI Settings | `mod+shift+s` | Opens the settings file | 85 | | Show Chat Menu | `mod+m` | Displays a context menu with common chat actions: New Chat, Save Chat, Rename Chat, Clone Chat, Lock Chat Memory (prevents removal during batch clear operations). | 86 | | Toggle Private Chat | `mod+shift+p` | Toggles if chat must be persisted or no in the chat history (incognito mode) | 87 | | Open New AI Assistant Tab | `mod+shift+m` | Opens a new LLM Chat UI | 88 | | Attach a source file | `mod+shift+a` | Attaches a source file to the current message, allowing the AI to analyze, review, or reference the file's contents in its response. | 89 | 90 | ## LLM Providers (`providers` section) 91 | 92 | ### Overview 93 | 94 | The `providers` object defines the different LLM services available in the chat UI. `ecode` comes pre-configured with several popular providers (Anthropic, DeepSeek, Google, Mistral, OpenAI, XAI) and local providers (Ollama, LMStudio). 95 | 96 | You generally don't need to modify the default providers unless you want to disable one or add/adjust model parameters. However, you can add definitions for new or custom LLM providers and models. 97 | 98 | ### Adding Custom Providers 99 | 100 | To add a new provider, you need to add a new key-value pair to the `providers` object in your settings file. The key should be a unique identifier for your provider (e.g., `"my_custom_ollama"`), and the value should be an object describing the provider and its models. 101 | 102 | **Provider Object Structure:** 103 | 104 | ```json 105 | "your_provider_id": { 106 | "api_url": "string", // Required: The base URL endpoint for the chat completion API. 107 | "display_name": "string", // Optional: User-friendly name shown in the UI. Defaults to the provider ID if missing. 108 | "enabled": boolean, // Optional: Set to `false` to disable this provider. Defaults to `true`. 109 | "version": number, // Optional: Identifier for the API version if the provider requires it (e.g., 1 for Anthropic). 110 | "open_api": boolean, // Optional: Set to `true` if the provider uses an OpenAI-compatible API schema (common for local models like Ollama, LMStudio). 111 | "fetch_models_url": "string", // Optional: URL to dynamically fetch the list of available models (e.g., from Ollama or LMStudio). If provided, the static `models` array below might be ignored or populated dynamically. 112 | "models": [ // Required (unless `fetch_models_url` is used and sufficient): An array of model objects available from this provider. 113 | // Model Object structure described below 114 | ] 115 | } 116 | ``` 117 | 118 | **Model Object Structure (within the `models` array):** 119 | 120 | ```json 121 | { 122 | "name": "string", // Required: The internal model identifier used in API requests (e.g., "claude-3-5-sonnet-latest"). 123 | "display_name": "string", // Optional: User-friendly name shown in the model selection dropdown. Defaults to `name` if missing. 124 | "max_tokens": number, // Optional: The maximum context window size (input tokens + output tokens) supported by the model. 125 | "max_output_tokens": number, // Optional: The maximum number of tokens the model can generate in a single response. 126 | "default_temperature": number, // Optional: Default sampling temperature (controls randomness/creativity). Typically between 0.0 and 2.0. Defaults might vary per provider or model. (e.g., 1.0) 127 | "cheapest": boolean, // Optional: Flag to indicate if this model is considered a cheaper option. It's usually used to generate the summary of the chat when the specific provider is being used. 128 | "cache_configuration": { // Optional: Configuration for potential internal caching or speculative execution features. May not apply to all providers or setups. 129 | "max_cache_anchors": number, // Specific caching parameter. 130 | "min_total_token": number, // Specific caching parameter. 131 | "should_speculate": boolean // Specific caching parameter. 132 | } 133 | // or "cache_configuration": null if not applicable/used for this model. 134 | } 135 | ``` 136 | 137 | **Example: Adding a hypothetical local provider** 138 | 139 | ```json 140 | { 141 | "providers": { 142 | // ... other existing providers ... 143 | "my_local_llm": { 144 | "api_url": "http://localhost:8080/v1/chat/completions", 145 | "display_name": "My Local LLM", 146 | "open_api": true, // Assuming it uses an OpenAI-compatible API 147 | "models": [ 148 | { 149 | "name": "local-model-v1", 150 | "display_name": "Local Model V1", 151 | "max_tokens": 4096 152 | }, 153 | { 154 | "name": "local-model-v2-experimental", 155 | "display_name": "Local Model V2 (Experimental)", 156 | "max_tokens": 8192 157 | } 158 | ] 159 | } 160 | } 161 | } 162 | ``` 163 | -------------------------------------------------------------------------------- /tools/data-migration/lite/language/core/json.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- json.lua 3 | -- 4 | -- Copyright (c) 2020 rxi 5 | -- 6 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | -- this software and associated documentation files (the "Software"), to deal in 8 | -- the Software without restriction, including without limitation the rights to 9 | -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 | -- of the Software, and to permit persons to whom the Software is furnished to do 11 | -- so, subject to the following conditions: 12 | -- 13 | -- The above copyright notice and this permission notice shall be included in all 14 | -- copies or substantial portions of the Software. 15 | -- 16 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | -- SOFTWARE. 23 | -- 24 | 25 | local json = { _version = "0.1.2" } 26 | 27 | ------------------------------------------------------------------------------- 28 | -- Encode 29 | ------------------------------------------------------------------------------- 30 | 31 | local encode 32 | 33 | local escape_char_map = { 34 | [ "\\" ] = "\\", 35 | [ "\"" ] = "\"", 36 | [ "\b" ] = "b", 37 | [ "\f" ] = "f", 38 | [ "\n" ] = "n", 39 | [ "\r" ] = "r", 40 | [ "\t" ] = "t", 41 | } 42 | 43 | local escape_char_map_inv = { [ "/" ] = "/" } 44 | for k, v in pairs(escape_char_map) do 45 | escape_char_map_inv[v] = k 46 | end 47 | 48 | 49 | local function escape_char(c) 50 | return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) 51 | end 52 | 53 | 54 | local function encode_nil(val) 55 | return "null" 56 | end 57 | 58 | 59 | local function encode_table(val, stack) 60 | local res = {} 61 | stack = stack or {} 62 | 63 | -- Circular reference? 64 | if stack[val] then error("circular reference") end 65 | 66 | stack[val] = true 67 | 68 | if rawget(val, 1) ~= nil or next(val) == nil then 69 | -- Treat as array -- check keys are valid and it is not sparse 70 | local n = 0 71 | for k in pairs(val) do 72 | if type(k) ~= "number" then 73 | error("invalid table: mixed or invalid key types") 74 | end 75 | n = n + 1 76 | end 77 | if n ~= #val then 78 | error("invalid table: sparse array") 79 | end 80 | -- Encode 81 | for i, v in ipairs(val) do 82 | table.insert(res, encode(v, stack)) 83 | end 84 | stack[val] = nil 85 | return "[" .. table.concat(res, ",") .. "]" 86 | 87 | else 88 | -- Treat as an object 89 | for k, v in pairs(val) do 90 | if type(k) ~= "string" then 91 | error("invalid table: mixed or invalid key types") 92 | end 93 | table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) 94 | end 95 | stack[val] = nil 96 | return "{" .. table.concat(res, ",") .. "}" 97 | end 98 | end 99 | 100 | 101 | local function encode_string(val) 102 | return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' 103 | end 104 | 105 | 106 | local function encode_number(val) 107 | -- Check for NaN, -inf and inf 108 | if val ~= val or val <= -math.huge or val >= math.huge then 109 | error("unexpected number value '" .. tostring(val) .. "'") 110 | end 111 | return string.format("%.14g", val) 112 | end 113 | 114 | 115 | local type_func_map = { 116 | [ "nil" ] = encode_nil, 117 | [ "table" ] = encode_table, 118 | [ "string" ] = encode_string, 119 | [ "number" ] = encode_number, 120 | [ "boolean" ] = tostring, 121 | } 122 | 123 | 124 | encode = function(val, stack) 125 | local t = type(val) 126 | local f = type_func_map[t] 127 | if f then 128 | return f(val, stack) 129 | end 130 | error("unexpected type '" .. t .. "'") 131 | end 132 | 133 | 134 | function json.encode(val) 135 | return ( encode(val) ) 136 | end 137 | 138 | 139 | ------------------------------------------------------------------------------- 140 | -- Decode 141 | ------------------------------------------------------------------------------- 142 | 143 | local parse 144 | 145 | local function create_set(...) 146 | local res = {} 147 | for i = 1, select("#", ...) do 148 | res[ select(i, ...) ] = true 149 | end 150 | return res 151 | end 152 | 153 | local space_chars = create_set(" ", "\t", "\r", "\n") 154 | local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") 155 | local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") 156 | local literals = create_set("true", "false", "null") 157 | 158 | local literal_map = { 159 | [ "true" ] = true, 160 | [ "false" ] = false, 161 | [ "null" ] = nil, 162 | } 163 | 164 | 165 | local function next_char(str, idx, set, negate) 166 | for i = idx, #str do 167 | if set[str:sub(i, i)] ~= negate then 168 | return i 169 | end 170 | end 171 | return #str + 1 172 | end 173 | 174 | 175 | local function decode_error(str, idx, msg) 176 | local line_count = 1 177 | local col_count = 1 178 | for i = 1, idx - 1 do 179 | col_count = col_count + 1 180 | if str:sub(i, i) == "\n" then 181 | line_count = line_count + 1 182 | col_count = 1 183 | end 184 | end 185 | error( string.format("%s at line %d col %d", msg, line_count, col_count) ) 186 | end 187 | 188 | 189 | local function codepoint_to_utf8(n) 190 | -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa 191 | local f = math.floor 192 | if n <= 0x7f then 193 | return string.char(n) 194 | elseif n <= 0x7ff then 195 | return string.char(f(n / 64) + 192, n % 64 + 128) 196 | elseif n <= 0xffff then 197 | return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) 198 | elseif n <= 0x10ffff then 199 | return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, 200 | f(n % 4096 / 64) + 128, n % 64 + 128) 201 | end 202 | error( string.format("invalid unicode codepoint '%x'", n) ) 203 | end 204 | 205 | 206 | local function parse_unicode_escape(s) 207 | local n1 = tonumber( s:sub(1, 4), 16 ) 208 | local n2 = tonumber( s:sub(7, 10), 16 ) 209 | -- Surrogate pair? 210 | if n2 then 211 | return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) 212 | else 213 | return codepoint_to_utf8(n1) 214 | end 215 | end 216 | 217 | 218 | local function parse_string(str, i) 219 | local res = "" 220 | local j = i + 1 221 | local k = j 222 | 223 | while j <= #str do 224 | local x = str:byte(j) 225 | 226 | if x < 32 then 227 | decode_error(str, j, "control character in string") 228 | 229 | elseif x == 92 then -- `\`: Escape 230 | res = res .. str:sub(k, j - 1) 231 | j = j + 1 232 | local c = str:sub(j, j) 233 | if c == "u" then 234 | local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) 235 | or str:match("^%x%x%x%x", j + 1) 236 | or decode_error(str, j - 1, "invalid unicode escape in string") 237 | res = res .. parse_unicode_escape(hex) 238 | j = j + #hex 239 | else 240 | if not escape_chars[c] then 241 | decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") 242 | end 243 | res = res .. escape_char_map_inv[c] 244 | end 245 | k = j + 1 246 | 247 | elseif x == 34 then -- `"`: End of string 248 | res = res .. str:sub(k, j - 1) 249 | return res, j + 1 250 | end 251 | 252 | j = j + 1 253 | end 254 | 255 | decode_error(str, i, "expected closing quote for string") 256 | end 257 | 258 | 259 | local function parse_number(str, i) 260 | local x = next_char(str, i, delim_chars) 261 | local s = str:sub(i, x - 1) 262 | local n = tonumber(s) 263 | if not n then 264 | decode_error(str, i, "invalid number '" .. s .. "'") 265 | end 266 | return n, x 267 | end 268 | 269 | 270 | local function parse_literal(str, i) 271 | local x = next_char(str, i, delim_chars) 272 | local word = str:sub(i, x - 1) 273 | if not literals[word] then 274 | decode_error(str, i, "invalid literal '" .. word .. "'") 275 | end 276 | return literal_map[word], x 277 | end 278 | 279 | 280 | local function parse_array(str, i) 281 | local res = {} 282 | local n = 1 283 | i = i + 1 284 | while 1 do 285 | local x 286 | i = next_char(str, i, space_chars, true) 287 | -- Empty / end of array? 288 | if str:sub(i, i) == "]" then 289 | i = i + 1 290 | break 291 | end 292 | -- Read token 293 | x, i = parse(str, i) 294 | res[n] = x 295 | n = n + 1 296 | -- Next token 297 | i = next_char(str, i, space_chars, true) 298 | local chr = str:sub(i, i) 299 | i = i + 1 300 | if chr == "]" then break end 301 | if chr ~= "," then decode_error(str, i, "expected ']' or ','") end 302 | end 303 | return res, i 304 | end 305 | 306 | 307 | local function parse_object(str, i) 308 | local res = {} 309 | i = i + 1 310 | while 1 do 311 | local key, val 312 | i = next_char(str, i, space_chars, true) 313 | -- Empty / end of object? 314 | if str:sub(i, i) == "}" then 315 | i = i + 1 316 | break 317 | end 318 | -- Read key 319 | if str:sub(i, i) ~= '"' then 320 | decode_error(str, i, "expected string for key") 321 | end 322 | key, i = parse(str, i) 323 | -- Read ':' delimiter 324 | i = next_char(str, i, space_chars, true) 325 | if str:sub(i, i) ~= ":" then 326 | decode_error(str, i, "expected ':' after key") 327 | end 328 | i = next_char(str, i + 1, space_chars, true) 329 | -- Read value 330 | val, i = parse(str, i) 331 | -- Set 332 | res[key] = val 333 | -- Next token 334 | i = next_char(str, i, space_chars, true) 335 | local chr = str:sub(i, i) 336 | i = i + 1 337 | if chr == "}" then break end 338 | if chr ~= "," then decode_error(str, i, "expected '}' or ','") end 339 | end 340 | return res, i 341 | end 342 | 343 | 344 | local char_func_map = { 345 | [ '"' ] = parse_string, 346 | [ "0" ] = parse_number, 347 | [ "1" ] = parse_number, 348 | [ "2" ] = parse_number, 349 | [ "3" ] = parse_number, 350 | [ "4" ] = parse_number, 351 | [ "5" ] = parse_number, 352 | [ "6" ] = parse_number, 353 | [ "7" ] = parse_number, 354 | [ "8" ] = parse_number, 355 | [ "9" ] = parse_number, 356 | [ "-" ] = parse_number, 357 | [ "t" ] = parse_literal, 358 | [ "f" ] = parse_literal, 359 | [ "n" ] = parse_literal, 360 | [ "[" ] = parse_array, 361 | [ "{" ] = parse_object, 362 | } 363 | 364 | 365 | parse = function(str, idx) 366 | local chr = str:sub(idx, idx) 367 | local f = char_func_map[chr] 368 | if f then 369 | return f(str, idx) 370 | end 371 | decode_error(str, idx, "unexpected character '" .. chr .. "'") 372 | end 373 | 374 | 375 | function json.decode(str) 376 | if type(str) ~= "string" then 377 | error("expected argument of type string, got " .. type(str)) 378 | end 379 | local res, idx = parse(str, next_char(str, 1, space_chars, true)) 380 | idx = next_char(str, idx, space_chars, true) 381 | if idx <= #str then 382 | decode_error(str, idx, "trailing garbage") 383 | end 384 | return res 385 | end 386 | 387 | 388 | return json 389 | -------------------------------------------------------------------------------- /tools/data-migration/lite/colors/core/common.lua: -------------------------------------------------------------------------------- 1 | local common = {} 2 | 3 | 4 | function common.is_utf8_cont(s, offset) 5 | local byte = s:byte(offset or 1) 6 | return byte >= 0x80 and byte < 0xc0 7 | end 8 | 9 | 10 | function common.utf8_chars(text) 11 | return text:gmatch("[\0-\x7f\xc2-\xf4][\x80-\xbf]*") 12 | end 13 | 14 | 15 | function common.clamp(n, lo, hi) 16 | return math.max(math.min(n, hi), lo) 17 | end 18 | 19 | 20 | function common.merge(a, b) 21 | a = type(a) == "table" and a or {} 22 | local t = {} 23 | for k, v in pairs(a) do 24 | t[k] = v 25 | end 26 | if b and type(b) == "table" then 27 | for k, v in pairs(b) do 28 | t[k] = v 29 | end 30 | end 31 | return t 32 | end 33 | 34 | 35 | function common.round(n) 36 | return n >= 0 and math.floor(n + 0.5) or math.ceil(n - 0.5) 37 | end 38 | 39 | 40 | function common.find_index(tbl, prop) 41 | for i, o in ipairs(tbl) do 42 | if o[prop] then return i end 43 | end 44 | end 45 | 46 | 47 | function common.lerp(a, b, t) 48 | if type(a) ~= "table" then 49 | return a + (b - a) * t 50 | end 51 | local res = {} 52 | for k, v in pairs(b) do 53 | res[k] = common.lerp(a[k], v, t) 54 | end 55 | return res 56 | end 57 | 58 | 59 | function common.distance(x1, y1, x2, y2) 60 | return math.sqrt(((x2-x1) ^ 2)+((y2-y1) ^ 2)) 61 | end 62 | 63 | 64 | function common.color(str) 65 | local r, g, b, a = str:match("^#(%x%x)(%x%x)(%x%x)(%x?%x?)$") 66 | if r then 67 | r = tonumber(r, 16) 68 | g = tonumber(g, 16) 69 | b = tonumber(b, 16) 70 | a = tonumber(a, 16) or 0xff 71 | elseif str:match("rgba?%s*%([%d%s%.,]+%)") then 72 | local f = str:gmatch("[%d.]+") 73 | r = (f() or 0) 74 | g = (f() or 0) 75 | b = (f() or 0) 76 | a = (f() or 1) * 0xff 77 | else 78 | error(string.format("bad color string '%s'", str)) 79 | end 80 | return r, g, b, a 81 | end 82 | 83 | 84 | function common.splice(t, at, remove, insert) 85 | assert(remove >= 0, "bad argument #3 to 'splice' (non-negative value expected)") 86 | insert = insert or {} 87 | local len = #insert 88 | if remove ~= len then table.move(t, at + remove, #t + remove, at + len) end 89 | table.move(insert, 1, len, at, t) 90 | end 91 | 92 | 93 | local function compare_score(a, b) 94 | return a.score > b.score 95 | end 96 | 97 | local function fuzzy_match_items(items, needle, files) 98 | local res = {} 99 | for _, item in ipairs(items) do 100 | local score = system.fuzzy_match(tostring(item), needle, files) 101 | if score then 102 | table.insert(res, { text = item, score = score }) 103 | end 104 | end 105 | table.sort(res, compare_score) 106 | for i, item in ipairs(res) do 107 | res[i] = item.text 108 | end 109 | return res 110 | end 111 | 112 | 113 | function common.fuzzy_match(haystack, needle, files) 114 | if type(haystack) == "table" then 115 | return fuzzy_match_items(haystack, needle, files) 116 | end 117 | return system.fuzzy_match(haystack, needle, files) 118 | end 119 | 120 | 121 | function common.fuzzy_match_with_recents(haystack, recents, needle) 122 | if needle == "" then 123 | local recents_ext = {} 124 | for i = 2, #recents do 125 | table.insert(recents_ext, recents[i]) 126 | end 127 | table.insert(recents_ext, recents[1]) 128 | local others = common.fuzzy_match(haystack, "", true) 129 | for i = 1, #others do 130 | table.insert(recents_ext, others[i]) 131 | end 132 | return recents_ext 133 | else 134 | return fuzzy_match_items(haystack, needle, true) 135 | end 136 | end 137 | 138 | 139 | function common.path_suggest(text, root) 140 | if root and root:sub(-1) ~= PATHSEP then 141 | root = root .. PATHSEP 142 | end 143 | local path, name = text:match("^(.-)([^/\\]*)$") 144 | local clean_dotslash = false 145 | -- ignore root if path is absolute 146 | local is_absolute = common.is_absolute_path(text) 147 | if not is_absolute then 148 | if path == "" then 149 | path = root or "." 150 | clean_dotslash = not root 151 | else 152 | path = (root or "") .. path 153 | end 154 | end 155 | 156 | -- Only in Windows allow using both styles of PATHSEP 157 | if (PATHSEP == "\\" and not string.match(path:sub(-1), "[\\/]")) or 158 | (PATHSEP ~= "\\" and path:sub(-1) ~= PATHSEP) then 159 | path = path .. PATHSEP 160 | end 161 | local files = system.list_dir(path) or {} 162 | local res = {} 163 | for _, file in ipairs(files) do 164 | file = path .. file 165 | local info = system.get_file_info(file) 166 | if info then 167 | if info.type == "dir" then 168 | file = file .. PATHSEP 169 | end 170 | if root then 171 | -- remove root part from file path 172 | local s, e = file:find(root, nil, true) 173 | if s == 1 then 174 | file = file:sub(e + 1) 175 | end 176 | elseif clean_dotslash then 177 | -- remove added dot slash 178 | local s, e = file:find("." .. PATHSEP, nil, true) 179 | if s == 1 then 180 | file = file:sub(e + 1) 181 | end 182 | end 183 | if file:lower():find(text:lower(), nil, true) == 1 then 184 | table.insert(res, file) 185 | end 186 | end 187 | end 188 | return res 189 | end 190 | 191 | 192 | function common.dir_path_suggest(text) 193 | local path, name = text:match("^(.-)([^/\\]*)$") 194 | local files = system.list_dir(path == "" and "." or path) or {} 195 | local res = {} 196 | for _, file in ipairs(files) do 197 | file = path .. file 198 | local info = system.get_file_info(file) 199 | if info and info.type == "dir" and file:lower():find(text:lower(), nil, true) == 1 then 200 | table.insert(res, file) 201 | end 202 | end 203 | return res 204 | end 205 | 206 | 207 | function common.dir_list_suggest(text, dir_list) 208 | local path, name = text:match("^(.-)([^/\\]*)$") 209 | local res = {} 210 | for _, dir_path in ipairs(dir_list) do 211 | if dir_path:lower():find(text:lower(), nil, true) == 1 then 212 | table.insert(res, dir_path) 213 | end 214 | end 215 | return res 216 | end 217 | 218 | 219 | function common.match_pattern(text, pattern, ...) 220 | if type(pattern) == "string" then 221 | return text:find(pattern, ...) 222 | end 223 | for _, p in ipairs(pattern) do 224 | local s, e = common.match_pattern(text, p, ...) 225 | if s then return s, e end 226 | end 227 | return false 228 | end 229 | 230 | 231 | function common.draw_text(font, color, text, align, x,y,w,h) 232 | local tw, th = font:get_width(text), font:get_height(text) 233 | if align == "center" then 234 | x = x + (w - tw) / 2 235 | elseif align == "right" then 236 | x = x + (w - tw) 237 | end 238 | y = common.round(y + (h - th) / 2) 239 | return renderer.draw_text(font, text, x, y, color), y + th 240 | end 241 | 242 | 243 | function common.bench(name, fn, ...) 244 | local start = system.get_time() 245 | local res = fn(...) 246 | local t = system.get_time() - start 247 | local ms = t * 1000 248 | local per = (t / (1 / 60)) * 100 249 | print(string.format("*** %-16s : %8.3fms %6.2f%%", name, ms, per)) 250 | return res 251 | end 252 | 253 | 254 | local function serialize(val, pretty, indent_str, escape, sort, limit, level) 255 | local space = pretty and " " or "" 256 | local indent = pretty and string.rep(indent_str, level) or "" 257 | local newline = pretty and "\n" or "" 258 | if type(val) == "string" then 259 | local out = string.format("%q", val) 260 | if escape then 261 | out = string.gsub(out, "\\\n", "\\n") 262 | out = string.gsub(out, "\\7", "\\a") 263 | out = string.gsub(out, "\\8", "\\b") 264 | out = string.gsub(out, "\\9", "\\t") 265 | out = string.gsub(out, "\\11", "\\v") 266 | out = string.gsub(out, "\\12", "\\f") 267 | out = string.gsub(out, "\\13", "\\r") 268 | end 269 | return out 270 | elseif type(val) == "table" then 271 | -- early exit 272 | if level >= limit then return tostring(val) end 273 | local next_indent = pretty and (indent .. indent_str) or "" 274 | local t = {} 275 | for k, v in pairs(val) do 276 | table.insert(t, 277 | next_indent .. "[" .. 278 | serialize(k, pretty, indent_str, escape, sort, limit, level + 1) .. 279 | "]" .. space .. "=" .. space .. serialize(v, pretty, indent_str, escape, sort, limit, level + 1)) 280 | end 281 | if #t == 0 then return "{}" end 282 | if sort then table.sort(t) end 283 | return "{" .. newline .. table.concat(t, "," .. newline) .. newline .. indent .. "}" 284 | end 285 | return tostring(val) 286 | end 287 | 288 | -- Serialize `val` into a parsable string. 289 | -- Available options 290 | -- * pretty: enable pretty printing 291 | -- * indent_str: indent to use (" " by default) 292 | -- * escape: use normal escape characters instead of the ones used by string.format("%q", ...) 293 | -- * sort: sort the keys inside tables 294 | -- * limit: limit how deep to serialize 295 | -- * initial_indent: the initial indentation level 296 | function common.serialize(val, opts) 297 | opts = opts or {} 298 | local indent_str = opts.indent_str or " " 299 | local initial_indent = opts.initial_indent or 0 300 | local indent = opts.pretty and string.rep(indent_str, initial_indent) or "" 301 | local limit = (opts.limit or math.huge) + initial_indent 302 | return indent .. serialize(val, opts.pretty, indent_str, 303 | opts.escape, opts.sort, limit, initial_indent) 304 | end 305 | 306 | 307 | function common.basename(path) 308 | -- a path should never end by / or \ except if it is '/' (unix root) or 309 | -- 'X:\' (windows drive) 310 | return path:match("[^\\/]+$") or path 311 | end 312 | 313 | 314 | -- can return nil if there is no directory part in the path 315 | function common.dirname(path) 316 | return path:match("(.+)[\\/][^\\/]+$") 317 | end 318 | 319 | 320 | function common.home_encode(text) 321 | if HOME and string.find(text, HOME, 1, true) == 1 then 322 | local dir_pos = #HOME + 1 323 | -- ensure we don't replace if the text is just "$HOME" or "$HOME/" so 324 | -- it must have a "/" following the $HOME and some characters following. 325 | if string.find(text, PATHSEP, dir_pos, true) == dir_pos and #text > dir_pos then 326 | return "~" .. text:sub(dir_pos) 327 | end 328 | end 329 | return text 330 | end 331 | 332 | 333 | function common.home_encode_list(paths) 334 | local t = {} 335 | for i = 1, #paths do 336 | t[i] = common.home_encode(paths[i]) 337 | end 338 | return t 339 | end 340 | 341 | 342 | function common.home_expand(text) 343 | return HOME and text:gsub("^~", HOME) or text 344 | end 345 | 346 | 347 | local function split_on_slash(s, sep_pattern) 348 | local t = {} 349 | if s:match("^[/\\]") then 350 | t[#t + 1] = "" 351 | end 352 | for fragment in string.gmatch(s, "([^/\\]+)") do 353 | t[#t + 1] = fragment 354 | end 355 | return t 356 | end 357 | 358 | 359 | -- The filename argument given to the function is supposed to 360 | -- come from system.absolute_path and as such should be an 361 | -- absolute path without . or .. elements. 362 | -- This function exists because on Windows the drive letter returned 363 | -- by system.absolute_path is sometimes with a lower case and sometimes 364 | -- with an upper case so we normalize to upper case. 365 | function common.normalize_volume(filename) 366 | if not filename then return end 367 | if PATHSEP == '\\' then 368 | local drive, rem = filename:match('^([a-zA-Z]:\\)(.-)'..PATHSEP..'?$') 369 | if drive then 370 | return drive:upper() .. rem 371 | end 372 | end 373 | return filename 374 | end 375 | 376 | 377 | function common.normalize_path(filename) 378 | if not filename then return end 379 | local volume 380 | if PATHSEP == '\\' then 381 | filename = filename:gsub('[/\\]', '\\') 382 | local drive, rem = filename:match('^([a-zA-Z]:\\)(.*)') 383 | if drive then 384 | volume, filename = drive:upper(), rem 385 | else 386 | drive, rem = filename:match('^(\\\\[^\\]+\\[^\\]+\\)(.*)') 387 | if drive then 388 | volume, filename = drive, rem 389 | end 390 | end 391 | else 392 | local relpath = filename:match('^/(.+)') 393 | if relpath then 394 | volume, filename = "/", relpath 395 | end 396 | end 397 | local parts = split_on_slash(filename, PATHSEP) 398 | local accu = {} 399 | for _, part in ipairs(parts) do 400 | if part == '..' then 401 | if #accu > 0 and accu[#accu] ~= ".." then 402 | table.remove(accu) 403 | elseif volume then 404 | error("invalid path " .. volume .. filename) 405 | else 406 | table.insert(accu, part) 407 | end 408 | elseif part ~= '.' then 409 | table.insert(accu, part) 410 | end 411 | end 412 | local npath = table.concat(accu, PATHSEP) 413 | return (volume or "") .. (npath == "" and PATHSEP or npath) 414 | end 415 | 416 | 417 | function common.is_absolute_path(path) 418 | return path:sub(1, 1) == PATHSEP or path:match("^(%a):\\") 419 | end 420 | 421 | 422 | function common.path_belongs_to(filename, path) 423 | return string.find(filename, path .. PATHSEP, 1, true) == 1 424 | end 425 | 426 | 427 | function common.relative_path(ref_dir, dir) 428 | local drive_pattern = "^(%a):\\" 429 | local drive, ref_drive = dir:match(drive_pattern), ref_dir:match(drive_pattern) 430 | if drive and ref_drive and drive ~= ref_drive then 431 | -- Windows, different drives, system.absolute_path fails for C:\..\D:\ 432 | return dir 433 | end 434 | local ref_ls = split_on_slash(ref_dir) 435 | local dir_ls = split_on_slash(dir) 436 | local i = 1 437 | while i <= #ref_ls do 438 | if dir_ls[i] ~= ref_ls[i] then 439 | break 440 | end 441 | i = i + 1 442 | end 443 | local ups = "" 444 | for k = i, #ref_ls do 445 | ups = ups .. ".." .. PATHSEP 446 | end 447 | local rel_path = ups .. table.concat(dir_ls, PATHSEP, i) 448 | return rel_path ~= "" and rel_path or "." 449 | end 450 | 451 | 452 | function common.mkdirp(path) 453 | local stat = system.get_file_info(path) 454 | if stat and stat.type then 455 | return false, "path exists", path 456 | end 457 | local subdirs = {} 458 | while path and path ~= "" do 459 | local success_mkdir = system.mkdir(path) 460 | if success_mkdir then break end 461 | local updir, basedir = path:match("(.*)[/\\](.+)$") 462 | table.insert(subdirs, 1, basedir or path) 463 | path = updir 464 | end 465 | for _, dirname in ipairs(subdirs) do 466 | path = path and path .. PATHSEP .. dirname or dirname 467 | if not system.mkdir(path) then 468 | return false, "cannot create directory", path 469 | end 470 | end 471 | return true 472 | end 473 | 474 | function common.rm(path, recursively) 475 | local stat = system.get_file_info(path) 476 | if not stat or (stat.type ~= "file" and stat.type ~= "dir") then 477 | return false, "invalid path given", path 478 | end 479 | 480 | if stat.type == "file" then 481 | local removed, error = os.remove(path) 482 | if not removed then 483 | return false, error, path 484 | end 485 | else 486 | local contents = system.list_dir(path) 487 | if #contents > 0 and not recursively then 488 | return false, "directory is not empty", path 489 | end 490 | 491 | for _, item in pairs(contents) do 492 | local item_path = path .. PATHSEP .. item 493 | local item_stat = system.get_file_info(item_path) 494 | 495 | if not item_stat then 496 | return false, "invalid file encountered", item_path 497 | end 498 | 499 | if item_stat.type == "dir" then 500 | local deleted, error, ipath = common.rm(item_path, recursively) 501 | if not deleted then 502 | return false, error, ipath 503 | end 504 | elseif item_stat.type == "file" then 505 | local removed, error = os.remove(item_path) 506 | if not removed then 507 | return false, error, item_path 508 | end 509 | end 510 | end 511 | 512 | local removed, error = system.rmdir(path) 513 | if not removed then 514 | return false, error, path 515 | end 516 | end 517 | 518 | return true 519 | end 520 | 521 | 522 | return common 523 | -------------------------------------------------------------------------------- /tools/data-migration/lite/language/core/common.lua: -------------------------------------------------------------------------------- 1 | local common = {} 2 | 3 | 4 | function common.is_utf8_cont(s, offset) 5 | local byte = s:byte(offset or 1) 6 | return byte >= 0x80 and byte < 0xc0 7 | end 8 | 9 | 10 | function common.utf8_chars(text) 11 | return text:gmatch("[\0-\x7f\xc2-\xf4][\x80-\xbf]*") 12 | end 13 | 14 | 15 | function common.clamp(n, lo, hi) 16 | return math.max(math.min(n, hi), lo) 17 | end 18 | 19 | 20 | function common.merge(a, b) 21 | a = type(a) == "table" and a or {} 22 | local t = {} 23 | for k, v in pairs(a) do 24 | t[k] = v 25 | end 26 | if b and type(b) == "table" then 27 | for k, v in pairs(b) do 28 | t[k] = v 29 | end 30 | end 31 | return t 32 | end 33 | 34 | 35 | function common.round(n) 36 | return n >= 0 and math.floor(n + 0.5) or math.ceil(n - 0.5) 37 | end 38 | 39 | 40 | function common.find_index(tbl, prop) 41 | for i, o in ipairs(tbl) do 42 | if o[prop] then return i end 43 | end 44 | end 45 | 46 | 47 | function common.lerp(a, b, t) 48 | if type(a) ~= "table" then 49 | return a + (b - a) * t 50 | end 51 | local res = {} 52 | for k, v in pairs(b) do 53 | res[k] = common.lerp(a[k], v, t) 54 | end 55 | return res 56 | end 57 | 58 | 59 | function common.distance(x1, y1, x2, y2) 60 | return math.sqrt(((x2-x1) ^ 2)+((y2-y1) ^ 2)) 61 | end 62 | 63 | 64 | function common.color(str) 65 | local r, g, b, a = str:match("^#(%x%x)(%x%x)(%x%x)(%x?%x?)$") 66 | if r then 67 | r = tonumber(r, 16) 68 | g = tonumber(g, 16) 69 | b = tonumber(b, 16) 70 | a = tonumber(a, 16) or 0xff 71 | elseif str:match("rgba?%s*%([%d%s%.,]+%)") then 72 | local f = str:gmatch("[%d.]+") 73 | r = (f() or 0) 74 | g = (f() or 0) 75 | b = (f() or 0) 76 | a = (f() or 1) * 0xff 77 | else 78 | error(string.format("bad color string '%s'", str)) 79 | end 80 | return r, g, b, a 81 | end 82 | 83 | 84 | function common.splice(t, at, remove, insert) 85 | assert(remove >= 0, "bad argument #3 to 'splice' (non-negative value expected)") 86 | insert = insert or {} 87 | local len = #insert 88 | if remove ~= len then table.move(t, at + remove, #t + remove, at + len) end 89 | table.move(insert, 1, len, at, t) 90 | end 91 | 92 | 93 | local function compare_score(a, b) 94 | return a.score > b.score 95 | end 96 | 97 | local function fuzzy_match_items(items, needle, files) 98 | local res = {} 99 | for _, item in ipairs(items) do 100 | local score = system.fuzzy_match(tostring(item), needle, files) 101 | if score then 102 | table.insert(res, { text = item, score = score }) 103 | end 104 | end 105 | table.sort(res, compare_score) 106 | for i, item in ipairs(res) do 107 | res[i] = item.text 108 | end 109 | return res 110 | end 111 | 112 | 113 | function common.fuzzy_match(haystack, needle, files) 114 | if type(haystack) == "table" then 115 | return fuzzy_match_items(haystack, needle, files) 116 | end 117 | return system.fuzzy_match(haystack, needle, files) 118 | end 119 | 120 | 121 | function common.fuzzy_match_with_recents(haystack, recents, needle) 122 | if needle == "" then 123 | local recents_ext = {} 124 | for i = 2, #recents do 125 | table.insert(recents_ext, recents[i]) 126 | end 127 | table.insert(recents_ext, recents[1]) 128 | local others = common.fuzzy_match(haystack, "", true) 129 | for i = 1, #others do 130 | table.insert(recents_ext, others[i]) 131 | end 132 | return recents_ext 133 | else 134 | return fuzzy_match_items(haystack, needle, true) 135 | end 136 | end 137 | 138 | 139 | function common.path_suggest(text, root) 140 | if root and root:sub(-1) ~= PATHSEP then 141 | root = root .. PATHSEP 142 | end 143 | local path, name = text:match("^(.-)([^/\\]*)$") 144 | local clean_dotslash = false 145 | -- ignore root if path is absolute 146 | local is_absolute = common.is_absolute_path(text) 147 | if not is_absolute then 148 | if path == "" then 149 | path = root or "." 150 | clean_dotslash = not root 151 | else 152 | path = (root or "") .. path 153 | end 154 | end 155 | 156 | -- Only in Windows allow using both styles of PATHSEP 157 | if (PATHSEP == "\\" and not string.match(path:sub(-1), "[\\/]")) or 158 | (PATHSEP ~= "\\" and path:sub(-1) ~= PATHSEP) then 159 | path = path .. PATHSEP 160 | end 161 | local files = system.list_dir(path) or {} 162 | local res = {} 163 | for _, file in ipairs(files) do 164 | file = path .. file 165 | local info = system.get_file_info(file) 166 | if info then 167 | if info.type == "dir" then 168 | file = file .. PATHSEP 169 | end 170 | if root then 171 | -- remove root part from file path 172 | local s, e = file:find(root, nil, true) 173 | if s == 1 then 174 | file = file:sub(e + 1) 175 | end 176 | elseif clean_dotslash then 177 | -- remove added dot slash 178 | local s, e = file:find("." .. PATHSEP, nil, true) 179 | if s == 1 then 180 | file = file:sub(e + 1) 181 | end 182 | end 183 | if file:lower():find(text:lower(), nil, true) == 1 then 184 | table.insert(res, file) 185 | end 186 | end 187 | end 188 | return res 189 | end 190 | 191 | 192 | function common.dir_path_suggest(text) 193 | local path, name = text:match("^(.-)([^/\\]*)$") 194 | local files = system.list_dir(path == "" and "." or path) or {} 195 | local res = {} 196 | for _, file in ipairs(files) do 197 | file = path .. file 198 | local info = system.get_file_info(file) 199 | if info and info.type == "dir" and file:lower():find(text:lower(), nil, true) == 1 then 200 | table.insert(res, file) 201 | end 202 | end 203 | return res 204 | end 205 | 206 | 207 | function common.dir_list_suggest(text, dir_list) 208 | local path, name = text:match("^(.-)([^/\\]*)$") 209 | local res = {} 210 | for _, dir_path in ipairs(dir_list) do 211 | if dir_path:lower():find(text:lower(), nil, true) == 1 then 212 | table.insert(res, dir_path) 213 | end 214 | end 215 | return res 216 | end 217 | 218 | 219 | function common.match_pattern(text, pattern, ...) 220 | if type(pattern) == "string" then 221 | return text:find(pattern, ...) 222 | end 223 | for _, p in ipairs(pattern) do 224 | local s, e = common.match_pattern(text, p, ...) 225 | if s then return s, e end 226 | end 227 | return false 228 | end 229 | 230 | 231 | function common.draw_text(font, color, text, align, x,y,w,h) 232 | local tw, th = font:get_width(text), font:get_height(text) 233 | if align == "center" then 234 | x = x + (w - tw) / 2 235 | elseif align == "right" then 236 | x = x + (w - tw) 237 | end 238 | y = common.round(y + (h - th) / 2) 239 | return renderer.draw_text(font, text, x, y, color), y + th 240 | end 241 | 242 | 243 | function common.bench(name, fn, ...) 244 | local start = system.get_time() 245 | local res = fn(...) 246 | local t = system.get_time() - start 247 | local ms = t * 1000 248 | local per = (t / (1 / 60)) * 100 249 | print(string.format("*** %-16s : %8.3fms %6.2f%%", name, ms, per)) 250 | return res 251 | end 252 | 253 | 254 | local function serialize(val, pretty, indent_str, escape, sort, limit, level) 255 | local space = pretty and " " or "" 256 | local indent = pretty and string.rep(indent_str, level) or "" 257 | local newline = pretty and "\n" or "" 258 | if type(val) == "string" then 259 | local out = string.format("%q", val) 260 | if escape then 261 | out = string.gsub(out, "\\\n", "\\n") 262 | out = string.gsub(out, "\\7", "\\a") 263 | out = string.gsub(out, "\\8", "\\b") 264 | out = string.gsub(out, "\\9", "\\t") 265 | out = string.gsub(out, "\\11", "\\v") 266 | out = string.gsub(out, "\\12", "\\f") 267 | out = string.gsub(out, "\\13", "\\r") 268 | end 269 | return out 270 | elseif type(val) == "table" then 271 | -- early exit 272 | if level >= limit then return tostring(val) end 273 | local next_indent = pretty and (indent .. indent_str) or "" 274 | local t = {} 275 | for k, v in pairs(val) do 276 | table.insert(t, 277 | next_indent .. "[" .. 278 | serialize(k, pretty, indent_str, escape, sort, limit, level + 1) .. 279 | "]" .. space .. "=" .. space .. serialize(v, pretty, indent_str, escape, sort, limit, level + 1)) 280 | end 281 | if #t == 0 then return "{}" end 282 | if sort then table.sort(t) end 283 | return "{" .. newline .. table.concat(t, "," .. newline) .. newline .. indent .. "}" 284 | end 285 | return tostring(val) 286 | end 287 | 288 | -- Serialize `val` into a parsable string. 289 | -- Available options 290 | -- * pretty: enable pretty printing 291 | -- * indent_str: indent to use (" " by default) 292 | -- * escape: use normal escape characters instead of the ones used by string.format("%q", ...) 293 | -- * sort: sort the keys inside tables 294 | -- * limit: limit how deep to serialize 295 | -- * initial_indent: the initial indentation level 296 | function common.serialize(val, opts) 297 | opts = opts or {} 298 | local indent_str = opts.indent_str or " " 299 | local initial_indent = opts.initial_indent or 0 300 | local indent = opts.pretty and string.rep(indent_str, initial_indent) or "" 301 | local limit = (opts.limit or math.huge) + initial_indent 302 | return indent .. serialize(val, opts.pretty, indent_str, 303 | opts.escape, opts.sort, limit, initial_indent) 304 | end 305 | 306 | 307 | function common.basename(path) 308 | -- a path should never end by / or \ except if it is '/' (unix root) or 309 | -- 'X:\' (windows drive) 310 | return path:match("[^\\/]+$") or path 311 | end 312 | 313 | 314 | -- can return nil if there is no directory part in the path 315 | function common.dirname(path) 316 | return path:match("(.+)[\\/][^\\/]+$") 317 | end 318 | 319 | 320 | function common.home_encode(text) 321 | if HOME and string.find(text, HOME, 1, true) == 1 then 322 | local dir_pos = #HOME + 1 323 | -- ensure we don't replace if the text is just "$HOME" or "$HOME/" so 324 | -- it must have a "/" following the $HOME and some characters following. 325 | if string.find(text, PATHSEP, dir_pos, true) == dir_pos and #text > dir_pos then 326 | return "~" .. text:sub(dir_pos) 327 | end 328 | end 329 | return text 330 | end 331 | 332 | 333 | function common.home_encode_list(paths) 334 | local t = {} 335 | for i = 1, #paths do 336 | t[i] = common.home_encode(paths[i]) 337 | end 338 | return t 339 | end 340 | 341 | 342 | function common.home_expand(text) 343 | return HOME and text:gsub("^~", HOME) or text 344 | end 345 | 346 | 347 | local function split_on_slash(s, sep_pattern) 348 | local t = {} 349 | if s:match("^[/\\]") then 350 | t[#t + 1] = "" 351 | end 352 | for fragment in string.gmatch(s, "([^/\\]+)") do 353 | t[#t + 1] = fragment 354 | end 355 | return t 356 | end 357 | 358 | 359 | -- The filename argument given to the function is supposed to 360 | -- come from system.absolute_path and as such should be an 361 | -- absolute path without . or .. elements. 362 | -- This function exists because on Windows the drive letter returned 363 | -- by system.absolute_path is sometimes with a lower case and sometimes 364 | -- with an upper case so we normalize to upper case. 365 | function common.normalize_volume(filename) 366 | if not filename then return end 367 | if PATHSEP == '\\' then 368 | local drive, rem = filename:match('^([a-zA-Z]:\\)(.-)'..PATHSEP..'?$') 369 | if drive then 370 | return drive:upper() .. rem 371 | end 372 | end 373 | return filename 374 | end 375 | 376 | 377 | function common.normalize_path(filename) 378 | if not filename then return end 379 | local volume 380 | if PATHSEP == '\\' then 381 | filename = filename:gsub('[/\\]', '\\') 382 | local drive, rem = filename:match('^([a-zA-Z]:\\)(.*)') 383 | if drive then 384 | volume, filename = drive:upper(), rem 385 | else 386 | drive, rem = filename:match('^(\\\\[^\\]+\\[^\\]+\\)(.*)') 387 | if drive then 388 | volume, filename = drive, rem 389 | end 390 | end 391 | else 392 | local relpath = filename:match('^/(.+)') 393 | if relpath then 394 | volume, filename = "/", relpath 395 | end 396 | end 397 | local parts = split_on_slash(filename, PATHSEP) 398 | local accu = {} 399 | for _, part in ipairs(parts) do 400 | if part == '..' then 401 | if #accu > 0 and accu[#accu] ~= ".." then 402 | table.remove(accu) 403 | elseif volume then 404 | error("invalid path " .. volume .. filename) 405 | else 406 | table.insert(accu, part) 407 | end 408 | elseif part ~= '.' then 409 | table.insert(accu, part) 410 | end 411 | end 412 | local npath = table.concat(accu, PATHSEP) 413 | return (volume or "") .. (npath == "" and PATHSEP or npath) 414 | end 415 | 416 | 417 | function common.is_absolute_path(path) 418 | return path:sub(1, 1) == PATHSEP or path:match("^(%a):\\") 419 | end 420 | 421 | 422 | function common.path_belongs_to(filename, path) 423 | return string.find(filename, path .. PATHSEP, 1, true) == 1 424 | end 425 | 426 | 427 | function common.relative_path(ref_dir, dir) 428 | local drive_pattern = "^(%a):\\" 429 | local drive, ref_drive = dir:match(drive_pattern), ref_dir:match(drive_pattern) 430 | if drive and ref_drive and drive ~= ref_drive then 431 | -- Windows, different drives, system.absolute_path fails for C:\..\D:\ 432 | return dir 433 | end 434 | local ref_ls = split_on_slash(ref_dir) 435 | local dir_ls = split_on_slash(dir) 436 | local i = 1 437 | while i <= #ref_ls do 438 | if dir_ls[i] ~= ref_ls[i] then 439 | break 440 | end 441 | i = i + 1 442 | end 443 | local ups = "" 444 | for k = i, #ref_ls do 445 | ups = ups .. ".." .. PATHSEP 446 | end 447 | local rel_path = ups .. table.concat(dir_ls, PATHSEP, i) 448 | return rel_path ~= "" and rel_path or "." 449 | end 450 | 451 | 452 | function common.mkdirp(path) 453 | local stat = system.get_file_info(path) 454 | if stat and stat.type then 455 | return false, "path exists", path 456 | end 457 | local subdirs = {} 458 | while path and path ~= "" do 459 | local success_mkdir = system.mkdir(path) 460 | if success_mkdir then break end 461 | local updir, basedir = path:match("(.*)[/\\](.+)$") 462 | table.insert(subdirs, 1, basedir or path) 463 | path = updir 464 | end 465 | for _, dirname in ipairs(subdirs) do 466 | path = path and path .. PATHSEP .. dirname or dirname 467 | if not system.mkdir(path) then 468 | return false, "cannot create directory", path 469 | end 470 | end 471 | return true 472 | end 473 | 474 | function common.rm(path, recursively) 475 | local stat = system.get_file_info(path) 476 | if not stat or (stat.type ~= "file" and stat.type ~= "dir") then 477 | return false, "invalid path given", path 478 | end 479 | 480 | if stat.type == "file" then 481 | local removed, error = os.remove(path) 482 | if not removed then 483 | return false, error, path 484 | end 485 | else 486 | local contents = system.list_dir(path) 487 | if #contents > 0 and not recursively then 488 | return false, "directory is not empty", path 489 | end 490 | 491 | for _, item in pairs(contents) do 492 | local item_path = path .. PATHSEP .. item 493 | local item_stat = system.get_file_info(item_path) 494 | 495 | if not item_stat then 496 | return false, "invalid file encountered", item_path 497 | end 498 | 499 | if item_stat.type == "dir" then 500 | local deleted, error, ipath = common.rm(item_path, recursively) 501 | if not deleted then 502 | return false, error, ipath 503 | end 504 | elseif item_stat.type == "file" then 505 | local removed, error = os.remove(item_path) 506 | if not removed then 507 | return false, error, item_path 508 | end 509 | end 510 | end 511 | 512 | local removed, error = system.rmdir(path) 513 | if not removed then 514 | return false, error, path 515 | end 516 | end 517 | 518 | return true 519 | end 520 | 521 | 522 | return common 523 | -------------------------------------------------------------------------------- /.github/workflows/ecode-release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - ecode-[0-9]+.* 7 | 8 | workflow_dispatch: 9 | inputs: 10 | version: 11 | description: Release Version 12 | default: ecode-0.7.3 13 | required: true 14 | 15 | permissions: write-all 16 | 17 | jobs: 18 | release: 19 | name: Create Release 20 | runs-on: ubuntu-22.04 21 | outputs: 22 | upload_url: ${{ steps.create_release.outputs.upload_url }} 23 | version: ${{ steps.tag.outputs.version }} 24 | steps: 25 | - name: Checkout Code 26 | uses: actions/checkout@v4 27 | with: { fetch-depth: 0, submodules: 'recursive' } 28 | - name: Set Tag 29 | id: tag 30 | run: | 31 | if [[ "${{ github.event.inputs.version }}" != "" ]]; then 32 | echo "version=${{ github.event.inputs.version }}" | sed 's/ecode-//' >> "$GITHUB_OUTPUT" 33 | else 34 | echo "version=${GITHUB_REF/refs\/tags\//}" | sed 's/ecode-//' >> "$GITHUB_OUTPUT" 35 | fi 36 | - name: Update Tag 37 | uses: richardsimko/update-tag@v1 38 | with: 39 | tag_name: ecode-${{ steps.tag.outputs.version }} 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | - name: Create Release 43 | id: create_release 44 | uses: softprops/action-gh-release@v2 45 | with: 46 | tag_name: ecode-${{ steps.tag.outputs.version }} 47 | name: ecode ${{ steps.tag.outputs.version }} 48 | draft: false 49 | prerelease: true 50 | generate_release_notes: true 51 | body: > 52 | Changelog pending 53 | 54 | build_linux_x86_64: 55 | name: Linux x86_64 56 | needs: release 57 | strategy: 58 | matrix: 59 | config: 60 | - arch: x86_64 61 | container: ubuntu-22.04 62 | runs-on: ${{ matrix.config.container }} 63 | container: 64 | image: ubuntu:20.04 65 | env: 66 | CC: gcc 67 | CXX: g++ 68 | steps: 69 | - name: Install essentials 70 | run: | 71 | export DEBIAN_FRONTEND=noninteractive 72 | apt-get update 73 | apt-get install -y --no-install-recommends software-properties-common build-essential git ca-certificates 74 | - name: Checkout Code 75 | uses: actions/checkout@v4 76 | with: 77 | repository: 'SpartanJ/eepp' 78 | fetch-depth: 0 79 | submodules: 'recursive' 80 | ref: 'refs/tags/ecode-${{ needs.release.outputs.version }}' 81 | - name: Set Environment Variables 82 | run: | 83 | echo "$HOME/.local/bin" >> "$GITHUB_PATH" 84 | echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV" 85 | echo "RARCH=$(uname -m)" >> "$GITHUB_ENV" 86 | echo $(ldd --version) 87 | - name: Mark Git directory as safe 88 | run: git config --global --add safe.directory "$GITHUB_WORKSPACE" 89 | - name: Install dependencies 90 | run: | 91 | apt-get install -y curl libfuse2 fuse premake4 mesa-common-dev libgl1-mesa-dev sudo file appstream 92 | curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - 93 | apt-get install -y nodejs 94 | add-apt-repository -y universe 95 | add-apt-repository -y multiverse 96 | add-apt-repository -y ppa:ubuntu-toolchain-r/test 97 | apt-get update 98 | apt-get install -y gcc-13 g++-13 libdw-dev 99 | update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 10 100 | update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 10 101 | update-alternatives --install /usr/bin/cc cc /usr/bin/gcc 30 102 | update-alternatives --set cc /usr/bin/gcc 103 | update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++ 30 104 | update-alternatives --set c++ /usr/bin/g++ 105 | update-alternatives --config gcc 106 | update-alternatives --config g++ 107 | bash projects/linux/scripts/install_sdl2.sh 108 | - name: Build ecode 109 | run: | 110 | bash projects/linux/ecode/build.app.sh --with-static-cpp --arch ${{ matrix.config.arch }} 111 | - name: Upload Files 112 | uses: softprops/action-gh-release@v2 113 | with: 114 | repository: SpartanJ/ecode 115 | tag_name: ecode-${{ needs.release.outputs.version }} 116 | draft: false 117 | prerelease: true 118 | files: | 119 | projects/linux/ecode/ecode-linux-${{ env.INSTALL_REF }}-${{ env.RARCH }}.AppImage 120 | projects/linux/ecode/ecode-linux-${{ env.INSTALL_REF }}-${{ env.RARCH }}.tar.gz 121 | 122 | build_linux_arm64: 123 | name: Linux arm64 124 | needs: release 125 | strategy: 126 | matrix: 127 | config: 128 | - arch: aarch64 129 | container: ubuntu-22.04 130 | runs-on: ${{ matrix.config.container }} 131 | env: 132 | CC: gcc 133 | CXX: g++ 134 | steps: 135 | - name: Checkout Code 136 | uses: actions/checkout@v4 137 | with: 138 | repository: 'SpartanJ/eepp' 139 | fetch-depth: 0 140 | submodules: 'recursive' 141 | ref: 'refs/tags/ecode-${{ needs.release.outputs.version }}' 142 | - name: Set Environment Variables 143 | run: | 144 | echo "$HOME/.local/bin" >> "$GITHUB_PATH" 145 | echo "/usr/lib/ccache" >> "$GITHUB_PATH" 146 | echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV" 147 | echo "CC=aarch64-linux-gnu-gcc" >> "$GITHUB_ENV" 148 | echo "CXX=aarch64-linux-gnu-g++" >> "$GITHUB_ENV" 149 | echo "AR=aarch64-linux-gnu-ar" >> "$GITHUB_ENV" 150 | echo "RANLIB=aarch64-linux-gnu-ranlib" >> "$GITHUB_ENV" 151 | echo "STRIP=aarch64-linux-gnu-strip" >> "$GITHUB_ENV" 152 | echo "RARCH=arm64" >> "$GITHUB_ENV" >> "$GITHUB_ENV" 153 | echo $(ldd --version) 154 | echo $(gcc --version) 155 | - name: Update Packages 156 | run: | 157 | codename=$(lsb_release -cs) 158 | url="http://ports.ubuntu.com/ubuntu-ports" 159 | repos="main restricted universe multiverse" 160 | echo "deb [arch=arm64] $url $codename $repos" > arm64.list 161 | echo "deb [arch=arm64] $url $codename-backports $repos" >> arm64.list 162 | echo "deb [arch=arm64] $url $codename-security $repos" >> arm64.list 163 | echo "deb [arch=arm64] $url $codename-updates $repos" >> arm64.list 164 | sudo mv arm64.list /etc/apt/sources.list.d/ 165 | sudo apt-get update 166 | sudo dpkg --add-architecture arm64 167 | - name: Install dependencies 168 | run: | 169 | sudo apt-get install -y premake4 libfuse2 fuse gcc-aarch64-linux-gnu g++-aarch64-linux-gnu 170 | bash projects/linux/scripts/install_sdl2.sh --aarch64 171 | - name: Build ecode 172 | run: | 173 | bash projects/linux/ecode/build.app.sh --arch ${{ matrix.config.arch }} 174 | - name: Upload Files 175 | uses: softprops/action-gh-release@v2 176 | with: 177 | repository: SpartanJ/ecode 178 | tag_name: ecode-${{ needs.release.outputs.version }} 179 | draft: false 180 | prerelease: true 181 | files: | 182 | projects/linux/ecode/ecode-linux-${{ env.INSTALL_REF }}-${{ env.RARCH }}.AppImage 183 | projects/linux/ecode/ecode-linux-${{ env.INSTALL_REF }}-${{ env.RARCH }}.tar.gz 184 | 185 | build_windows_cross: 186 | name: Windows x86_64 187 | needs: release 188 | strategy: 189 | matrix: 190 | config: 191 | - compiler: default 192 | arch: x86_64 193 | container: ubuntu-22.04 194 | runs-on: ${{ matrix.config.container }} 195 | env: 196 | CC: gcc 197 | CXX: g++ 198 | steps: 199 | - name: Checkout Code 200 | uses: actions/checkout@v4 201 | with: 202 | repository: 'SpartanJ/eepp' 203 | fetch-depth: 0 204 | submodules: 'recursive' 205 | ref: 'refs/tags/ecode-${{ needs.release.outputs.version }}' 206 | - name: Set Environment Variables 207 | run: | 208 | echo "$HOME/.local/bin" >> "$GITHUB_PATH" 209 | echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV" 210 | echo "RARCH=$(uname -m)" >> "$GITHUB_ENV" 211 | - name: Update Packages 212 | run: | 213 | sudo add-apt-repository -y universe 214 | sudo add-apt-repository -y multiverse 215 | sudo apt-get update 216 | - name: Install dependencies 217 | run: | 218 | sudo apt-get install -y premake4 libfuse2 fuse mingw-w64 gcc-12 g++-12 219 | wget https://cdn.ensoft.dev/eepp-assets/premake-5.0.0-beta6-linux.tar.gz 220 | tar xvzf premake-5.0.0-beta6-linux.tar.gz 221 | sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 10 222 | sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 10 223 | sudo update-alternatives --install /usr/bin/cc cc /usr/bin/gcc 30 224 | sudo update-alternatives --set cc /usr/bin/gcc 225 | sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++ 30 226 | sudo update-alternatives --set c++ /usr/bin/g++ 227 | sudo update-alternatives --config gcc 228 | sudo update-alternatives --config g++ 229 | - name: Build ecode 230 | run: | 231 | bash projects/mingw32/ecode/build.app.sh 232 | - name: Upload Files 233 | uses: softprops/action-gh-release@v2 234 | with: 235 | repository: SpartanJ/ecode 236 | tag_name: ecode-${{ needs.release.outputs.version }} 237 | draft: false 238 | prerelease: true 239 | files: | 240 | projects/mingw32/ecode/ecode-windows-${{ env.INSTALL_REF }}-${{ env.RARCH }}.zip 241 | 242 | build_windows_arm64_cross: 243 | name: Windows arm64 244 | needs: release 245 | strategy: 246 | matrix: 247 | config: 248 | - compiler: default 249 | arch: arm64 250 | container: ubuntu-22.04 251 | runs-on: ${{ matrix.config.container }} 252 | steps: 253 | - name: Checkout Code 254 | uses: actions/checkout@v4 255 | with: 256 | repository: 'SpartanJ/eepp' 257 | fetch-depth: 0 258 | submodules: 'recursive' 259 | ref: 'refs/tags/ecode-${{ needs.release.outputs.version }}' 260 | - name: Set Environment Variables 261 | run: | 262 | echo "$HOME/.local/bin" >> "$GITHUB_PATH" 263 | echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV" 264 | - name: Update Packages 265 | run: | 266 | sudo add-apt-repository -y universe 267 | sudo add-apt-repository -y multiverse 268 | sudo apt-get update 269 | - name: Install dependencies 270 | run: | 271 | sudo apt-get install -y libfuse2 fuse 272 | wget https://cdn.ensoft.dev/eepp-assets/premake-5.0.0-beta6-linux.tar.gz 273 | tar xvzf premake-5.0.0-beta6-linux.tar.gz 274 | - name: Build ecode 275 | run: | 276 | bash projects/mingw32/ecode/build.app.sh --arch=arm64 277 | - name: Upload Files 278 | uses: softprops/action-gh-release@v2 279 | with: 280 | tag_name: ecode-${{ needs.release.outputs.version }} 281 | draft: false 282 | prerelease: true 283 | files: | 284 | projects/mingw32/ecode/ecode-windows-${{ env.INSTALL_REF }}-arm64.zip 285 | 286 | build_macos: 287 | name: macOS arm64 288 | needs: release 289 | runs-on: macos-14 290 | strategy: 291 | matrix: 292 | arch: [arm64] 293 | env: 294 | CC: clang 295 | CXX: clang++ 296 | MACOS_CERTIFICATE_P12_B64: ${{ secrets.MACOS_CERTIFICATE_P12_B64 }} 297 | MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} 298 | MACOS_APPLE_ID: ${{ secrets.MACOS_APPLE_ID }} 299 | MACOS_NOTARIZATION_PASSWORD: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }} 300 | MACOS_TEAM_ID: ${{ secrets.MACOS_TEAM_ID }} 301 | steps: 302 | - name: Checkout Code 303 | uses: actions/checkout@v4 304 | with: 305 | repository: 'SpartanJ/eepp' 306 | fetch-depth: 0 307 | submodules: 'recursive' 308 | ref: 'refs/tags/ecode-${{ needs.release.outputs.version }}' 309 | - name: System Information 310 | run: | 311 | system_profiler SPSoftwareDataType 312 | bash --version 313 | gcc -v 314 | xcodebuild -version 315 | uname -a 316 | - name: Set Environment Variables 317 | run: | 318 | echo "$HOME/.local/bin" >> "$GITHUB_PATH" 319 | echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV" 320 | - name: Install Dependencies 321 | run: | 322 | brew install bash sdl2 create-dmg premake p7zip 323 | - name: Build 324 | run: | 325 | bash projects/macos/ecode/build.app.sh 326 | - name: Sign Application 327 | if: env.MACOS_CERTIFICATE_P12_B64 != '' 328 | working-directory: projects/macos/ecode 329 | run: | 330 | bash ./sign.sh ecode.app 331 | - name: Create DMG Image 332 | run: | 333 | bash projects/macos/ecode/create.dmg.sh 334 | - name: Notarize DMG 335 | if: env.MACOS_CERTIFICATE_P12_B64 != '' 336 | working-directory: projects/macos/ecode 337 | run: | 338 | DMG_NAME="ecode-macos-${{ env.INSTALL_REF }}-arm64.dmg" 339 | bash ./sign.sh "$DMG_NAME" 340 | - name: Upload Files 341 | uses: softprops/action-gh-release@v2 342 | with: 343 | repository: SpartanJ/ecode 344 | tag_name: ecode-${{ needs.release.outputs.version }} 345 | draft: false 346 | prerelease: true 347 | files: | 348 | projects/macos/ecode/ecode-macos-${{ env.INSTALL_REF }}-arm64.dmg 349 | 350 | build_macos_cross: 351 | name: macOS x86_64 352 | needs: release 353 | runs-on: macos-14 354 | strategy: 355 | matrix: 356 | arch: [arm64] 357 | env: 358 | CC: clang 359 | CXX: clang++ 360 | MACOS_CERTIFICATE_P12_B64: ${{ secrets.MACOS_CERTIFICATE_P12_B64 }} 361 | MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} 362 | MACOS_APPLE_ID: ${{ secrets.MACOS_APPLE_ID }} 363 | MACOS_NOTARIZATION_PASSWORD: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }} 364 | MACOS_TEAM_ID: ${{ secrets.MACOS_TEAM_ID }} 365 | steps: 366 | - name: Checkout Code 367 | uses: actions/checkout@v4 368 | with: 369 | repository: 'SpartanJ/eepp' 370 | fetch-depth: 0 371 | submodules: 'recursive' 372 | ref: 'refs/tags/ecode-${{ needs.release.outputs.version }}' 373 | - name: System Information 374 | run: | 375 | system_profiler SPSoftwareDataType 376 | bash --version 377 | gcc -v 378 | xcodebuild -version 379 | uname -a 380 | - name: Set Environment Variables 381 | run: | 382 | echo "$HOME/.local/bin" >> "$GITHUB_PATH" 383 | echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV" 384 | - name: Install Dependencies 385 | run: | 386 | brew install bash create-dmg premake p7zip 387 | curl -OL https://github.com/libsdl-org/SDL/releases/download/release-2.32.10/SDL2-2.32.10.dmg 388 | hdiutil attach SDL2-2.32.10.dmg 389 | sudo cp -r /Volumes/SDL2/SDL2.framework /Library/Frameworks/ 390 | hdiutil detach /Volumes/SDL2 391 | - name: Build 392 | run: | 393 | bash projects/macos/ecode/cross.build.app.sh 394 | - name: Sign Application 395 | if: env.MACOS_CERTIFICATE_P12_B64 != '' 396 | working-directory: projects/macos/ecode 397 | run: | 398 | bash ./sign.sh ecode.app 399 | - name: Create DMG Image 400 | run: | 401 | bash projects/macos/ecode/cross.create.dmg.sh 402 | - name: Notarize DMG 403 | if: env.MACOS_CERTIFICATE_P12_B64 != '' 404 | working-directory: projects/macos/ecode 405 | run: | 406 | DMG_NAME="ecode-macos-${{ env.INSTALL_REF }}-x86_64.dmg" 407 | bash ./sign.sh "$DMG_NAME" 408 | - name: Upload Files 409 | uses: softprops/action-gh-release@v2 410 | with: 411 | repository: SpartanJ/ecode 412 | tag_name: ecode-${{ needs.release.outputs.version }} 413 | draft: false 414 | prerelease: true 415 | files: | 416 | projects/macos/ecode/ecode-macos-${{ env.INSTALL_REF }}-x86_64.dmg 417 | 418 | build_freebsd_x86_64: 419 | name: FreeBSD x86_64 420 | needs: release 421 | runs-on: ubuntu-latest 422 | steps: 423 | - name: Checkout Code 424 | uses: actions/checkout@v4 425 | with: 426 | repository: 'SpartanJ/eepp' 427 | fetch-depth: 0 428 | submodules: 'recursive' 429 | ref: 'refs/tags/ecode-${{ needs.release.outputs.version }}' 430 | - name: Set Environment Variables 431 | run: | 432 | echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV" 433 | echo "RARCH=$(uname -m)" >> "$GITHUB_ENV" 434 | - uses: vmactions/freebsd-vm@v1 435 | env: 436 | INSTALL_REF: ${{ needs.release.outputs.version }} 437 | with: 438 | release: '14.1' 439 | envs: 'INSTALL_REF' 440 | sync: sshfs 441 | usesh: true 442 | mem: 8192 443 | prepare: | 444 | pkg install -y bash git sdl2 curl premake5 gsed gmake 445 | run: | 446 | export CC=clang 447 | export CXX=clang++ 448 | git config --global --add safe.directory "$GITHUB_WORKSPACE" 449 | sh projects/freebsd/ecode/build.app.sh 450 | - name: Upload Files 451 | uses: softprops/action-gh-release@v2 452 | with: 453 | tag_name: ecode-${{ needs.release.outputs.version }} 454 | draft: false 455 | prerelease: true 456 | files: | 457 | projects/freebsd/ecode/ecode-freebsd-${{ env.INSTALL_REF }}-x86_64.tar.gz 458 | -------------------------------------------------------------------------------- /docs/customlanguages.md: -------------------------------------------------------------------------------- 1 | ## Custom languages support 2 | 3 | Custom languages support can be added in the languages directory found at: 4 | 5 | * *Linux*: uses `XDG_CONFIG_HOME`, usually translates to `~/.config/ecode/languages` 6 | * *macOS*: uses `Application Support` folder in `HOME`, usually translates to `~/Library/Application Support/ecode/languages` 7 | * *Windows*: uses `APPDATA`, usually translates to `C:\Users\{username}\AppData\Roaming\ecode\languages` 8 | 9 | ecode will read each file located at that directory with `json` extension. Each file can contain one or several language definitions. 10 | 11 | * **Single Language:** If the root element of the JSON file is an object, it defines a single language. 12 | * **Multiple Languages / Sub-Grammars:** If the root element is an array, it can define multiple independent languages *or* a main language along with **sub-language definitions** used for nesting within the main language (see Nested Syntaxes below). Each object in the array must be a complete language definition with at least a unique `"name"`. 13 | 14 | Language definitions can override any currently supported definition. ecode will prioritize user defined definitions. Sub-language definitions used only for nesting might not need fields like `"files"` or `"headers"` if they aren't intended to be selectable top-level languages. 15 | 16 | ### Language definition format 17 | 18 | ```json 19 | { 20 | "name": "language_name", // (Required) The display name of the language. Must be unique, especially if referenced by other definitions for nesting or includes. 21 | "files": [ // (Required if `visible` is `true`) An array of Lua patterns matching filenames for this language. 22 | "%.ext$", // Example: Matches files ending in .ext 23 | "^Makefile$" // Example: Matches the exact filename Makefile 24 | ], 25 | "comment": "//", // (Optional) Sets the single-line comment string used for auto-comment functionality. 26 | "patterns": [ // (Required) An array defining syntax highlighting rules. See "Pattern Rule Types" below for details. 27 | // ... pattern rules defined here ... 28 | ], 29 | "repository": { // (Optional) A collection of named pattern sets for reuse within this language definition. 30 | // Keys are repository item names (e.g., "comments", "strings", "expressions"). 31 | // Values are arrays of pattern rules, following the same format as the main "patterns" array. 32 | // These items can be referenced in any "patterns" array using an "include" rule (e.g., { "include": "#comments" }). 33 | "my_reusable_rules": [ 34 | { "pattern": "foo", "type": "keyword" }, 35 | { "pattern": ["bar_start", "bar_end"], "type": "string" } 36 | ] 37 | }, 38 | "symbols": [ // (Optional) An array defining specific types for exact words, primarily used in conjunction with patterns having `type: "symbol"`. 39 | // Structure: An array where each element is an object containing exactly one key-value pair. 40 | // - The key is the literal word (symbol) to match. 41 | // - The value is the `type_name` to apply when that word is matched via a `type: "symbol"` pattern rule. 42 | 43 | // How it works: 44 | // When a pattern rule results in a `type: "symbol"` match (either for the whole pattern or a capture group), 45 | // the actual text matched by that pattern/group is looked up within this `symbols` array. 46 | // The editor iterates through the array, checking if the key of any object matches the text. 47 | // If a match is found (e.g., the text is "if" and an object `{ "if": "keyword" }` exists), 48 | // the corresponding value ("keyword") is used for highlighting. 49 | // If the matched text is not found as a key in any object within this array, the highlighting typically falls back to the "normal" type. 50 | // This mechanism is essential for highlighting keywords, built-in constants/literals, and other reserved words 51 | // that might otherwise be matched by more general patterns (like a pattern for all words). 52 | 53 | // Example: 54 | { "if": "keyword" }, // If a `type: "symbol"` pattern matches "if", it will be highlighted as "keyword". 55 | { "else": "keyword" }, 56 | { "true": "literal" }, // If a `type: "symbol"` pattern matches "true", it will be highlighted as "literal". 57 | { "false": "literal" }, 58 | { "MyClass": "type" }, // If a `type: "symbol"` pattern matches "MyClass", it will be highlighted as "type". 59 | { "begin": "keyword" }, 60 | { "end": "keyword" } 61 | // ... add other specific words and their types as needed ... 62 | ], 63 | "headers": [ // (Optional) Array of Lua Patterns to identify file type by reading the first few lines (header). 64 | "^#!.*[ /]bash" // Example: Identifies bash scripts like '#!/bin/bash' 65 | ], 66 | "visible": true, // (Optional) If true (default), language appears in main selection menus. Set to false for internal/helper languages. 67 | "case_insensitive": false, // (Optional) If true, pattern matching ignores case. Default is false (case-sensitive). 68 | "auto_close_xml_tag": false, // (Optional) If true, enables auto-closing of XML/HTML tags (e.g., typing `
` automatically adds `
`). Default is false. 69 | "extension_priority": false, // (Optional) If true, this language definition takes priority if multiple languages define the same file extension. Default is false. 70 | "lsp_name": "language_server_name", // (Optional) Specifies the name recognized by Language Servers (LSP). Defaults to the 'name' field in lowercase if omitted. 71 | 72 | "fold_range_type": "braces", // (Optional) Specifies the strategy used to detect foldable code regions. Default behavior if omitted might be no folding or a global default. Possible values: 73 | // - "braces": Folding is determined by matching pairs of characters (defined in `fold_braces`). Suitable for languages like C, C++, Java, JavaScript, JSON. 74 | // - "indentation": Folding is determined by changes in indentation level. Suitable for languages like Python, YAML, Nim. 75 | // - "tag": Folding is based on matching HTML/XML tags (e.g., `
...
`). Suitable for HTML, XML, SVG. 76 | // - "markdown": Folding is based on Markdown header levels (e.g., `## Section Title`). Suitable for Markdown. 77 | "fold_braces": [ // (Required *only if* `fold_range_type` is "braces") Defines the pairs of characters used for brace-based folding. 78 | // This is an array of objects, where each object specifies a starting and ending character pair. 79 | { "start": "{", "end": "}" }, // Example: Standard curly braces 80 | { "start": "[", "end": "]" }, // Example: Square brackets 81 | { "start": "(", "end": ")" } // Example: Parentheses 82 | ] 83 | } 84 | ``` 85 | 86 | #### Pattern Rule Types 87 | 88 | The `"patterns"` array is the core of syntax highlighting. It contains an ordered list of rules that ecode attempts to match against the text. Each rule is an object. Here are the types of rules you can define: 89 | 90 | ```json 91 | "patterns": [ 92 | // --- Simple Rules --- 93 | // Rule using Lua patterns: 94 | { "pattern": "lua_pattern", "type": "type_name" }, 95 | // Rule using Lua patterns with capture groups mapping to different types: 96 | { "pattern": "no_capture(pattern_capture_1)(pattern_capture_2)", "type": [ "no_capture_type_name", "capture_1_type_name", "capture_2_type_name" ] }, 97 | // Rule using Perl-compatible regular expressions (PCRE): 98 | { "regex": "perl_regex", "type": "type_name" }, 99 | // Rule using PCRE with capture groups mapping to different types: 100 | { "regex": "no_capture(pattern_capture_1)(pattern_capture_2)", "type": [ "no_capture_type_name", "capture_1_type_name", "capture_2_type_name" ] }, 101 | 102 | // --- Multi-line Block Rules (Lua Patterns or PCRE) --- 103 | // Defines a block spanning multiple lines using start/end patterns and an optional escape character. 104 | // These rules can use either "pattern": ["start", "end", "escape?"] for Lua patterns 105 | // or "regex": ["start_regex", "end_regex", "escape_char?"] for PCRE. 106 | // They support the same "type" and "end_type" combinations for styling delimiters as detailed previously. 107 | // 108 | // Basic usage (same type for start and end delimiters): 109 | { "pattern": ["lua_pattern_start", "lua_pattern_end", "escape_character"], "type": "type_name" }, 110 | // Using different types for start and end delimiters: 111 | { "regex": ["regex_start", "regex_end"], "type": "start_type_name", "end_type": "end_type_name" }, 112 | // Using capture groups with different types for start and end delimiters: 113 | { "pattern": ["start_nocap(scap1)", "end_nocap(ecap1)(ecap2)", "escape"], "type": ["start_nocap_type", "start_cap1_type"], "end_type": ["end_nocap_type", "end_cap1_type", "end_cap2_type"] }, 114 | 115 | // --- Contextual Patterns within Blocks (Inner Patterns) --- 116 | // Multi-line block rules can define their own "patterns" array to apply specific rules 117 | // to the content *between* their start and end delimiters. This allows for more granular 118 | // highlighting within a block without needing to define a full sub-language via the "syntax" key. 119 | { 120 | "regex": ["
", "
"], // Defines the block 121 | "type": "keyword", // Type for "
" delimiter 122 | "end_type": "keyword", // Type for "
" delimiter 123 | "patterns": [ // (Optional) Inner patterns for content *inside*
...
124 | { "regex": "highlight_this_inside", "type": "function" }, 125 | { "include": "#common_section_rules" } // Can also include repository items 126 | // These inner patterns are matched only against the text between "
" and "
". 127 | // Inner patterns can themselves be block rules with their own inner patterns, allowing for nested contextual highlighting. 128 | ] 129 | }, 130 | // Note: If a block rule includes both inner "patterns" and a "syntax" key (for nested languages), 131 | // the "syntax" key typically takes precedence, causing the content to be highlighted by the 132 | // specified sub-language. Inner "patterns" are primarily for applying rules from the 133 | // *current* language's context or ad-hoc rules specifically to the content of this block. 134 | 135 | // --- Custom Parser Rule --- 136 | // Rule using a custom parser implemented in native code (as previously described): 137 | { "parser": "custom_parser_name", "type": "type_name" }, 138 | 139 | // --- Symbol Lookup Rule --- 140 | // Rule assigning the "symbol" type, for lookup in the language's "symbols" definition (as previously described): 141 | { "pattern": "[%a_][%w_]*", "type": "symbol" }, 142 | // Rule using "symbol" within capture groups (as previously described): 143 | { "pattern": "(%s%-%a[%w_%-]*%s+)(%a[%a%-_:=]+)", "type": [ "normal", "function", "symbol" ] }, 144 | 145 | // --- Include Rules --- 146 | // Allows reusing sets of patterns defined elsewhere in the grammar. 147 | // This helps in organizing complex grammars and avoiding repetition. 148 | { 149 | "include": "#repository_item_name" // Includes rules from the 'repository_item_name' entry 150 | // in the top-level "repository" object of this language definition. 151 | // The '#' prefix is mandatory for repository items. 152 | }, 153 | { 154 | "include": "$self" // Includes all rules from the main top-level "patterns" array 155 | // of the *current* language definition. This is useful for 156 | // recursive definitions, such as nested expressions or blocks. 157 | }, 158 | // Example using "include" with a "repository": 159 | // "repository": { 160 | // "comments_and_strings": [ 161 | // { "pattern": "//.*", "type": "comment" }, 162 | // { "pattern": ["\"", "\"", "\\\\"], "type": "string" } 163 | // ] 164 | // }, 165 | // "patterns": [ 166 | // { "include": "#comments_and_strings" }, 167 | // // ... other rules ... 168 | // ] 169 | 170 | // --- NESTED SYNTAX RULE --- 171 | // Defines a multi-line block that switches to a DIFFERENT language syntax inside. 172 | // Uses the same multi-line pattern/regex format and delimiter styling options ("type", "end_type"). 173 | { 174 | "pattern": ["lua_pattern_start", "lua_pattern_end", "escape_character"], // Can also use "regex" 175 | "syntax": "NestedLanguageName", // (Required for nesting) The 'name' of another language definition. 176 | "type": "start_delimiter_type", // (Optional) Type(s) for the START delimiter. 177 | "end_type": "end_delimiter_type" // (Optional) Type(s) for the END delimiter. 178 | } 179 | // This is distinct from "Contextual Patterns within Blocks (Inner Patterns)". 180 | // The "syntax" key switches highlighting to a completely different, pre-defined language 181 | // for the content within the delimiters. Inner "patterns", on the other hand, apply a 182 | // specific set of rules from the *current* language's context or ad-hoc rules to the block's content. 183 | ] 184 | ``` 185 | 186 | ### Nested Syntaxes (Sub-Grammars) 187 | 188 | ecode supports **nested syntaxes**, allowing a block of code within one language to be highlighted according to the rules of another language. This is crucial for accurately representing modern languages that often embed other languages or domain-specific languages. 189 | 190 | **How it works:** 191 | 192 | 1. **Define Sub-Languages:** Define the syntax for the language to be embedded (e.g., "CSS", "JavaScript", "XML", "SQL") as a separate language definition. Often, these are defined within the *same JSON file* as the main language, using a JSON array as the root element (see [Custom languages support](#custom-languages-support)). The sub-language definition needs a unique `"name"`. 193 | 2. **Reference in Patterns:** In the main language's `"patterns"`, use a multi-line block rule (`pattern` or `regex` array). Add the `"syntax"` key to this rule, setting its value to the `"name"` of the sub-language definition you want to use inside the block. 194 | 3. **Highlighting:** When ecode encounters this block, it applies the highlighting rules from the specified sub-language to the content *between* the start and end delimiters. The delimiters themselves are styled according to the `type` (and `end_type`) specified in the *outer* rule. 195 | 196 | **Contrast with Inner Patterns:** 197 | While the `syntax` key is used to embed an *entirely different language* within a block, multi-line block rules can also contain their own `patterns` array (see "Contextual Patterns within Blocks" under [Pattern Rule Types](#pattern-rule-types)). This inner `patterns` array allows for defining specific highlighting rules for the content *within* the block using rules from the current language's context or ad-hoc rules. This is useful when a full language switch isn't necessary but more granular control over the block's content highlighting is desired (e.g., highlighting specific keywords differently only within a certain type of block in the parent language). 198 | 199 | **Example Use Cases for `syntax` key:** 200 | 201 | * HTML files containing `