Cleanup and minor Fixes

This commit is contained in:
2026-01-28 18:46:44 +00:00
parent cca0177929
commit 6abdefa808
22 changed files with 343 additions and 371 deletions

2
.gitignore vendored
View File

@@ -13,5 +13,7 @@ build
bin bin
.thinlto-cache/ .thinlto-cache/
Gemfile*
.ruby-lsp/
__old__ __old__

View File

@@ -13,14 +13,14 @@ CCACHE := ccache
CXX := $(CCACHE) clang++ CXX := $(CCACHE) clang++
CFLAGS_DEBUG :=\ CFLAGS_DEBUG :=\
-std=c++20 -Wall -Wextra \ -std=c++23 -Wall -Wextra -Wno-c23-extensions \
-O0 -fno-inline -gsplit-dwarf\ -O0 -fno-inline -gsplit-dwarf\
-g -fno-omit-frame-pointer\ -g -fno-omit-frame-pointer\
-Wno-unused-command-line-argument \ -Wno-unused-command-line-argument \
-I./include -I./libs \ -I./include -I./libs \
-I/usr/include/ruby-3.4.0 -I/usr/include/ruby-3.4.0/x86_64-linux -I/usr/include/ruby-3.4.0 -I/usr/include/ruby-3.4.0/x86_64-linux
CFLAGS_RELEASE :=\ CFLAGS_RELEASE :=\
-std=c++20 -O3 -march=native \ -std=c++23 -O3 -march=native \
-fno-rtti -fstrict-aliasing \ -fno-rtti -fstrict-aliasing \
-ffast-math -flto=thin \ -ffast-math -flto=thin \
-fvisibility=hidden -fuse-ld=lld \ -fvisibility=hidden -fuse-ld=lld \
@@ -28,6 +28,7 @@ CFLAGS_RELEASE :=\
-mllvm -vectorize-loops \ -mllvm -vectorize-loops \
-fno-unwind-tables -fno-asynchronous-unwind-tables\ -fno-unwind-tables -fno-asynchronous-unwind-tables\
-Wno-unused-command-line-argument \ -Wno-unused-command-line-argument \
-Wno-c23-extensions \
-I./include -I./libs \ -I./include -I./libs \
-I/usr/include/ruby-3.4.0 -I/usr/include/ruby-3.4.0/x86_64-linux -I/usr/include/ruby-3.4.0 -I/usr/include/ruby-3.4.0/x86_64-linux
@@ -41,7 +42,7 @@ UNICODE_OBJ_RELEASE := $(patsubst libs/unicode_width/%.c,$(OBJ_DIR)/release/unic
LIBS := \ LIBS := \
libs/libgrapheme/libgrapheme.a \ libs/libgrapheme/libgrapheme.a \
-lpcre2-8 -lmagic -lruby -Wl,-Bstatic -lpcre2-8 -Wl,-Bdynamic -lmagic -lruby
SRC := $(wildcard $(SRC_DIR)/**/*.cc) $(wildcard $(SRC_DIR)/*.cc) SRC := $(wildcard $(SRC_DIR)/**/*.cc) $(wildcard $(SRC_DIR)/*.cc)
OBJ_DEBUG := $(patsubst $(SRC_DIR)/%.cc,$(OBJ_DIR)/debug/%.o,$(SRC)) OBJ_DEBUG := $(patsubst $(SRC_DIR)/%.cc,$(OBJ_DIR)/debug/%.o,$(SRC))

150
README.md
View File

@@ -51,16 +51,19 @@ And any modern terminal should work fine - preferably `kitty` or `wezterm`.<br>
#### `./libs` folder #### `./libs` folder
Some other dependancies like `libgrapheme` and `unicode_width` are added as submodules or copied.<br> Some other dependancies are added as submodules or copied.<br>
`unicode_width` is compiled by the makefile so nothing to do there.<br> - `unicode_width` is compiled by the makefile so nothing to do there.<br>
`libgrapheme` needs to be compiled using `make` in it's folder.<br> - `libgrapheme` needs to be compiled using `make` in it's folder.<br>
- ``
#### LSPs #### LSPs
The following lsp's are supported and can be installed anywhere in your `$PATH`<br> Lsp's are defined in the `libcrib.rb` file and you can use your `config/main.rb` file to add more.<br>
The following lsp's are added by default and can be installed anywhere in your `$PATH`<br>
* [clangd](https://clangd.llvm.org/) * [clangd](https://clangd.llvm.org/)
* [solargraph](https://solargraph.org/) * [ruby-lsp](https://shopify.github.io/ruby-lsp/)
* [bash-language-server](https://github.com/bash-lsp/bash-language-server) * [bash-language-server](https://github.com/bash-lsp/bash-language-server)
* [vscode-css-language-server](https://github.com/hrsh7th/vscode-langservers-extracted) * [vscode-css-language-server](https://github.com/hrsh7th/vscode-langservers-extracted)
* [vscode-json-language-server](https://github.com/hrsh7th/vscode-langservers-extracted) * [vscode-json-language-server](https://github.com/hrsh7th/vscode-langservers-extracted)
@@ -81,14 +84,11 @@ The following lsp's are supported and can be installed anywhere in your `$PATH`<
* [make-language-server](https://github.com/Freed-Wu/autotools-language-server) * [make-language-server](https://github.com/Freed-Wu/autotools-language-server)
> As it is still in development, some of these may not work as expected or that well.<br> > As it is still in development, some of these may not work as expected or that well.<br>
> But for c/ruby/lua/python it should work fine (I test more with these).<br>
> It should work even if the lsp is not installed but lsp features will not work.<br> > It should work even if the lsp is not installed but lsp features will not work.<br>
> See `include/config.h` & `include/ts/decl.h` if you want to add your own lsp and/or tree-sitter grammar.<br>
#### Compiler #### Compiler
`g++` and `clang++` should both work fine but `c++20+` is required. `clang++` should work fine but `c++23+` is required.<br>
The makefile uses `clang++` by default.<br>
Can remove `ccache` if you want from the makefile.<br> Can remove `ccache` if you want from the makefile.<br>
#### Compliling #### Compliling
@@ -100,9 +100,6 @@ make release
### Running ### Running
Preferably add the `bin` folder to PATH or move `bin/crib` to somewhere in PATH.<br> Preferably add the `bin` folder to PATH or move `bin/crib` to somewhere in PATH.<br>
But make sure that `scripts/` are at `../` relative to the binary or it will crash.<br>
`scripts/init.sh` and `scripts/exit.sh` can be used to add hooks to the editor on startup and exit
(Make sure to remove my `kitty` hooks from them if you want).<br>
For some LSP's to work properly `crib` needs to be run from the root folder of the project. *To be fixed*<br> For some LSP's to work properly `crib` needs to be run from the root folder of the project. *To be fixed*<br>
then do -<br> then do -<br>
@@ -110,127 +107,12 @@ then do -<br>
crib ./filename.ext crib ./filename.ext
``` ```
*If `filename.ext` does not exist, it will fail to load the editor - use `touch filename.ext` to create it - to be fixed*<br> *If `filename.ext` does not exist, it will be created*<br>
*Try out with files in `samples/`*<br>
## Keybindings ## Keybindings
### Mouse Interactions TODO: add keybind information on how to set in `config/main.rb`
and default / unchangeable keybinds
These interactions work globally or generally across the editor canvas.
| Action | Function |
| --- | --- |
| **Scroll Up/Down** | Scrolls the view. |
| **Scroll Left/Right** | Moves the cursor left or right. |
| **Left Click (Press)** | Moves cursor to position; resets selection. |
| **Left Click (Double)** | Selects the **word** under the cursor (enters SELECT mode). |
| **Left Click (Triple)** | Selects the **line** under the cursor (enters SELECT mode). |
| **Left Click (Drag)** | Selects text (Character, Word, or Line based on initial click type). |
| **Left Click (Release)** | If cursor and selection start are the same, returns to NORMAL mode. |
### Navigation (Global / Special Keys)
These keys work primarily in Normal mode but handle movement logic.
| Key | Modifier | Function |
| --- | --- | --- |
| **Arrows** (Up/Down/Left/Right) | None | Move cursor 1 step in that direction. |
| **Arrows** (Up/Down) | `CTRL` | Move cursor **5 steps** in that direction. |
| **Arrows** (Left/Right) | `CTRL` | Jump to the previous/next **word boundary**. |
| **Arrows** (Up/Down) | `ALT` | **Move the current line** Up or Down. |
| **Arrows** (Left/Right) | `ALT` | Move cursor **8 steps** in that direction. |
### NORMAL Mode
This is the default navigation and command mode.
| Key | Function |
| --- | --- |
| **i** | Enter **INSERT** mode (at current position). |
| **a** | Enter **INSERT** mode (append: moves cursor right by 1 first). |
| **s** or **v** | Enter **SELECT** mode (start character selection). |
| **:** or **;** | Enter **RUNNER** mode (Command Bar). |
| **u** | Select the **last line** of the file (Jumps to bottom). |
| **h** | Trigger **LSP Hover** information for the symbol under cursor. |
| **Ctrl + h** | Scroll the hover window **Up**. |
| **Ctrl + l** | Scroll the hover window **Down**. |
| **Ctrl + s** | **Save** the file. |
| **Ctrl + d** | Scroll Page **Down** (1 unit). |
| **Ctrl + u** | Scroll Page **Up** (1 unit). |
| **p** | **Paste** from clipboard at cursor position (moves cursor to end of paste). |
| **>** or **.** | **Indent** the current line. |
| **<** or **,** | **Dedent** (un-indent) the current line. |
| **Space** | Move cursor Right. |
| **Backspace** (`0x7F`) | Move cursor Left. |
| **Enter** (`\n`, `\r`) | Move cursor Down. |
| **\| or \\** | Move cursor Up. |
| **n** | Enter **JUMPER** mode (Set Bookmark). |
| **m** | Enter **JUMPER** mode (Jump to Bookmark). |
| **N** | Clear specific Jumper hook (logic attempts to clear hook at current line). |
### INSERT Mode
Used for typing text.
| Key | Function |
| --- | --- |
| **Esc** (`0x1B`) | Return to **NORMAL** mode. |
| **Tab** (`\t`) | Inserts 2 spaces. |
| **Enter** | Inserts newline + **Auto-indents** based on previous line/context. |
| **Backspace** | Deletes previous character or auto-collapses empty pairs (e.g., `{` -> `}`). |
| **Ctrl + w** | **Delete Previous Word**. |
| **Del** | Delete character under cursor. |
| **Ctrl + Del** | Delete **Next Word**. |
| **Typing** | Inserts characters. |
| **Ctrl + Shift + v or as configured in your terminal** | System pasting. |
| **{ ( [ " '** | Auto-inserts closing pair (e.g., typing `{` inserts `{}`). |
| **} ) ] " '** | If the next char matches the typed char, skip insertion (overwrite), otherwise insert. |
#### Autocompletion (Inside Insert Mode)
These function only if LSP and completion are active.
| Key | Function |
| --- | --- |
| **Ctrl + p** | Select **Next** completion item. |
| **Ctrl + o** | Select **Previous** completion item. |
| **Ctrl + \\** | **Accept** selected completion OR trigger new completion request. |
| **Trigger Chars** | (e.g., `.`, `>`) Automatically triggers completion popup. |
### SELECT Mode
Used for highlighting text.
| Key | Function |
| --- | --- |
| **Esc**, **s**, **v** | Cancel selection and return to **NORMAL** mode. |
| **y** | **Yank (Copy)** selection to clipboard → Return to Normal. |
| **x** | **Cut** selection to clipboard → Return to Normal. |
| **p** | **Paste** over selection (Replace text) → Return to Normal. |
| **f** | **Fold** the selected range (collapses code) → Return to Normal. |
### JUMPER Mode
This mode uses a bookmarking system mapped to keyboard characters.
* **Entered via `n` (Set Mode):**
* Pressing any key `!` through `~` assigns the current line number to that key.
* **Entered via `m` (Jump Mode):**
* Pressing any key `!` through `~` jumps the cursor to the line previously assigned to that key.
### RUNNER Mode (Command Bar)
Activated by `:` or `;`.
| Key | Function |
| --- | --- |
| **Esc** | Cancel and return to **NORMAL** mode. |
| **Enter** | Execute the typed command. |
| **Left / Right** | Move cursor within the command bar. |
| **Up / Down** | Intended for command history. (Not implemented) |
| **Typing** | Insert characters into the command bar. (Not implemented) |
## Features Implemented ## Features Implemented
@@ -250,7 +132,7 @@ Activated by `:` or `;`.
- hooks jumping (bookmarking) - hooks jumping (bookmarking)
- color hex code highlighting - color hex code highlighting
- current line highlighting - current line highlighting
<!-- - TODO: current word under cursor highlighting --> - all instances of current word under cursor highlighting
#### syntax highlighting and filetype detection (using extention or libmagic) for: #### syntax highlighting and filetype detection (using extention or libmagic) for:
- ruby - ruby
@@ -286,11 +168,13 @@ Activated by `:` or `;`.
#### LSP-powered features: #### LSP-powered features:
- diagnostics - diagnostics
- autocompletion - autocompletion
- hover docs (with markdown support) - hover docs
- formatting support - formatting support
- Full file formatting on save - Full file formatting on save
- Ontype formatting when inserting special characters defined by the language server - Ontype formatting when inserting special characters defined by the language server
- *(few lsp's actually support this - try to configure a few more which can but need configuration and for others need to add support for external formatters)* - *(few lsp's actually support this - try to configure a few more which can but need configuration and for others need to add support for external formatters)*
- A list of all supported lsp's can be found [here](#lsps). - A list of some lsp's can be found [here](#lsps).
- Any lsp can be added to the `config/main.rb` file.
- Though not all might work well. Open an issue if you find a lsp that doesn't work well.
**A lot lot more to come** **A lot lot more to come**

67
TODO.md
View File

@@ -2,15 +2,12 @@ Copyright 2025 Syed Daanish
# TODO # TODO
### Critical Fixes ##### BTW Check each lsp with each of the features implemented
##### Check each lsp with each of the features implemented
* Static link pcre2-8 and libmagic and add precompiled libmagic magic database for better file detection
* [ ] **LSP Bug:** Check why `fish-lsp` is behaving so off with completions filtering. * [ ] **LSP Bug:** Check why `fish-lsp` is behaving so off with completions filtering.
* [ ] **File IO:** Normalize/validate unicode on file open (enforce UTF-8, handle other types gracefully).
* [ ] **Critical Crash:** Fix bug where closing immediately while LSP is still loading hangs and then segfaults (especially on slow ones like fish-lsp where quick edits and exit can hang). * [ ] **Critical Crash:** Fix bug where closing immediately while LSP is still loading hangs and then segfaults (especially on slow ones like fish-lsp where quick edits and exit can hang).
* [ ] **Line move:** fix the move line functions to work without the calculations from folds as folds are removed. * [ ] **Line move:** fix the move line functions to work without the calculations from folds as folds are removed.
* [ ] **Modularize handle_events and renderer functions:** The function is over 700 lines with a lot of repeating blocks. Split into smaller functions.
* [ ] **Editor Indentation Fix:** - Main : merger indentation with the parser for more accurate results. * [ ] **Editor Indentation Fix:** - Main : merger indentation with the parser for more accurate results.
* [ ] Keep cache of language maps in engine to reduce lookup time. * [ ] Keep cache of language maps in engine to reduce lookup time.
* [ ] In indents add function to support tab which indents if before any content and inserts a pure \t otherwise. * [ ] In indents add function to support tab which indents if before any content and inserts a pure \t otherwise.
@@ -20,22 +17,35 @@ Copyright 2025 Syed Daanish
* [ ] These will dedent when the block immediately after them is dedented * [ ] These will dedent when the block immediately after them is dedented
* [ ] Dont dedent if ending is valid starting is invalid but also empty * [ ] Dont dedent if ending is valid starting is invalid but also empty
* [ ] Just leave asis if starting is empty * [ ] Just leave asis if starting is empty
* [ ] **Readme:** Update readme to show indetation mechanics. * [ ] **Readme:** Update readme to show ruby based config.
* [ ] **LSP Bug:** Try to find out why emojis are breaking lsp edits. (check the ruby sample)
* [ ] **UI Refinement:** * [ ] **UI Refinement:**
* [ ] Allow completion list to be scrolled; show only `x` max items. * [ ] Allow completion list to be scrolled; show only `x` max items.
* [ ] Finish autocomplete box style functions. * [ ] Finish autocomplete box style functions.
* [ ] **Documentation UI:** Capture `Ctrl+h` / `Ctrl+l` for scrolling documentation windows. * [ ] **Documentation UI:** Capture `Ctrl+h` / `Ctrl+l` for scrolling documentation windows.
* [ ] **Redo hooks and folding as proper engines**: With functions to checkstate/cursor like function and edits application. * [ ] Redo hooks as a struct.
* [ ] Do trextmate like regex grammar parsing with lsp symbols for semantic highlighting. * [ ] breakdown the render function into smaller functions.
* Probably remove tre--sitter or just keep it for context tree.
* Making bracket matching andignoring strings/comments easier.
* remove tree-sitter mention from everywhere especially submodules
* make it faster for line inserts/deletes too (treeify the vector)
* Try to make all functions better now that folds have been purged * Try to make all functions better now that folds have been purged
* Cleanup syntax and renderer files * Cleanup syntax and renderer files
probably remove solargraph support and use ruby-lsp (or sorbet?) instead * Add a thing called view which is a rect with speacial type editor . but otherwise a ruby or c++ view
* can be used for stuff like file manager/git manager/theme picker.
* allow flushing functions in ruby to tell c++ to refresh keybinds/themes etc.
* allow keybinds to be set in ruby
check::
pull diagnostics for ruby-lsp
lsp selection range - use to highlight start / end of range maybe?
goto definiton
signature help
document symbol for the top bar maybe? (or workspace symbol)
also setup workspaces
Semantic highlighting
Quick fixes
Rename symbols
probably remove solargraph support and use ruby-lsp (or sorbet?) instead because it is a pain for utf stuff
ruby-lsp also supports erb so thats a plus
move lsp configs to json and also allow configs for windows-style vs unix-style line endings and utf-8 vs utf-16 move lsp configs to json and also allow configs for windows-style vs unix-style line endings and utf-8 vs utf-16
@@ -55,7 +65,7 @@ move lsp configs to json and also allow configs for windows-style vs unix-style
* [ ] **Tree-sitter Indent:** Attempt to allow Tree-sitter to handle indentation if possible. * [ ] **Tree-sitter Indent:** Attempt to allow Tree-sitter to handle indentation if possible.
* [ ] **Scrolling:** Add logic where selecting at the end of the screen scrolls down (and vice versa). * [ ] **Scrolling:** Add logic where selecting at the end of the screen scrolls down (and vice versa).
* *Implementation:* Update `main.cc` to send drag events to the selected editor. * *Implementation:* Update `main.cc` to send drag events to the selected editor even if coordinates are out of bounds.
### UX ### UX
@@ -72,11 +82,6 @@ move lsp configs to json and also allow configs for windows-style vs unix-style
* [ ] Handle snippets properly in autocomplete: use only the last word in signature when replacing and set cursor to the first one. * [ ] Handle snippets properly in autocomplete: use only the last word in signature when replacing and set cursor to the first one.
* [ ] **Basic Autocomplete:** Keep a list of words in the current buffer for non-LSP fallback. * [ ] **Basic Autocomplete:** Keep a list of words in the current buffer for non-LSP fallback.
* [ ] **Language Support:**
* [ ] Add ECMA to JS and make TSX support.
* [ ] Add formatting for files where LSP doesn't provide it.
* [ ] Redo grammar files properly (especially cpp).
### Major Features ### Major Features
@@ -93,20 +98,11 @@ move lsp configs to json and also allow configs for windows-style vs unix-style
* [ ] **Block Selection:** * [ ] **Block Selection:**
* [ ] Double-clicking a bracket selects the whole block (first time only) and sets mode to `WORD`. * [ ] Double-clicking a bracket selects the whole block (first time only) and sets mode to `WORD`.
* [ ] **Tree-sitter Context:**
* [ ] Get code context from Tree-sitter.
* [ ] Get node path of current cursor and add indicator bar (breadcrumbs).
* [ ] Highlight block edges when cursor is on/in a bracket.
### Visuals, UI & Extensions? ### Visuals, UI & Extensions?
*Focus: Aesthetics and external integrations.*
* [ ] **Status Bar:** Complete status bar and command runner. * [ ] **Status Bar:** Complete status bar and command runner.
* [ ] **Visual Aids:**
* [ ] Expand color regex to match CSS colors in CSS files.
* [ ] Add color picker/palette. * [ ] Add color picker/palette.
* [ ] **Git:** Add Git integration (status, diffs). * [ ] **Git:** Add Git integration (status, diffs).
@@ -120,21 +116,8 @@ move lsp configs to json and also allow configs for windows-style vs unix-style
### Optimizations & Fluff ### Optimizations & Fluff
* [ ] **Event Loop:**
* [ ] Make the whole engine event-driven rather than clock-driven.
* [ ] Mybe keep background thread with dirty flag.
* [ ] But merge input and render into a single loop that only renders when input affects render or background thread needs refresh and try to couple multiple renders.
* [ ] LSP and inputs should be blocking (lsp on its fd) and inputs in io/input.cc
* [ ] **Performance:** * [ ] **Performance:**
* [ ] Switch JSON parser to `RapidJSON` (or similar high-performance lib). * [ ] Switch JSON parser to `RapidJSON` (or similar high-performance lib).
* [ ] Decrease usage of `std::string` in UI, LSP, and warnings. * [ ] Decrease usage of `std::string` in UI, LSP, and warnings.
* [ ] Also for vectors into managed memory especially for completions/lsp-stuff. * [ ] Also for vectors into managed memory especially for completions/lsp-stuff.
* [ ] **Folding:** Redo folding system and its relation to `move_line_*` functions.
* [ ] **Grammars:**
* [ ] Manually add redo SCM files (especially cpp/c/h).
* [ ] Create `lua-typed` and `man pages` Tree-sitter grammars.
* [ ] **Repo Maintenance:** Once renderer is proven check commit `43f443e`.

View File

@@ -1,6 +1,6 @@
require_relative "libcrib"
# basic configuration # basic configuration
# This can also be used to do speacail configs for different projects.
# its ruby guys script whatever you want.
C.startup do C.startup do
puts "Starting crib..." puts "Starting crib..."
@@ -86,7 +86,7 @@ C.theme = {
# lsp: "solargraph" # lsp: "solargraph"
# } # }
C.line_endings = :unix # or :windows C.line_endings = :auto_unix # or :unix or :windows or :auto_windows
# C.extra_highlights do |_line, _idx| # C.extra_highlights do |_line, _idx|
# # the return can be an array of # # the return can be an array of
@@ -99,7 +99,7 @@ C.line_endings = :unix # or :windows
# The highlighter will be aplied to the language as long as the langauge is defined in C.languages # The highlighter will be aplied to the language as long as the langauge is defined in C.languages
C.highlighters[:ruby_n] = { C.highlighters[:ruby_n] = {
parser: ->(line, state) { parser: ->(line, state, line_idx) {
# the return value is a hash # the return value is a hash
# it contains the state and the highlights # it contains the state and the highlights
# state can be of any type but will be consistent between calls # state can be of any type but will be consistent between calls
@@ -108,14 +108,14 @@ C.highlighters[:ruby_n] = {
# the highlights can be an array of # the highlights can be an array of
# [K_type, start, end] # [K_type, start, end]
# K_type is a constant from the constants defined in libcrib.rb # K_type is a constant from the constants defined in libcrib.rb
# for ex: for strings it would be C::K_STRING or for numbers C::K_NUMBER etc. # for ex: for strings it would be Tokens::K_STRING or for numbers Tokens::K_NUMBER etc.
# and start and end are integers strictly inside the line # and start and end are integers strictly inside the line
return { return {
state: "", state: "",
tokens: [ tokens: [
# This will highlight the entire line as a string # This will highlight the entire line as a string
# Any wrong format will not be handled and lead to crashes # Any wrong format will not be handled and lead to crashes
{ type: C::K_STRING, start: 0, end: line.length } { type: Tokens::K_STRING, start: 0, end: line.length }
] ]
} }
}, },

View File

@@ -11,6 +11,7 @@
#include "ui/diagnostics.h" #include "ui/diagnostics.h"
#include "ui/hover.h" #include "ui/hover.h"
#include "utils/utils.h" #include "utils/utils.h"
#include <cstdint>
#define CHAR 0 #define CHAR 0
#define WORD 1 #define WORD 1
@@ -57,7 +58,7 @@ struct Editor {
}; };
Editor *new_editor(const char *filename_arg, Coord position, Coord size, Editor *new_editor(const char *filename_arg, Coord position, Coord size,
bool unix_eol); uint8_t eol);
void save_file(Editor *editor); void save_file(Editor *editor);
void free_editor(Editor *editor); void free_editor(Editor *editor);
void render_editor(Editor *editor); void render_editor(Editor *editor);

View File

@@ -7,15 +7,16 @@
extern std::unordered_map<std::string, std::pair<VALUE, VALUE>> extern std::unordered_map<std::string, std::pair<VALUE, VALUE>>
custom_highlighters; custom_highlighters;
void ruby_start(const char *main_file); void ruby_start();
void ruby_shutdown(); void ruby_shutdown();
void ruby_log(std::string msg); void ruby_log(std::string msg);
void load_theme(); void load_theme();
void load_languages_info(); void load_languages_info();
bool read_line_endings(); uint8_t read_line_endings();
void load_custom_highlighters(); void load_custom_highlighters();
VALUE parse_custom(std::vector<Token> *tokens, VALUE parser_block, VALUE parse_custom(std::vector<Token> *tokens, VALUE parser_block,
const char *line, uint32_t len, VALUE state); const char *line, uint32_t len, VALUE state,
uint32_t line_num);
bool custom_compare(VALUE match_block, VALUE state1, VALUE state2); bool custom_compare(VALUE match_block, VALUE state1, VALUE state2);
#endif #endif

View File

@@ -26,6 +26,24 @@ const std::unordered_map<std::string, TokenKind> kind_map = {
#undef ADD #undef ADD
}; };
constexpr const char tokens_def[] = "module Tokens\n"
#define STRINGIFY_HELPER(x) #x
#define STRINGIFY(x) STRINGIFY_HELPER(x)
#define ADD(name) " " #name " = " STRINGIFY(__COUNTER__) "\n"
#include "syntax/tokens.def"
#undef ADD
#undef STRINGIFY
#undef STRINGIFY_HELPER
" freeze\n"
"end";
constexpr const char crib_module[] = {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc23-extensions"
#embed "libcrib.rb"
#pragma clang diagnostic pop
, '\0'};
extern std::array<Highlight, TOKEN_KIND_COUNT> highlights; extern std::array<Highlight, TOKEN_KIND_COUNT> highlights;
struct Token { struct Token {

View File

@@ -4,9 +4,9 @@
#include "syntax/decl.h" #include "syntax/decl.h"
#define DEF_LANG(name) \ #define DEF_LANG(name) \
std::shared_ptr<void> name##_parse(std::vector<Token> *tokens, \ std::shared_ptr<void> name##_parse( \
std::shared_ptr<void> in_state, \ std::vector<Token> *tokens, std::shared_ptr<void> in_state, \
const char *text, uint32_t len); \ const char *text, uint32_t len, uint32_t line_num); \
bool name##_state_match(std::shared_ptr<void> state_1, \ bool name##_state_match(std::shared_ptr<void> state_1, \
std::shared_ptr<void> state_2); std::shared_ptr<void> state_2);
@@ -34,9 +34,9 @@ DEF_LANG(bash);
inline static const std::unordered_map< inline static const std::unordered_map<
std::string, std::string,
std::tuple<std::shared_ptr<void> (*)(std::vector<Token> *tokens, std::tuple<std::shared_ptr<void> (*)(
std::shared_ptr<void> in_state, std::vector<Token> *tokens, std::shared_ptr<void> in_state,
const char *text, uint32_t len), const char *text, uint32_t len, uint32_t line_num),
bool (*)(std::shared_ptr<void> state_1, bool (*)(std::shared_ptr<void> state_1,
std::shared_ptr<void> state_2)>> std::shared_ptr<void> state_2)>>
parsers = { parsers = {

View File

@@ -1,57 +1,4 @@
module C module C
K_DATA = 0
K_SHEBANG = 1
K_COMMENT = 2
K_ERROR = 3
K_STRING = 4
K_ESCAPE = 5
K_INTERPOLATION = 6
K_REGEXP = 7
K_NUMBER = 8
K_TRUE = 9
K_FALSE = 10
K_CHAR = 11
K_KEYWORD = 12
K_KEYWORDOPERATOR = 13
K_OPERATOR = 14
K_FUNCTION = 15
K_TYPE = 16
K_CONSTANT = 17
K_VARIABLEINSTANCE = 18
K_VARIABLEGLOBAL = 19
K_ANNOTATION = 20
K_DIRECTIVE = 21
K_LABEL = 22
K_BRACE1 = 23
K_BRACE2 = 24
K_BRACE3 = 25
K_BRACE4 = 26
K_BRACE5 = 27
K_HEADING1 = 28
K_HEADING2 = 29
K_HEADING3 = 30
K_HEADING4 = 31
K_HEADING5 = 32
K_HEADING6 = 33
K_BLOCKQUOTE = 34
K_LIST = 35
K_LISTITEM = 36
K_CODE = 37
K_LANGUAGENAME = 38
K_LINKLABEL = 39
K_IMAGELABEL = 40
K_LINK = 41
K_TABLE = 42
K_TABLEHEADER = 43
K_ITALIC = 44
K_BOLD = 45
K_UNDERLINE = 46
K_STRIKETHROUGH = 47
K_HORIXONTALRULE = 48
K_TAG = 49
K_ATTRIBUTE = 50
K_CHECKDONE = 51
K_CHECKNOTDONE = 52
@lsp_config = { @lsp_config = {
"clangd" => [ "clangd" => [
"--background-index", "--background-index",
@@ -195,7 +142,7 @@ module C
extensions: ["erb"], extensions: ["erb"],
filenames: [], filenames: [],
mimetypes: ["text/x-erb"], mimetypes: ["text/x-erb"],
lsp: "emmet-language-server" lsp: "ruby-lsp"
}, },
lua: { lua: {
color: 0x36a3d9, color: 0x36a3d9,
@@ -350,11 +297,45 @@ module C
lsp: "bash-language-server" lsp: "bash-language-server"
} }
} }
@theme = {
:default => { fg: 0xEEEEEE },
:shebang => { fg: 0x7DCFFF },
:error => { fg: 0xEF5168 },
:comment => { fg: 0xAAAAAA, italic: true },
:string => { fg: 0xAAD94C },
:escape => { fg: 0x7DCFFF },
:interpolation => { fg: 0x7DCFFF },
:regexp => { fg: 0xD2A6FF },
:number => { fg: 0xE6C08A },
# rubocop:disable Lint/BooleanSymbol
:true => { fg: 0x7AE93C },
:false => { fg: 0xEF5168 },
# rubocop:enable Lint/BooleanSymbol
:char => { fg: 0xFFAF70 },
:keyword => { fg: 0xFF8F40 },
:keywordoperator => { fg: 0xF07178 },
:operator => { fg: 0xFFFFFF, italic: true },
:function => { fg: 0xFFAF70 },
:type => { fg: 0xF07178 },
:constant => { fg: 0x7DCFFF },
:variableinstance => { fg: 0x95E6CB },
:variableglobal => { fg: 0xF07178 },
:annotation => { fg: 0x7DCFFF },
:directive => { fg: 0xFF8F40 },
:label => { fg: 0xD2A6FF },
:brace1 => { fg: 0xD2A6FF },
:brace2 => { fg: 0xFFAFAF },
:brace3 => { fg: 0xFFFF00 },
:brace4 => { fg: 0x0FFF0F },
:brace5 => { fg: 0xFF0F0F }
}
@line_endings = :auto_unix
@key_handlers = {} @key_handlers = {}
@key_binds = {} @key_binds = {}
@highlighters = {} @highlighters = {}
@log_queue = [] @log_queue = []
@line_endings = :unix @b_startup = nil
@b_shutdown = nil
class << self class << self
attr_accessor :theme, :lsp_config, :languages, attr_accessor :theme, :lsp_config, :languages,

View File

@@ -10,7 +10,8 @@ struct Parser {
std::string lang; std::string lang;
std::shared_ptr<void> (*parse_func)(std::vector<Token> *tokens, std::shared_ptr<void> (*parse_func)(std::vector<Token> *tokens,
std::shared_ptr<void> in_state, std::shared_ptr<void> in_state,
const char *text, uint32_t len); const char *text, uint32_t len,
uint32_t line_num);
bool (*state_match_func)(std::shared_ptr<void> state_1, bool (*state_match_func)(std::shared_ptr<void> state_1,
std::shared_ptr<void> state_2); std::shared_ptr<void> state_2);
VALUE parser_block = Qnil; VALUE parser_block = Qnil;

View File

@@ -1,4 +1,3 @@
// When updating this file with new types do not forget to update libcrib.rb to parallel changes
ADD(K_DATA) ADD(K_DATA)
ADD(K_SHEBANG) ADD(K_SHEBANG)
ADD(K_COMMENT) ADD(K_COMMENT)

View File

@@ -154,7 +154,7 @@ std::string path_abs(const std::string &path_str);
std::string path_to_file_uri(const std::string &path_str); std::string path_to_file_uri(const std::string &path_str);
std::string filename_from_path(const std::string &path); std::string filename_from_path(const std::string &path);
std::string get_exe_dir(); std::string get_exe_dir();
char *load_file(const char *path, uint32_t *out_len); char *load_file(const char *path, uint32_t *out_len, bool *out_eol);
char *detect_file_type(const char *filename); char *detect_file_type(const char *filename);
Language language_for_file(const char *filename); Language language_for_file(const char *filename);

View File

@@ -47,7 +47,7 @@ while ((counter < 5)); do
done done
# Subshelled loops and alternating quoting # Subshelled loops and alternating quoting
for item in "${ITEMS[@]}"; do for item in "${ITEMS[@]}}"; do
( (
msg="Processing $item" msg="Processing $item"
echo "$(colorize blue "$msg")" echo "$(colorize blue "$msg")"

View File

@@ -4,13 +4,12 @@
# Purpose: Test syntax highlighting + width calculation in your editor # Purpose: Test syntax highlighting + width calculation in your editor
# --------------------------------------------------------------- # ---------------------------------------------------------------
# Mixed-width CJKssssssssssssssss LoadErssssssssssssssssssssssss # Mixed-width CJKssssssssssssssss LoadErssssssssssssssssssssssss
cjk_samples = [ cjk_samples = [
"漢字テスト", '漢字テスト',
"測試中文字串", '測試中文字串',
"한국어 테스트", '한국어 테스트',
"ひらがなカタカナ混合", 'ひらがなカタカナ混合'
] ]
# a hex color: #FFFFFF shouldn't hl here: hsl(147rad, 50%, 47%) as it is not css-style file # a hex color: #FFFFFF shouldn't hl here: hsl(147rad, 50%, 47%) as it is not css-style file
@@ -18,34 +17,33 @@ cjk_samples = [
0x603010 # another hex color 0x603010 # another hex color
# Ruby regex with unicode # Ruby regex with unicode
$unicode_regex_multiline = /[一-龯ぁ-ん#{0x3000} $unicode_regex_multiline = /[一-龯ぁ-ん12288
\-ヶー \-ヶー
s wow s wow
々〆〤]/ 々〆〤]/
UNICORE = %r{ UNICORE = /
[s] s
{#{ss}} {#{ss}}
\C-s\u{10} \C-s\u{10}
} /
UNINITCORE = %{ UNINITCORE = %(
{{#{}}} {{#{}}}
test = "A:\x41 B:\101 C:\u0043 D:\u{44 45} NUL:\0 DEL:\c? CTRL_A:\cA META_X:\M-x CTRL_META_X:\C-\M-x MIX:\C-\M-z N:\N{UNICODE NAME}" test = "A:\x41 B:\101 C:\u0043 D:\u{44 45} NUL:\0 DEL:\c? CTRL_A:\cA META_X:\M-x CTRL_META_X:\C-\M-x MIX:\C-\M-z N:\N{UNICODE NAME}"
} )
# Unicode identifiers (valid in Ruby) # Unicode identifiers (valid in Ruby)
= 0x5_4eddaee = 0x5_4eddaee
π = 3.14_159e+2, ?\u0234, ?\,, ?\x0A, ?s, true, false, 0 π = 0.314_159e+2, ?\u0234, "\,", ?\x0A, 's', true, false, 0
= -> { "こんに \n ちは" } = -> { "こんに \n ちは" }
arr = Array.new() arr = []
not_arr = NotABuiltin.new() not_arr = NotABuiltin.new
raise NameError or SystemExit or CustomError or Errno or ErrorNotAtAll raise NameError or SystemExit or CustomError or Errno or ErrorNotAtAll
@@ -56,7 +54,7 @@ end
# Iterate through CJK samples # Iterate through CJK samples
cjk_samples.each_with_index do |str, idx:| cjk_samples.each_with_index do |str, idx:|
puts %Q! CJK[#{idx}] => #{str} (len=#{str.length})\! ! puts %! CJK[#{idx}] => #{str} (len=#{str.length})\! !
symbol = :" symbol = :"
a a
" "
@@ -67,7 +65,7 @@ end
puts "Emoji count: #{emojis.length}" puts "Emoji count: #{emojis.length}"
# Multi-line string with unicode # Multi-line string with unicode
multi = <<BASH multi = <<~BASH
# Function recursion demo # Function recursion demo
factorial() { factorial() {
local n="$1" local n="$1"
@@ -78,10 +76,10 @@ multi = <<BASH
prev=$(factorial $((n - 1))) prev=$(factorial $((n - 1)))
echo $((n * prev)) echo $((n * prev))
before #{ interpol before #{ interpol
# {' '}
# comment should be fine heres s # comment should be fine heres s
$a / $-s + 0xFF $a / $-s + 0xFF
}s }s#{' '}
x x
a after a after
fi fi
@@ -93,20 +91,17 @@ puts multi
# Arrays mixing everything # Arrays mixing everything
mixed = [ mixed = [
"🐍 Ruby + Python? sacrilege! 🐍", '🐍 Ruby + Python? sacrilege! 🐍',
"日本語とEnglishと🔧mix", '日本語とEnglishと🔧mix',
"Spacing test →→→→→→→", 'Spacing test →→→→→→→',
"Zero-width joiner test: 👨‍👩‍👧‍👦 family emoji", 'Zero-width joiner test: 👨‍👩‍👧‍👦 family emoji'
] ]
two_docs = <<DOC1 , <<DOC2 two_docs = <<~DOC1, <<~DOC2
stuff for doc2 stuff for doc2
rdvajehvbaejbfh rdvajehvbaejbfh
DOC1 DOC1
stuff for doc 2 with #{not interpolation} and more stuff for doc 2 with #{!interpolation} and more
DOC2 DOC2
p = 0 << 22 # not a heredoc p = 0 << 22 # not a heredoc
@@ -129,7 +124,7 @@ puts escaped
p = 0 << 2 p = 0 << 2
# Frozen string literal test # Frozen string literal test
# frozen_string_literal: true # frozen_string_literal: true
const_str = "定数文字列🔒".freeze const_str = '定数文字列🔒'.freeze
puts const_str puts const_str
# End marker # End marker
@@ -137,14 +132,12 @@ puts '--- END OF UNICODE TEST FILE ---'
# Ruby syntax highlighting test # Ruby syntax highlighting test
=begin # This is a multi-line comment.
This is a multi-line comment. # It spans multiple lines.
It spans multiple lines. # Good for testing highlighting.
Good for testing highlighting. #
# This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped linetest,
This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped linetest, #
=end
# Constants # Constants
@@ -153,11 +146,12 @@ MAX_ITER = 5
# Module # Module
module Utilities module Utilities
def self.random_greeting def self.random_greeting
["Hello", "Hi", "Hey", "Hola", "Bonjour", "Merhaba"].sample %w[Hello Hi Hey Hola Bonjour Merhaba].sample
end end
def self.factorial(n) def self.factorial(n)
return 1 if n <= 1 return 1 if n <= 1
n * factorial(n - 1) n * factorial(n - 1)
end end
end end
@@ -218,18 +212,16 @@ end
# Method definition # Method definition
def greet_person(name) def greet_person(name)
puts "#{Utilities.random_greeting}, #{name}!" puts "#{Utilities.random_greeting}, #{name}!"
if (name == "harry") return true if name == 'harry'
return true
else 's'
return "s"
end
end end
h = a / a h = a / a
# Calling methods # Calling methods
greet_person("Alice") greet_person('Alice')
greet_person("Bob") greet_person('Bob')
# Loops # Loops
i = 0 i = 0
@@ -249,7 +241,7 @@ begin
rescue ZeroDivisionError => e rescue ZeroDivisionError => e
puts "Caught an error: #{e}" puts "Caught an error: #{e}"
ensure ensure
puts "This runs no matter what" puts 'This runs no matter what'
end end
# Arrays of objects # Arrays of objects
@@ -285,7 +277,7 @@ end
end end
# Special objects # Special objects
so = SpecialObject.new("Special", 10) so = SpecialObject.new('Special', 10)
puts "Double: #{so.double_value}, Triple: #{so.triple_value}" puts "Double: #{so.double_value}, Triple: #{so.triple_value}"
# String interpolation and formatting # String interpolation and formatting
@@ -296,8 +288,8 @@ multi_line = <<~TEXT
k kmW ; k kmW ;
This is a multi-line string. This is a multi-line string.
It spans multiple lines. It spans multiple lines.
Gossn m Gossn sssmss
dd ddsss
od for testing highlighting. od for testing highlighting.
TEXT TEXT
@@ -305,7 +297,7 @@ puts multi_line
# Symbols and strings # Symbols and strings
sym = :my_symbol == __dir__ sym = :my_symbol == __dir__
str = "my string" str = 'my string'
puts "Symbol: #{sym}, String: #{str}" puts "Symbol: #{sym}, String: #{str}"
# Random numbers # Random numbers
@@ -324,25 +316,25 @@ end
# Block with yield # Block with yield
def wrapper def wrapper
puts "Before block" puts 'Before block'
yield if block_given? yield if block_given?
puts "After block" puts 'After block'
end end
# ss # ss
wrapper { puts "Inside block" } wrapper { puts 'Inside block' }
# Sorting # Sorting
sorted = rand_nums.sort sorted = rand_nums.sort
puts "Sorted: #{sorted.join(', ')}" puts "Sorted: #{sorted.join(', ')}"
# Regex # Regex
sample_text = "The quick brown fox jumps over the lazy dog" sample_text = 'The quick brown fox jumps over the lazy dog'
puts "Match 'fox'?" if sample_text =~ /fox/ puts "Match 'fox'?" if sample_text =~ /fox/
# End of test script # End of test script
puts "Ruby syntax highlighting test complete." puts 'Ruby syntax highlighting test complete.'
__END__ __END__

View File

@@ -5,18 +5,21 @@
#include "utils/utils.h" #include "utils/utils.h"
Editor *new_editor(const char *filename_arg, Coord position, Coord size, Editor *new_editor(const char *filename_arg, Coord position, Coord size,
bool unix_eol) { uint8_t eol) {
Editor *editor = new Editor(); Editor *editor = new Editor();
if (!editor) if (!editor)
return nullptr; return nullptr;
uint32_t len = 0; uint32_t len = 0;
std::string filename = path_abs(filename_arg); std::string filename = path_abs(filename_arg);
char *str = load_file(filename.c_str(), &len); editor->unix_eol = eol & 1;
char *str = load_file(filename.c_str(), &len, &editor->unix_eol);
if (!str) { if (!str) {
free_editor(editor); str = (char *)malloc(1);
return nullptr; *str = '\n';
len = 1;
} }
editor->unix_eol = unix_eol; if ((eol >> 1) & 1)
editor->unix_eol = eol & 1;
editor->filename = filename; editor->filename = filename;
editor->uri = path_to_file_uri(filename); editor->uri = path_to_file_uri(filename);
editor->position = position; editor->position = position;

View File

@@ -76,7 +76,7 @@ int main(int argc, char *argv[]) {
ruby_init(); ruby_init();
ruby_start((get_exe_dir() + "/../config/main.rb").c_str()); ruby_start();
load_theme(); load_theme();
load_languages_info(); load_languages_info();
load_custom_highlighters(); load_custom_highlighters();
@@ -84,10 +84,10 @@ int main(int argc, char *argv[]) {
Coord screen = start_screen(); Coord screen = start_screen();
const char *filename = (argc > 1) ? argv[1] : ""; const char *filename = (argc > 1) ? argv[1] : "";
bool unix_eol = read_line_endings(); uint8_t eol = read_line_endings();
Editor *editor = Editor *editor =
new_editor(filename, {0, 0}, {screen.row - 2, screen.col}, unix_eol); new_editor(filename, {0, 0}, {screen.row - 2, screen.col}, eol);
Bar bar(screen); Bar bar(screen);
auto end = std::chrono::high_resolution_clock::now(); auto end = std::chrono::high_resolution_clock::now();

View File

@@ -1,5 +1,3 @@
#include "ruby/internal/gc.h"
#include "ruby/internal/value.h"
#include "scripting/decl.h" #include "scripting/decl.h"
#include "utils/utils.h" #include "utils/utils.h"
@@ -28,16 +26,55 @@ struct R_Language {
VALUE C_module = Qnil; VALUE C_module = Qnil;
std::mutex ruby_mutex; std::mutex ruby_mutex;
void ruby_start(const char *main_file) { namespace fs = std::filesystem;
static void ruby_load(const char *main_file) {
std::lock_guard lock(ruby_mutex); std::lock_guard lock(ruby_mutex);
ruby_init_loadpath(); ruby_init_loadpath();
int state = 0; int state = 0;
rb_load_protect(rb_str_new_cstr(main_file), 0, &state); rb_load_protect(rb_str_new_cstr(main_file), 0, &state);
if (state) { if (state) {
rb_errinfo(); VALUE err = rb_errinfo();
rb_set_errinfo(Qnil); rb_set_errinfo(Qnil);
fprintf(stderr, "%d: Failed to load Ruby file\n", state); fprintf(stderr, "%d: Failed to load Ruby file %s\n", state, main_file);
return; }
}
static void ruby_eval_string(const char *code) {
int state = 0;
rb_eval_string_protect(code, &state);
if (state) {
VALUE err = rb_errinfo();
rb_set_errinfo(Qnil);
fprintf(stderr, "Ruby eval failed\n");
}
}
void ruby_start() {
fs::path exe_dir = get_exe_dir();
std::vector<fs::path> candidates;
candidates.emplace_back("./crib.rb");
const char *xdg = std::getenv("XDG_CONFIG_HOME");
const char *home = std::getenv("HOME");
if (xdg) {
candidates.emplace_back(fs::path(xdg) / "crib/crib.rb");
candidates.emplace_back(fs::path(xdg) / "crib/main.rb");
candidates.emplace_back(fs::path(xdg) / "crib.rb");
} else if (home) {
fs::path base = fs::path(home) / ".config";
candidates.emplace_back(base / "crib/crib.rb");
candidates.emplace_back(base / "crib/main.rb");
candidates.emplace_back(base / "crib.rb");
}
candidates.emplace_back(exe_dir / "../config/main.rb");
candidates.emplace_back(exe_dir / "../config/crib.rb");
ruby_eval_string(crib_module);
ruby_eval_string(tokens_def);
for (const auto &p : candidates) {
if (fs::exists(p) && fs::is_regular_file(p)) {
ruby_load(p.string().c_str());
break;
}
} }
C_module = rb_const_get(rb_cObject, rb_intern("C")); C_module = rb_const_get(rb_cObject, rb_intern("C"));
if (C_module == Qnil) if (C_module == Qnil)
@@ -102,14 +139,16 @@ bool custom_compare(VALUE match_block, VALUE state1, VALUE state2) {
} }
VALUE parse_custom(std::vector<Token> *tokens, VALUE parser_block, VALUE parse_custom(std::vector<Token> *tokens, VALUE parser_block,
const char *line, uint32_t len, VALUE state) { const char *line, uint32_t len, VALUE state,
uint32_t c_line) {
std::lock_guard lock(ruby_mutex); std::lock_guard lock(ruby_mutex);
tokens->clear(); tokens->clear();
if (NIL_P(parser_block)) if (NIL_P(parser_block))
return {}; return {};
VALUE ruby_line = rb_str_new(line, len); VALUE ruby_line = rb_str_new(line, len);
VALUE tokens_and_state_hash = VALUE line_idx = UINT2NUM(c_line);
rb_funcall(parser_block, rb_intern("call"), 2, ruby_line, state); VALUE tokens_and_state_hash = rb_funcall(parser_block, rb_intern("call"), 3,
ruby_line, state, line_idx);
VALUE tokens_rb = VALUE tokens_rb =
rb_hash_aref(tokens_and_state_hash, ID2SYM(rb_intern("tokens"))); rb_hash_aref(tokens_and_state_hash, ID2SYM(rb_intern("tokens")));
for (long i = 0; i < RARRAY_LEN(tokens_rb); ++i) { for (long i = 0; i < RARRAY_LEN(tokens_rb); ++i) {
@@ -287,12 +326,22 @@ void load_languages_info() {
lsps[lsp.command] = lsp; lsps[lsp.command] = lsp;
} }
bool read_line_endings() { uint8_t read_line_endings() {
std::lock_guard lock(ruby_mutex); std::lock_guard lock(ruby_mutex);
if (C_module == Qnil) if (C_module == Qnil)
return true; return 1;
VALUE le = rb_funcall(C_module, rb_intern("line_endings"), 0); VALUE le = rb_funcall(C_module, rb_intern("line_endings"), 0);
if (SYMBOL_P(le)) if (!SYMBOL_P(le))
return std::string(rb_id2name(SYM2ID(le))) == "unix"; return 1;
return true; uint8_t flags = 1;
const char *name = rb_id2name(SYM2ID(le));
if (std::strcmp(name, "unix") == 0)
flags = 0b01;
else if (std::strcmp(name, "windows") == 0)
flags = 0b00;
else if (std::strcmp(name, "auto_unix") == 0)
flags = 0b11;
else if (std::strcmp(name, "auto_windows") == 0)
flags = 0b10;
return flags;
} }

View File

@@ -1,11 +1,10 @@
#include "syntax/decl.h" #include "syntax/decl.h"
#include "syntax/langs.h" #include "syntax/langs.h"
#include <cstdint>
struct BashFullState { struct BashFullState {
int brace_level = 0; int brace_level = 0;
enum : uint8_t { NONE, STRING, HEREDOC }; enum : uint8_t { NONE, STRING, HEREDOC, PARAMETER };
uint8_t in_state = BashFullState::NONE; uint8_t in_state = BashFullState::NONE;
bool line_cont = false; bool line_cont = false;
@@ -50,7 +49,8 @@ bool bash_state_match(std::shared_ptr<void> state_1,
std::shared_ptr<void> bash_parse(std::vector<Token> *tokens, std::shared_ptr<void> bash_parse(std::vector<Token> *tokens,
std::shared_ptr<void> in_state, std::shared_ptr<void> in_state,
const char *text, uint32_t len) { const char *text, uint32_t len,
uint32_t line_num) {
static bool keywords_trie_init = false; static bool keywords_trie_init = false;
if (!keywords_trie_init) { if (!keywords_trie_init) {
keywords_trie_init = true; keywords_trie_init = true;
@@ -64,10 +64,44 @@ std::shared_ptr<void> bash_parse(std::vector<Token> *tokens,
if (len == 0) if (len == 0)
return state; return state;
while (i < len) { while (i < len) {
if (state->full_state->in_state == BashFullState::PARAMETER) {
uint32_t start = i;
while (i < len) {
if (text[i] == '{') {
i++;
state->full_state->brace_level++;
continue;
}
if (text[i] == '}') {
if (--state->full_state->brace_level == 0 &&
!state->interp_stack.empty()) {
tokens->push_back({i - 1, i, TokenKind::K_INTERPOLATION});
state->full_state = state->interp_stack.top();
state->interp_stack.pop();
i++;
break;
}
}
i++;
}
continue;
}
if (state->full_state->in_state == BashFullState::STRING) { if (state->full_state->in_state == BashFullState::STRING) {
uint32_t start = i; uint32_t start = i;
while (i < len) { while (i < len) {
if (state->full_state->lit.allow_interp && text[i] == '$') {
if (++i < len && text[i] == '{') {
tokens->push_back({start, i - 1, TokenKind::K_STRING});
tokens->push_back({i - 1, i, TokenKind::K_INTERPOLATION});
state->interp_stack.push(state->full_state);
state->full_state = std::make_shared<BashFullState>();
state->full_state->in_state = BashFullState::PARAMETER;
state->full_state->brace_level = 1;
break;
}
}
if (text[i] == state->full_state->lit.delim[0]) { if (text[i] == state->full_state->lit.delim[0]) {
i++;
tokens->push_back({start, i, TokenKind::K_STRING}); tokens->push_back({start, i, TokenKind::K_STRING});
state->full_state->in_state = BashFullState::NONE; state->full_state->in_state = BashFullState::NONE;
break; break;
@@ -79,7 +113,7 @@ std::shared_ptr<void> bash_parse(std::vector<Token> *tokens,
continue; continue;
} }
if (text[i] == '#') { if (text[i] == '#') {
if (i == 0 && len > 4 && text[i + 1] == '!') { if (line_num == 0 && i == 0 && len > 4 && text[i + 1] == '!') {
tokens->push_back({0, len, TokenKind::K_SHEBANG}); tokens->push_back({0, len, TokenKind::K_SHEBANG});
return state; return state;
} }
@@ -91,6 +125,12 @@ std::shared_ptr<void> bash_parse(std::vector<Token> *tokens,
state->full_state->lit.allow_interp = false; state->full_state->lit.allow_interp = false;
tokens->push_back({i, ++i, TokenKind::K_STRING}); tokens->push_back({i, ++i, TokenKind::K_STRING});
continue; continue;
} else if (text[i] == '"') {
state->full_state->in_state = BashFullState::STRING;
state->full_state->lit.delim = "\"";
state->full_state->lit.allow_interp = true;
tokens->push_back({i, ++i, TokenKind::K_STRING});
continue;
} }
i++; i++;
} }

View File

@@ -64,6 +64,10 @@ void Parser::work() {
dirty_lines.push(c_line); dirty_lines.push(c_line);
continue; continue;
} }
if (scroll_max > 50 && c_line < scroll_max - 50) {
dirty_lines.push(c_line);
continue;
}
uint32_t line_count = line_tree.count(); uint32_t line_count = line_tree.count();
lock_data.lock(); lock_data.lock();
std::shared_ptr<void> prev_state = std::shared_ptr<void> prev_state =
@@ -118,13 +122,14 @@ void Parser::work() {
std::static_pointer_cast<CustomState>(prev_state); std::static_pointer_cast<CustomState>(prev_state);
state = state_ptr->state; state = state_ptr->state;
} }
VALUE out_state = VALUE out_state = parse_custom(&line_data->tokens, parser_block, text,
parse_custom(&line_data->tokens, parser_block, text, r_len, state); r_len, state, c_line);
std::shared_ptr<CustomState> out_state_ptr = std::shared_ptr<CustomState> out_state_ptr =
std::make_shared<CustomState>(out_state); std::make_shared<CustomState>(out_state);
new_state = out_state_ptr; new_state = out_state_ptr;
} else { } else {
new_state = parse_func(&line_data->tokens, prev_state, text, r_len); new_state =
parse_func(&line_data->tokens, prev_state, text, r_len, c_line);
} }
line_data->in_state = prev_state; line_data->in_state = prev_state;
line_data->out_state = new_state; line_data->out_state = new_state;
@@ -134,7 +139,8 @@ void Parser::work() {
} }
prev_state = new_state; prev_state = new_state;
c_line++; c_line++;
if (c_line < line_count && c_line > scroll_max + 50) { if (c_line < line_count && c_line > scroll_max + 50 && scroll_max < 50 &&
c_line < scroll_max + 50) {
lock_data.unlock(); lock_data.unlock();
if (lock.owns_lock()) if (lock.owns_lock())
lock.unlock(); lock.unlock();

View File

@@ -288,7 +288,8 @@ bool ruby_state_match(std::shared_ptr<void> state_1,
std::shared_ptr<void> ruby_parse(std::vector<Token> *tokens, std::shared_ptr<void> ruby_parse(std::vector<Token> *tokens,
std::shared_ptr<void> in_state, std::shared_ptr<void> in_state,
const char *text, uint32_t len) { const char *text, uint32_t len,
uint32_t line_num) {
static bool keywords_trie_init = false; static bool keywords_trie_init = false;
static Trie<void> base_keywords_trie; static Trie<void> base_keywords_trie;
static Trie<void> expecting_keywords_trie; static Trie<void> expecting_keywords_trie;
@@ -703,7 +704,7 @@ std::shared_ptr<void> ruby_parse(std::vector<Token> *tokens,
i++; i++;
continue; continue;
} else if (text[i] == '#') { } else if (text[i] == '#') {
if (i == 0 && len > 4 && text[i + 1] == '!') { if (line_num == 0 && i == 0 && len > 4 && text[i + 1] == '!') {
state->full_state->expecting_expr = false; state->full_state->expecting_expr = false;
tokens->push_back({0, len, TokenKind::K_SHEBANG}); tokens->push_back({0, len, TokenKind::K_SHEBANG});
return state; return state;

View File

@@ -44,7 +44,7 @@ std::string get_exe_dir() {
return path.substr(0, path.find_last_of('/')); return path.substr(0, path.find_last_of('/'));
} }
char *load_file(const char *path, uint32_t *out_len) { char *load_file(const char *path, uint32_t *out_len, bool *out_eol) {
std::ifstream file(path, std::ios::binary | std::ios::ate); std::ifstream file(path, std::ios::binary | std::ios::ate);
if (!file.is_open()) if (!file.is_open())
return nullptr; return nullptr;
@@ -64,18 +64,28 @@ char *load_file(const char *path, uint32_t *out_len) {
if (!buf) if (!buf)
return nullptr; return nullptr;
file.read(buf, data_len); file.read(buf, data_len);
if (memchr(buf, '\r', data_len) == nullptr) { bool has_cr = memchr(buf, '\r', data_len) != nullptr;
bool has_lf = memchr(buf, '\n', data_len) != nullptr;
if (!has_cr && !has_lf) {
uint32_t write = data_len; uint32_t write = data_len;
if (write == 0 || buf[write - 1] != '\n')
buf[write++] = '\n'; buf[write++] = '\n';
*out_len = write; *out_len = write;
return buf; return buf;
} }
if (!has_cr) {
*out_eol = true;
uint32_t write = data_len;
if (buf[write - 1] != '\n')
buf[write++] = '\n';
*out_len = write;
return buf;
}
*out_eol = false;
uint32_t write = 0; uint32_t write = 0;
for (uint32_t i = 0; i < data_len; ++i) for (uint32_t i = 0; i < data_len; ++i)
if (buf[i] != '\r') if (buf[i] != '\r')
buf[write++] = buf[i]; buf[write++] = buf[i];
if (write == 0 || buf[write - 1] != '\n') if (buf[write - 1] != '\n')
buf[write++] = '\n'; buf[write++] = '\n';
*out_len = write; *out_len = write;
return buf; return buf;