ecode is a lightweight multi-platform code editor designed for modern hardware with a focus on
responsiveness and performance. It has been developed with the hardware-accelerated eepp GUI,
which provides the core technology for the editor. The project comes as the first serious project using
the eepp GUI, and it’s currently being developed to improve the
eepp GUI library as part of one of its main objectives.
Screenshots
For more screenshots checkout running on macOS, running on Windows, running on Haiku, low dpi, code completion, terminal, file locator, file formats, global find, global replace, linter.
Notable Features
- Lightweight
- Portable
- Minimalist GUI
- Syntax Highlighting (including nested syntax highlighting, supporting over 50 languages)
- Terminal support
- LSP support
- Auto-Completion
- Customizable Linter support
- Customizable Formatter support
- Customizable Color-Schemes
- Customizable keyboard bindings
- Unlimited editor splitting
- Minimap
- Fast global search (and replace)
- Customizable and scalable (non-integer) GUI (thanks to eepp GUI)
- Dark & Light Mode
- File system Tree View (with real-time file system changes)
- Smart hot-reload of files
- Folders as Projects with
.gitignore
support * - Per Project Settings
- Smart and fast project file locator
- Multiline search and replace
- Project/Folder state persist between sessions
- Lua pattern searches support
- Plugins support.
Folder / Project Settings (*)
ecode treats folders as projects, like many other editors. The main difference is that it also tries
to automatically sanitize the project files by filtering out any file that it’s filtered in the repository
.gitignore
files. The idea is to use the .gitignore
file as a project setting.
The project files will be the ones used to find files in the project and do global searches.
Usually, this translates into much better results for any project-related search.
Currently there’s no way to “unfilter” files filtered by the .gitignore
configuration, but I plan
to add the functionality soon.
Also, ecode will only add files that are supported by the editor, the editor won’t try to do anything
with files that are not officially supported.
Philosophy
Some points to illustrate the project philosophy:
- Extendable functionality but in a controlled environment. New features and new plugins are accepted, but the author will supervise any new content that might affect the product quality and performance.
- Load as few files and resources as possible and load asynchronously as many resources as possible. Startup time of the application is considered critical.
- Use the machine resources but not abuse them.
- The editor implementation will try to prioritize performance and memory usage over simplicity.
- Developed with modern hardware in mind: expected hardware should have low file system latency (SSD), high cores count and decent GPU acceleration.
- Plugins and non-main functionality should never lock the main thread (GUI thread) or at least should block it as little as possible.
- Terminals are part of the developer workflow.
Live Demo
ecode can be compiled to WASM and run in any modern browser. There are no plans to focus the
development on the web version (at least for the moment) since there are plenty of good solutions out
there. But you can give it a try:
Demo Clarifications
- You’ll need a modern browser with SharedArrayBuffer support
- Linter, Formatter and LSP plugins won’t work since both work running other processes
- WebGL renderer isn’t optimized, so it’s not as fast as it could/should be (still, performance is good in chromium based browsers)
- Demo is designed for desktop resolutions (mobile is unusable, IME keyboard won’t show up due to an emscripten limitation)
Source Code
Currently, the source code is located at the eepp project repository.
ecode editor source is located at src/tools/ecode.
ecode is being used to actively improve and iterate the eepp GUI library. At some point, it will be
migrated to this repository. The ecode repository should be used for issues and documentation.
PRs for ecode will be accepted at the eepp repository.
Build from Source
There are scripts for each supported platform ready to build the application.
For Linux and macOS it is trivial to build the project, you’ll just need to have GCC/Clang installed
and also the development library from libSDL2. Windows build script is currently a cross-compiling script and it uses mingw64.
But it also can be easily built with Visual Studio and libSDL2 development libraries installed.
For more information on how to build manually a project please follow the eepp build instructions.
The project name is always ecode (so if you are building with make, you’ll need to run make ecode
).
- Linux build script can be found here. Running
build.app.sh
will try to build theAppImage
package. - macOS build script can be found here. Running
build.app.sh
will createecode.app
. - Windows cross-compiling build script can be found here. Running
build.app.sh
will create azip
file with the zipped application package.
Plugins
Plugins extend the base code editor functionality. Currently all plugins are enabled by default, but
they are optional and they can be disabled at any time. ecode implements an internal protocol that
allow plugins to communicate with each other. The LSP protocol is going to be used as a base to implement
the plugin communication. And, for example, the Linter plugin will consume the LSP to improve its diagnostics.
Also the Auto Complete module will request assistance from the LSP, if available, to improve the
completions and to provide signature help.
Linter
Linter support is provided by executing already stablished linters from each language.
ecode provides support for several languages by default and can be extended easily by expanding the
linters.json
configuration. linters.json
default configuration can be obtained from here.
To configure new linters you can create a new linters.json
file in the default configuration path of ecode.
linters.json
format
The format is a very simple JSON object with a config object and array of objects containing the file
formats supported, the Lua pattern to find any error printed by the linter to the stdout, the position
of each group of the pattern, and the command to execute. It also supports some optional extra object keys.
JavaScript linter example (using eslint)
{ "config": { "delay_time": "0.5s" }, "linters": [ { "file_patterns": ["%.js$", "%.ts$"], "warning_pattern": "[^:]:(%d+):(%d+): ([^%[]+)%[([^n]+)", "warning_pattern_order": { "line": 1, "col": 2, "message": 3, "type": 4 }, "command": "eslint --no-ignore --format unix $FILENAME" } ] }
That’s all we need to have a working linter in ecode. Linters executables must be installed manually
by the user, linters will not come with the editor, and they also need to be visible to the executable.
This means that it must be on PATH
environment variable or the path to the binary must be absolute.
Currently supported linters and languages supported
- JavaScript and TypeScript: eslint
- PHP: uses the php official binary
- JSON: linted with jq
- Lua: uses luacheck
- Python: uses pycodestyle
- sh: uses shellcheck
- Solidity: uses solhint
- C++: uses cppcheck
- Kotlin: uses ktlint
- Zig: uses the zig official binary
- Nim: uses the nim official binary
Linter config object keys
- delay_time: Delay to run the linter after editing a document
- enable_lsp_diagnostics: Boolean that enable/disable LSP diagnostics as part of the linting. Enabled by default.
- disable_lsp_languages: Array of LSP languages disabled for LSP diagnostics. For example:
"disable_lsp_languages": ["lua", "python"]
, disables lua and python.
Linter JSON object keys
- file_patterns: Array of Lua Patterns representing the file extensions that must use the linter
- warning_pattern: Lua Pattern to be parsed from the executable stdout
- 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.
- command: The command to execute to run the linter. $FILENAME represents the file path.
- expected_exitcodes: Array of integer numbers accepted as parseable exit codes (optional)
- no_errors_exit_code: Integer number representing the exit code that means that no errors were found (optional).
- deduplicate: In case the linter outputs duplicated errors, this boolean will ignore duplicated errors (optional, boolean true/false)
- 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).
Formatter
The formatter plugin works exactly like the linter plugin, but it will execute tools that auto-format code.
ecode provides support for several languages by default with can be extended easily by expanding the
formatters.json
configuration. formatters.json
default configuration can be obtained from here.
It also supports some formatters natively, this means that the formatter comes with ecode without requiring any external dependency.
To configure new formatters you can create a new formatters.json
file in the default configuration path of ecode.
formatters.json
format
{ "config": { "auto_format_on_save": false }, "keybindings": { "format-doc": "alt+f" }, "formatters": [ { "file_patterns": ["%.js$", "%.ts$"], "command": "prettier $FILENAME" } ] }
Currently supported formatters and languages supported
- JavaScript and TypeScript: uses prettier formatter
- JSON: uses JSON for Modern C++ native formatter (no external formatter required).
- C++: uses clang-format formatter
- Python: uses black formatter
- Kotlin: uses ktlint formatter
- CSS: uses the eepp CSS native formatter (no external formatter required).
- XML: uses pugixml native formatter (no external formatter required).
Formatter config object keys
- auto_format_on_save: Indicates if after saving the file it should be auto-formatted
Formatter keybindings object keys
- *format-doc: Keybinding to format the doc with the configured language formatter
Formatter JSON object keys
- file_patterns: Array of Lua Patterns representing the file extensions that must use the formatter
- command: The command to execute to run the formatter. $FILENAME represents the file path
- 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)
LSP Client
LSP support is provided by executing already stablished LSP from each language. It’s currently being
developed and many features aren’t present at the moment.
ecode provides support for several languages by default and can be extended easily by expanding the
lspclient.json
configuration. lspclient.json
default configuration can be obtained from here.
To configure new LSPs you can create a new lspclient.json
file in the default configuration path of ecode.
Important note: LSP servers can be very resource intensive and might not be always the best option for simple projects.
Implementation details: LSP servers are only loaded when needed, no process will be opened until a
supported file is opened in the project.
lspclient.json
format
The format follows the same pattern that all previous configuration files. Configuration is represented
in a JSON file with three main keys: config
, keybindings
, servers
.
C and C++ LSP server example (using clangd)
{ "config": { "hover_delay": "0.5s" }, "servers": [ { "language": "c", "name": "clangd", "url": "https://clangd.llvm.org/", "command": "clangd -log=error --background-index --limit-results=500 --completion-style=bundled", "file_patterns": ["%.c$", "%.h$", "%.C$", "%.H$", "%.objc$"] }, { "language": "cpp", "use": "clangd", "file_patterns": ["%.inl$", "%.cpp$", "%.hpp$", "%.cc$", "%.cxx$", "%.c++$", "%.hh$", "%.hxx$", "%.h++$", "%.objcpp$"] } ] }
That’s all we need to have a working LSP in ecode. LSPs executables must be installed manually
by the user, LSPs will not come with the editor, and they also need to be visible to the executable.
This means that it must be on PATH
environment variable or the path to the binary must be absolute.
Currently supported LSPs and languages supported
- JavaScript and TypeScript: typescript-language-server
- C and C++: uses clangd
- D: uses server
- Zig: uses zls
- Go: uses gopls
- PHP: uses intelephense
- Lua: uses lua-language-server
- Python: uses pylsp
- Rust: uses the rust-analyzer
LSP Client config object keys
- hover_delay: The time the editor must wait to show symbol information when hovering any piece of code.
- 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.
LSP Client keybindings object keys
- lsp-go-to-definition: Keybinding to “Go to Definition”
- lsp-go-to-declaration: Keybinding to “Go to Declaration”
- lsp-go-to-implementation: Keybinding to “Go to Implementation”
- lsp-go-to-type-definition: Keybinding to “Go to Type Definition”
- lsp-switch-header-source: Keybinding to “Switch Header/Source” (only available for C and C++)
LSP Client JSON object keys
- language: The LSP language identifier. Some identifiers can be found here
- name: The name of the language server
- url (optional): The web page URL of the language server
- 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).
- file_patterns: Array of Lua Patterns representing the file extensions that must use the LSP client
- command: The command to execute to run the LSP
- 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. Usu