├── glyph ├── rust-toolchain ├── lsp │ ├── src │ │ ├── action.rs │ │ ├── lib.rs │ │ ├── parse.rs │ │ ├── main.rs │ │ ├── rpc.rs │ │ ├── nonblock.rs │ │ └── client.rs │ └── Cargo.toml ├── fonts │ └── FiraCode.ttf ├── editor │ ├── shaders │ │ ├── highlight.f.glsl │ │ ├── diagnostic.f.glsl │ │ ├── text.f.glsl │ │ ├── cursor.v.glsl │ │ ├── highlight.v.glsl │ │ ├── cursor.f.glsl │ │ ├── text.v.glsl │ │ └── diagnostic.v.glsl │ ├── src │ │ ├── constants.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── theme.rs │ │ ├── gl_program.rs │ │ ├── atlas.rs │ │ ├── vim.rs │ │ ├── window.rs │ │ └── editor.rs │ ├── Cargo.toml │ └── Cargo.lock ├── macros │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── Cargo.toml ├── syntax │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── todo.md ├── sample.txt └── Cargo.lock ├── .gitignore ├── images ├── go.png └── rust.png ├── .gitmodules └── README.md /glyph/rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | **/.vscode 3 | **/.DS_Store -------------------------------------------------------------------------------- /glyph/lsp/src/action.rs: -------------------------------------------------------------------------------- 1 | pub enum Action { 2 | GoToDef, 3 | } 4 | -------------------------------------------------------------------------------- /images/go.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zackradisic/glyph/HEAD/images/go.png -------------------------------------------------------------------------------- /images/rust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zackradisic/glyph/HEAD/images/rust.png -------------------------------------------------------------------------------- /glyph/fonts/FiraCode.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zackradisic/glyph/HEAD/glyph/fonts/FiraCode.ttf -------------------------------------------------------------------------------- /glyph/editor/shaders/highlight.f.glsl: -------------------------------------------------------------------------------- 1 | void main() { 2 | gl_FragColor = vec4(0.05882353, 0.7490196, 1.0, 0.2); 3 | } 4 | -------------------------------------------------------------------------------- /glyph/editor/src/constants.rs: -------------------------------------------------------------------------------- 1 | pub const SCREEN_WIDTH: u32 = 800; 2 | pub const SCREEN_HEIGHT: u32 = 600; 3 | pub const MAX_WIDTH: u32 = 1024; 4 | -------------------------------------------------------------------------------- /glyph/editor/shaders/diagnostic.f.glsl: -------------------------------------------------------------------------------- 1 | varying vec4 v_color; 2 | 3 | void main() { 4 | // gl_FragColor = vec4(0.05882353, 0.7490196, 1.0, 0.2); 5 | gl_FragColor = vec4(v_color.xyz, 0.2); 6 | } 7 | -------------------------------------------------------------------------------- /glyph/macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "macros" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | syn = "1.0.81" 11 | quote = "1.0" 12 | -------------------------------------------------------------------------------- /glyph/editor/shaders/text.f.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 texpos; 2 | varying vec4 v_color; 3 | 4 | uniform sampler2D tex; 5 | 6 | void main(void) { 7 | gl_FragColor = vec4(1, 1, 1, texture2D(tex, texpos).a) * v_color; 8 | } -------------------------------------------------------------------------------- /glyph/editor/shaders/cursor.v.glsl: -------------------------------------------------------------------------------- 1 | attribute vec3 aPos; 2 | attribute float y_translate; 3 | attribute float x_translate; 4 | 5 | void main() { 6 | 7 | gl_Position = vec4(aPos.x + x_translate, aPos.y + (y_translate * -1.0) , aPos.z, 1.0); 8 | } -------------------------------------------------------------------------------- /glyph/lsp/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(thread_id_value)] 2 | pub use lsp_types::{Diagnostic, Position, Range}; 3 | pub use rpc::*; 4 | 5 | pub use client::*; 6 | pub mod action; 7 | mod client; 8 | pub mod nonblock; 9 | mod parse; 10 | mod rpc; 11 | -------------------------------------------------------------------------------- /glyph/editor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "glyph" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | freetype-rs = "0.28.0" 8 | gl = "0.14.0" 9 | once_cell = "1.8.0" 10 | ropey = "1.3.1" 11 | sdl2 = "0.35.1" 12 | syntax = { path = "../syntax" } 13 | lsp = { path = "../lsp" } 14 | -------------------------------------------------------------------------------- /glyph/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "editor", 4 | "macros", 5 | "syntax", 6 | "lsp" 7 | ] 8 | 9 | [patch.crates-io] 10 | tree-sitter = { path = "../deps/tree-sitter/lib", version = ">= 0.19, < 0.21"} 11 | 12 | [patch."https://github.com/tree-sitter/tree-sitter"] 13 | tree-sitter = { path = "../deps/tree-sitter/lib", version = ">= 0.19, < 0.21"} -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/tree-sitter"] 2 | path = deps/tree-sitter 3 | url = https://github.com/tree-sitter/tree-sitter 4 | [submodule "deps/tree-sitter-rust"] 5 | path = deps/tree-sitter-rust 6 | url = https://github.com/tree-sitter/tree-sitter-rust 7 | [submodule "deps/tree-sitter-go"] 8 | path = deps/tree-sitter-go 9 | url = https://github.com/tree-sitter/tree-sitter-go 10 | -------------------------------------------------------------------------------- /glyph/lsp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lsp" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.51" 10 | bytes = "1.1.0" 11 | colored = "2.0.0" 12 | combine = "4.6.2" 13 | jsonrpc-core = "18.0.0" 14 | libc = "0.2.108" 15 | lsp-types = "0.91.1" 16 | serde = "1.0.130" 17 | serde_json = "1.0.72" 18 | macros = { path = "../macros" } 19 | -------------------------------------------------------------------------------- /glyph/editor/shaders/highlight.v.glsl: -------------------------------------------------------------------------------- 1 | attribute vec3 aPos; 2 | attribute float y_translate; 3 | attribute float x_translate; 4 | 5 | void main() { 6 | // mat4 aMat4 = mat4(1.0, 0.0, 0.0, x_translate, 7 | // 0.0, 1.0, 0.0, (y_translate - (8.0/600.0)) * -1.0, 8 | // 0.0, 0.0, 1.0, 0.0, 9 | // 0.0, 0.0, 0.0, 1.0); 10 | 11 | gl_Position = vec4(aPos.x + x_translate, aPos.y + (y_translate * -1.0) , aPos.z, 1.0); 12 | // gl_Position = vec4(aPos.xyz, 1.0) * aMat4; 13 | } -------------------------------------------------------------------------------- /glyph/editor/shaders/cursor.f.glsl: -------------------------------------------------------------------------------- 1 | #define PERIOD 0.5 2 | #define BLINK_THRESHOLD 0.5 3 | 4 | uniform bool is_blinking; 5 | uniform float time; 6 | uniform float last_stroke; 7 | 8 | void main() { 9 | if (is_blinking) { 10 | float t = time - last_stroke; 11 | float threshold = float(t < BLINK_THRESHOLD); 12 | float blink = mod(floor(t / PERIOD), float(2)); 13 | // gl_FragColor = vec4(1.0) * min(threshold + blink, 1.0); 14 | gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); 15 | } else { 16 | gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /glyph/editor/shaders/text.v.glsl: -------------------------------------------------------------------------------- 1 | attribute vec4 coord; 2 | attribute float y_translate; 3 | attribute float x_translate; 4 | attribute vec4 vertex_color; 5 | 6 | varying vec2 texpos; 7 | varying vec4 v_color; 8 | 9 | void main(void) { 10 | mat4 aMat4 = mat4(1.0, 0.0, 0.0, x_translate, 11 | 0.0, 1.0, 0.0, (y_translate - (8.0/600.0)) * -1.0, 12 | 0.0, 0.0, 1.0, 0.0, 13 | 0.0, 0.0, 0.0, 1.0); 14 | 15 | gl_Position = vec4(coord.xy, 0, 1) * aMat4; 16 | texpos = coord.zw; 17 | v_color = vertex_color; 18 | } 19 | -------------------------------------------------------------------------------- /glyph/editor/shaders/diagnostic.v.glsl: -------------------------------------------------------------------------------- 1 | attribute vec3 aPos; 2 | attribute float y_translate; 3 | attribute float x_translate; 4 | attribute vec4 vertex_color; 5 | 6 | varying vec4 v_color; 7 | 8 | void main() { 9 | // mat4 aMat4 = mat4(1.0, 0.0, 0.0, x_translate, 10 | // 0.0, 1.0, 0.0, (y_translate - (8.0/600.0)) * -1.0, 11 | // 0.0, 0.0, 1.0, 0.0, 12 | // 0.0, 0.0, 0.0, 1.0); 13 | 14 | gl_Position = vec4(aPos.x + x_translate, aPos.y + (y_translate * -1.0) , aPos.z, 1.0); 15 | v_color = vertex_color; 16 | // gl_Position = vec4(aPos.xyz, 1.0) * aMat4; 17 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Header image](images/rust.png) 2 | # Glyph 3 | 4 | This is my personal code editor that I am building for fun and to get more familiar with OpenGL. 5 | 6 | Glyph currently supports Vim keybinds, syntax highlighting, and runs at a consistent and smooth ~100 FPS. Eventually Glyph will have LSP support and become my main editor. 7 | 8 | Glyph is designed for my own personal use, as a result I've made engineering and design decisions that suit only my own needs. The code is also meant to be read only by me, but can still be a useful starting point for others who wish to build their own code editor. 9 | 10 | [I'm writing about the engineering process here](https://zackoverflow.dev), where I talk about some of the technical details of building Glyph. 11 | -------------------------------------------------------------------------------- /glyph/syntax/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "syntax" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [patch.crates-io] 9 | tree-sitter = { path = "../../deps/tree-sitter/lib", version = ">= 0.19, < 0.21"} 10 | 11 | [patch."https://github.com/tree-sitter/tree-sitter"] 12 | tree-sitter = { path = "../../deps/tree-sitter/lib", version = ">= 0.19, < 0.21"} 13 | 14 | [dependencies] 15 | tree-sitter = ">= 0.19, < 0.21" 16 | tree-sitter-highlight = {path = "../../deps/tree-sitter/highlight"} 17 | tree-sitter-javascript = "0.20.0" 18 | tree-sitter-go = "0.19.1" 19 | tree-sitter-typescript = "0.20.0" 20 | macros = { path = "../macros" } 21 | once_cell = "1.8.0" 22 | tree-sitter-rust = { path= "../../deps/tree-sitter-rust"} 23 | 24 | -------------------------------------------------------------------------------- /glyph/lsp/src/parse.rs: -------------------------------------------------------------------------------- 1 | use combine::{ 2 | from_str, 3 | parser::{ 4 | combinator::{any_send_partial_state, AnySendPartialState}, 5 | range::{range, take, take_while1}, 6 | }, 7 | skip_many, ParseError, Parser, RangeStream, 8 | }; 9 | 10 | pub fn decode_header<'a, I>( 11 | ) -> impl Parser, PartialState = AnySendPartialState> + 'a 12 | where 13 | I: RangeStream + 'a, 14 | // Necessary due to rust-lang/rust#24159 15 | I::Error: ParseError, 16 | { 17 | let content_length = 18 | range(&b"Content-Length: "[..]).with(from_str(take_while1(|b: u8| b.is_ascii_digit()))); 19 | 20 | any_send_partial_state( 21 | ( 22 | skip_many(range(&b"\r\n"[..])), 23 | content_length, 24 | range(&b"\r\n\r\n"[..]).map(|_| ()), 25 | ) 26 | .then_partial(|&mut (_, message_length, _)| { 27 | take(message_length).map(|bytes: &[u8]| bytes.to_owned()) 28 | }), 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /glyph/lsp/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, path::PathBuf, time::Duration}; 2 | 3 | use lsp::{Client, Either, Message, MessageKind, NotifMessage, Notification, ReqMessage}; 4 | use lsp_types::{DidOpenTextDocumentParams, TextDocumentItem, Url}; 5 | 6 | fn main() { 7 | let client = Client::new( 8 | "/usr/local/bin/rust-analyzer", 9 | "/Users/zackradisic/Desktop/Code/lsp-test-workspace", 10 | ); 11 | 12 | println!("{:?}", client.diagnostics()); 13 | 14 | println!( 15 | "URI: {}", 16 | Url::parse("file://Users/zackradisic/Desktop/Code/lsp-test-workspace").unwrap() 17 | ); 18 | let f = DidOpenTextDocumentParams { 19 | text_document: TextDocumentItem::new( 20 | Url::parse("file:///Users/zackradisic/Desktop/Code/lsp-test-workspace/src/lib.rs") 21 | .unwrap(), 22 | "rust".into(), 23 | 0, 24 | "fn main() { printasfasfln!(\"HELLO!\") }".into(), 25 | ), 26 | }; 27 | let notif = NotifMessage::new( 28 | "textDocument/didOpen", 29 | Some(f), 30 | Notification::TextDocDidOpen, 31 | ); 32 | 33 | std::thread::sleep(Duration::from_millis(3000)); 34 | client.send_message(Box::new(notif)); 35 | 36 | std::thread::sleep(Duration::from_millis(10000)); 37 | } 38 | -------------------------------------------------------------------------------- /glyph/todo.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | 1. ~~Implement MVP editing features~~ 4 | * ~~Visual mode~~ 5 | * ~~Undo/redo~~ 6 | 2. LSP support (at this point Glyph can replace my main editor) 7 | * Diagnostics 8 | * Go to definition 9 | * Code actions 10 | * Suggestions 11 | * Hover buffer for peek definition 12 | 3. Other niceties 13 | * File tree 14 | * Vim surround 15 | * Tab/status line 16 | * Git diff & hunk viewer 17 | 18 | 19 | # Future niceties 20 | 21 | ## Clean up window.rs? 22 | Getting kind of big. 23 | 24 | ## Make x and y translations uniforms 25 | 26 | ## Resizing 27 | Seems simple enough, handle the SDL event and capture the updated size. Use this to 28 | update the global variables. Not a priority for me, not resizing frequently enough. 29 | 30 | ## Syntax Highlighting Optimizations 31 | I imagine implementing LSP support may result in performance degradation. Performance with current syntax highlighting is pretty good. If performance optimizations are necessary here are 32 | a few ideas: 33 | 34 | ### Incremental Parsing 35 | Need to edit `tree-sitter-highlighter` to accept the previous syntax tree as input to allow incremental parsing. 36 | 37 | ### Concurrency 38 | Tree-sitter parsing the syntax tree and collecting the corresponding colors for the highlights can be executed in a 39 | separate thread while we calculate vertices for the text in another. -------------------------------------------------------------------------------- /glyph/sample.txt: -------------------------------------------------------------------------------- 1 | +-------------------------+ 2 | | Infinite monkey theorem | 3 | +-------------------------+ 4 | 5 | The infinite monkey theorem states that a monkey hitting keys at random on a typewriter keyboard for an 6 | infinite amount of time will almost surely type any given text, such as the complete works of William Shakespeare. 7 | 8 | In fact, the monkey would almost surely type every possible finite text an infinite number of times. 9 | 10 | However, the probability that monkeys filling the entire observable universe would type a single complete work, 11 | such as Shakespeare's Hamlet, is so tiny that the chance of it occurring during a period of time hundreds of 12 | thousands of orders of magnitude longer than the age of the universe is extremely low (but technically not zero). 13 | 14 | The theorem can be generalized to state that any sequence of events which has a non-zero probability of happening, 15 | at least as long as it hasn't occurred, will almost certainly eventually occur. 16 | 17 | In this context, "almost surely" is a mathematical term meaning the event happens with probability 1, and 18 | the "monkey" is not an actual monkey, but a metaphor for an abstract device that produces an endless random 19 | sequence of letters and symbols. One of the earliest instances of the use of the "monkey metaphor" is that of 20 | French mathematician Emile Borel in 1913, but the first instance may have been even earlier. 21 | 22 | Variants of the theorem include multiple and even infinitely many typists, and the target text varies between 23 | an entire library and a single sentence. Jorge Luis Borges traced the history of this idea from 24 | Aristotle's On Generation and Corruption and Cicero's De Natura Deorum (On the Nature of the Gods), through 25 | Blaise Pascal and Jonathan Swift, up to modern statements with their iconic simians and typewriters. 26 | 27 | In the early 20th century, Borel and Arthur Eddington used the theorem to illustrate the timescales implicit in 28 | the foundations of statistical mechanics. 29 | -------------------------------------------------------------------------------- /glyph/syntax/src/lib.rs: -------------------------------------------------------------------------------- 1 | use macros::make_highlights; 2 | use once_cell::sync::Lazy; 3 | 4 | pub use tree_sitter; 5 | pub use tree_sitter_highlight; 6 | use tree_sitter_highlight::HighlightConfiguration; 7 | pub use tree_sitter_javascript; 8 | pub use tree_sitter_rust; 9 | 10 | make_highlights!( 11 | "attribute", 12 | "comment", 13 | "constant", 14 | "constructor", 15 | "function.builtin", 16 | "function", 17 | "keyword", 18 | "label", 19 | "operator", 20 | "param", 21 | "property", 22 | "punctuation", 23 | "punctuation.bracket", 24 | "punctuation.delimiter", 25 | "punctuation.special", 26 | "string", 27 | "string.special", 28 | "tag", 29 | "type", 30 | "type.builtin", 31 | "variable", 32 | "variable.builtin", 33 | "variable.parameter" 34 | ); 35 | 36 | pub static TS_CFG: Lazy = Lazy::new(|| { 37 | let mut cfg = HighlightConfiguration::new( 38 | tree_sitter_typescript::language_typescript(), 39 | tree_sitter_typescript::HIGHLIGHT_QUERY, 40 | "", 41 | tree_sitter_typescript::LOCALS_QUERY, 42 | ) 43 | .unwrap(); 44 | 45 | cfg.configure(HIGHLIGHTS); 46 | 47 | cfg 48 | }); 49 | 50 | pub static GO_CFG: Lazy = Lazy::new(|| { 51 | let mut cfg = HighlightConfiguration::new( 52 | tree_sitter_go::language(), 53 | tree_sitter_go::HIGHLIGHT_QUERY, 54 | "", 55 | "", 56 | ) 57 | .unwrap(); 58 | 59 | cfg.configure(HIGHLIGHTS); 60 | 61 | cfg 62 | }); 63 | 64 | pub static JS_CFG: Lazy = Lazy::new(|| { 65 | let mut cfg = HighlightConfiguration::new( 66 | tree_sitter_javascript::language(), 67 | tree_sitter_javascript::HIGHLIGHT_QUERY, 68 | tree_sitter_javascript::INJECTION_QUERY, 69 | tree_sitter_javascript::LOCALS_QUERY, 70 | ) 71 | .unwrap(); 72 | 73 | cfg.configure(HIGHLIGHTS); 74 | 75 | cfg 76 | }); 77 | 78 | pub static RUST_CFG: Lazy = Lazy::new(|| { 79 | let mut cfg = HighlightConfiguration::new( 80 | tree_sitter_rust::language(), 81 | tree_sitter_rust::HIGHLIGHT_QUERY, 82 | "", 83 | "", 84 | ) 85 | .unwrap(); 86 | 87 | cfg.configure(HIGHLIGHTS); 88 | 89 | cfg 90 | }); 91 | -------------------------------------------------------------------------------- /glyph/editor/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(option_result_unwrap_unchecked)] 2 | 3 | use once_cell::sync::Lazy; 4 | 5 | pub use atlas::*; 6 | pub use constants::*; 7 | pub use editor::*; 8 | pub use gl_program::*; 9 | pub use theme::*; 10 | pub use window::*; 11 | 12 | mod atlas; 13 | mod constants; 14 | mod editor; 15 | mod gl_program; 16 | mod theme; 17 | mod vim; 18 | mod window; 19 | #[derive(Debug)] 20 | 21 | pub enum EventResult { 22 | Nothing, 23 | Draw, 24 | Scroll, 25 | Quit, 26 | } 27 | 28 | #[derive(Debug, PartialEq, Clone, Copy)] 29 | pub enum EditorEvent { 30 | Nothing, 31 | DrawText, 32 | DrawCursor, 33 | DrawSelection, 34 | Multiple, 35 | } 36 | 37 | pub enum MoveWordKind { 38 | Next, 39 | Prev, 40 | End, 41 | } 42 | 43 | pub enum WindowFrameKind { 44 | Draw, 45 | Scroll, 46 | } 47 | 48 | pub struct MoveWord { 49 | pub kind: MoveWordKind, 50 | pub skip_punctuation: bool, 51 | } 52 | 53 | pub const ERROR_RED: Color = Color { 54 | r: 215, 55 | g: 0, 56 | b: 21, 57 | a: 255, 58 | }; 59 | 60 | pub const HIGHLIGHT_BLUE: Color = Color { 61 | r: 15, 62 | g: 191, 63 | b: 255, 64 | a: 51, 65 | }; 66 | 67 | #[repr(C)] 68 | #[derive(Copy, Clone)] 69 | pub struct Color { 70 | pub r: u8, 71 | pub g: u8, 72 | pub b: u8, 73 | pub a: u8, 74 | } 75 | 76 | impl Color { 77 | pub fn floats(&self) -> [f32; 4] { 78 | [ 79 | self.r as f32 / 255.0, 80 | self.g as f32 / 255.0, 81 | self.b as f32 / 255.0, 82 | self.a as f32 / 255.0, 83 | ] 84 | } 85 | 86 | fn from_hex(hex: &str) -> Self { 87 | let [r, g, b, a] = Color::hex_to_rgba(hex); 88 | Self { r, g, b, a } 89 | } 90 | 91 | fn hex_to_rgba(hex: &str) -> [u8; 4] { 92 | let mut rgba = [0, 0, 0, 255]; 93 | let hex = hex.trim_start_matches('#'); 94 | for (i, c) in hex 95 | .chars() 96 | .step_by(2) 97 | .zip(hex.chars().skip(1).step_by(2)) 98 | .enumerate() 99 | { 100 | let c = c.0.to_digit(16).unwrap() << 4 | c.1.to_digit(16).unwrap(); 101 | rgba[i] = c as u8; 102 | } 103 | rgba 104 | } 105 | } 106 | 107 | pub type ThemeType = Lazy>; 108 | 109 | pub static TOKYO_NIGHT_STORM: Lazy> = 110 | Lazy::new(|| Box::new(TokyoNightStorm::new())); 111 | 112 | pub static GITHUB: Lazy> = Lazy::new(|| Box::new(GithubDark::new())); 113 | -------------------------------------------------------------------------------- /glyph/macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::{Literal, TokenStream, TokenTree}; 2 | use quote::{format_ident, quote}; 3 | 4 | /// Converts highlight names 5 | #[proc_macro] 6 | pub fn make_highlights(stream: TokenStream) -> TokenStream { 7 | let highlights_raw: Vec = stream 8 | .into_iter() 9 | .filter_map(|tt| match tt { 10 | TokenTree::Literal(lit) => Some(lit), 11 | _ => None, 12 | }) 13 | .collect(); 14 | 15 | let count = highlights_raw.len(); 16 | 17 | let mut array_tree = quote! {}; 18 | let mut enum_tree = quote! {}; 19 | let mut convert_tree = quote! {}; 20 | let mut reverse_convert_tree = quote! {}; 21 | 22 | let mut stream = TokenStream::new(); 23 | for (i, lit) in highlights_raw.into_iter().enumerate() { 24 | let raw = lit.to_string().replace("\"", ""); 25 | let enum_name = format_ident!( 26 | "{}", 27 | raw.split('.') 28 | .map(|s| { 29 | s.chars() 30 | .next() 31 | .iter() 32 | .map(|c| c.to_ascii_uppercase()) 33 | .chain(s.chars().skip(1)) 34 | .collect::() 35 | }) 36 | .collect::() 37 | ); 38 | let i = i as u8; 39 | 40 | array_tree = quote! { 41 | #array_tree 42 | #raw, 43 | }; 44 | enum_tree = quote! { 45 | #enum_tree 46 | #enum_name, 47 | }; 48 | reverse_convert_tree = quote! { 49 | #reverse_convert_tree 50 | #i => Some(Highlight::#enum_name), 51 | }; 52 | convert_tree = quote! { 53 | #convert_tree 54 | Highlight::#enum_name => #i, 55 | }; 56 | } 57 | 58 | let array_tokens = quote! { 59 | pub const HIGHLIGHTS: &[&str; #count] = &[ 60 | #array_tree 61 | ]; 62 | }; 63 | let enum_tokens = quote! { 64 | #[derive(Debug)] 65 | pub enum Highlight { 66 | #enum_tree 67 | } 68 | 69 | impl Highlight { 70 | #[inline] 71 | pub fn from_u8(val: u8) -> Option { 72 | match val { 73 | #reverse_convert_tree 74 | _ => None 75 | } 76 | } 77 | 78 | #[inline] 79 | pub fn to_u8(&self) -> u8 { 80 | match self { 81 | #convert_tree 82 | } 83 | } 84 | } 85 | }; 86 | 87 | stream.extend(TokenStream::from(enum_tokens)); 88 | stream.extend(TokenStream::from(array_tokens)); 89 | stream 90 | } 91 | 92 | #[proc_macro] 93 | pub fn make_request(stream: TokenStream) -> TokenStream { 94 | let mut req_tt = quote! {}; 95 | let mut count: u8 = 0; 96 | 97 | for tt in stream.into_iter() { 98 | if let TokenTree::Ident(ident) = tt { 99 | count += 1; 100 | let ident = format_ident!("{}", ident.to_string()); 101 | req_tt = quote! { 102 | #req_tt 103 | #ident, 104 | } 105 | } 106 | } 107 | 108 | let req_tt = req_tt; 109 | TokenStream::from(quote! { 110 | #[derive(Debug, Clone, Copy, PartialEq)] 111 | pub enum Request { 112 | #req_tt 113 | } 114 | 115 | impl Request { 116 | fn from_u8(val: u8) -> Result { 117 | if val >= #count { 118 | Err(anyhow::anyhow!("Invalid value: {}", val)) 119 | } else { 120 | unsafe { Ok(std::mem::transmute::(val)) } 121 | } 122 | } 123 | } 124 | }) 125 | } 126 | 127 | #[proc_macro] 128 | pub fn make_notification(stream: TokenStream) -> TokenStream { 129 | let mut req_tt = quote! {}; 130 | let mut count: u8 = 0; 131 | 132 | for tt in stream.into_iter() { 133 | if let TokenTree::Ident(ident) = tt { 134 | count += 1; 135 | let ident = format_ident!("{}", ident.to_string()); 136 | req_tt = quote! { 137 | #req_tt 138 | #ident, 139 | } 140 | } 141 | } 142 | 143 | let req_tt = req_tt; 144 | TokenStream::from(quote! { 145 | #[derive(Debug, Clone, Copy, PartialEq)] 146 | pub enum Notification { 147 | #req_tt 148 | } 149 | 150 | impl Notification { 151 | fn from_u8(val: u8) -> Result { 152 | if val >= #count { 153 | Err(anyhow::anyhow!("Invalid value: {}", val)) 154 | } else { 155 | unsafe { Ok(std::mem::transmute::(val)) } 156 | } 157 | } 158 | } 159 | }) 160 | } 161 | -------------------------------------------------------------------------------- /glyph/editor/src/main.rs: -------------------------------------------------------------------------------- 1 | use core::time; 2 | use std::{ 3 | ffi::CStr, 4 | fs, 5 | time::{SystemTime, UNIX_EPOCH}, 6 | }; 7 | 8 | use glyph::{EventResult, Window, WindowFrameKind, GITHUB, SCREEN_HEIGHT, SCREEN_WIDTH}; 9 | use lsp::Client; 10 | 11 | fn main() { 12 | #[cfg(debug_assertions)] 13 | let filepath_idx = 2; 14 | #[cfg(not(debug_assertions))] 15 | let filepath_idx = 1; 16 | 17 | let initial_text = std::env::args() 18 | .nth(filepath_idx) 19 | .map(|path| fs::read_to_string(path).unwrap()); 20 | 21 | let sdl_ctx = sdl2::init().unwrap(); 22 | let video_subsystem = sdl_ctx.video().unwrap(); 23 | let timer = sdl_ctx.timer().unwrap(); 24 | 25 | let mut window = video_subsystem 26 | .window("glyph", SCREEN_WIDTH, SCREEN_HEIGHT) 27 | .resizable() 28 | .allow_highdpi() 29 | .opengl() 30 | .build() 31 | .unwrap(); 32 | 33 | let gl_attr = video_subsystem.gl_attr(); 34 | 35 | gl_attr.set_context_profile(sdl2::video::GLProfile::Compatibility); 36 | gl_attr.set_context_version(2, 0); 37 | 38 | let _gl_ctx = window.gl_create_context().unwrap(); 39 | gl::load_with(|s| video_subsystem.gl_get_proc_address(s) as *const std::os::raw::c_void); 40 | 41 | unsafe { 42 | println!( 43 | "OpenGL version: {}", 44 | CStr::from_ptr(gl::GetString(gl::VERSION) as *const i8) 45 | .to_str() 46 | .unwrap() 47 | ); 48 | } 49 | 50 | // Set background 51 | unsafe { 52 | gl::Enable(gl::BLEND); 53 | gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); 54 | gl::Enable(gl::TEXTURE_2D); 55 | gl::ClearColor(0.0, 0.0, 0.0, 1.0); 56 | gl::Clear(gl::COLOR_BUFFER_BIT); 57 | } 58 | 59 | let lsp_client = Client::new( 60 | "/usr/local/bin/rust-analyzer", 61 | "/Users/zackradisic/Desktop/Code/lsp-test-workspace", 62 | ); 63 | 64 | let mut editor_window = Window::new(initial_text, &GITHUB, &lsp_client); 65 | editor_window.render_text(); 66 | window.gl_swap_window(); 67 | 68 | let mut event_pump = sdl_ctx.event_pump().unwrap(); 69 | video_subsystem.text_input().start(); 70 | 71 | let mut start: u64; 72 | let mut end: u64; 73 | let mut elapsed: u64; 74 | 75 | let mut frames: u128 = 0; 76 | let mut start_now = SystemTime::now() 77 | .duration_since(UNIX_EPOCH) 78 | .expect("Time went backwards!") 79 | .as_millis(); 80 | 81 | let mut start_capturing = false; 82 | 83 | let bg = editor_window.theme().bg().floats(); 84 | 'running: loop { 85 | start = timer.performance_counter(); 86 | unsafe { 87 | gl::Enable(gl::BLEND); 88 | gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); 89 | gl::Enable(gl::TEXTURE_2D); 90 | gl::ClearColor(bg[0], bg[1], bg[2], bg[3]); 91 | gl::Clear(gl::COLOR_BUFFER_BIT); 92 | } 93 | 94 | let mut draw = false; 95 | let mut scroll = false; 96 | for event in event_pump.poll_iter() { 97 | match editor_window.event(event, timer.ticks()) { 98 | EventResult::Quit => break 'running, 99 | EventResult::Draw | EventResult::Nothing => { 100 | draw = true; 101 | } 102 | EventResult::Scroll => { 103 | scroll = true; 104 | } 105 | } 106 | } 107 | 108 | editor_window.queue_diagnostics(); 109 | 110 | frames += 1; 111 | if draw { 112 | editor_window.frame(WindowFrameKind::Draw, timer.ticks()); 113 | window.gl_swap_window(); 114 | } else if scroll { 115 | editor_window.frame(WindowFrameKind::Scroll, timer.ticks()); 116 | window.gl_swap_window(); 117 | } 118 | 119 | end = timer.performance_counter(); 120 | elapsed = ((end - start) / timer.performance_frequency()) * 1000; 121 | 122 | match SystemTime::now().duration_since(UNIX_EPOCH) { 123 | Err(_) => {} 124 | Ok(time) => { 125 | let ms = time.as_millis(); 126 | if start_capturing { 127 | if ms - start_now > 1000 { 128 | let _ = window.set_title(&format!( 129 | "glyph — {:.1$} FPS", 130 | frames as f64 / ((time.as_millis() - start_now) as f64 / 1000.0), 131 | 3 132 | )); 133 | frames = 0; 134 | start_now = ms; 135 | } 136 | } else if ms - start_now > 5000 { 137 | start_capturing = true; 138 | } 139 | } 140 | } 141 | 142 | std::thread::sleep(time::Duration::from_millis(8/*.666*/ - elapsed)); 143 | // std::thread::sleep(time::Duration::from_millis(1000)); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /glyph/editor/src/theme.rs: -------------------------------------------------------------------------------- 1 | use syntax::Highlight; 2 | 3 | use crate::Color; 4 | 5 | pub trait Theme { 6 | fn bg(&self) -> &Color; 7 | fn fg(&self) -> &Color; 8 | fn highlight(&self, highlight: Highlight) -> Option<&Color>; 9 | } 10 | 11 | macro_rules! define_theme { 12 | ($name:ident, $(($color_name:ident $hex:literal)),*) => { 13 | #[derive(Clone)] 14 | pub struct $name { 15 | $( 16 | $color_name: Color, 17 | )* 18 | } 19 | 20 | impl $name { 21 | pub fn new() -> Self { 22 | Self { 23 | $( 24 | $color_name: Color::from_hex($hex), 25 | )* 26 | } 27 | } 28 | } 29 | 30 | impl Default for $name { 31 | fn default() -> Self { 32 | Self::new() 33 | } 34 | } 35 | }; 36 | } 37 | 38 | define_theme!( 39 | TokyoNightStorm, 40 | (fg "#c0caf5"), 41 | (fg_dark "#a9b1d6"), 42 | (bg "#24283b"), 43 | (cyan "#7dcfff"), 44 | (green "#9ece6a"), 45 | (blue "#7aa2f7"), 46 | (blue5 "#89ddff"), 47 | (blue1 "#2ac3de"), 48 | (orange "#ff9e64"), 49 | (red "#f7768e"), 50 | (green1 "#73daca"), 51 | (comment "#565f89"), 52 | (magenta "#bb9af7"), 53 | (yellow "#e0af68") 54 | ); 55 | 56 | impl Theme for TokyoNightStorm { 57 | #[inline] 58 | fn bg(&self) -> &Color { 59 | &self.bg 60 | } 61 | 62 | #[inline] 63 | fn fg(&self) -> &Color { 64 | &self.fg 65 | } 66 | 67 | #[inline] 68 | fn highlight(&self, highlight: Highlight) -> Option<&Color> { 69 | match highlight { 70 | Highlight::Attribute => None, 71 | Highlight::Constant => Some(&self.orange), 72 | Highlight::Constructor => Some(&self.fg_dark), 73 | Highlight::Comment => Some(&self.comment), 74 | Highlight::FunctionBuiltin => None, 75 | Highlight::Function => Some(&self.blue), 76 | Highlight::Keyword => Some(&self.magenta), 77 | Highlight::Label => Some(&self.blue), 78 | Highlight::Operator => Some(&self.blue5), 79 | Highlight::Property => None, /* Some(&self.green1) */ 80 | Highlight::Param => Some(&self.yellow), 81 | Highlight::Punctuation => None, 82 | Highlight::PunctuationBracket => Some(&self.fg_dark), 83 | Highlight::PunctuationDelimiter => Some(&self.blue5), 84 | Highlight::PunctuationSpecial => Some(&self.cyan), 85 | Highlight::String => Some(&self.green), 86 | Highlight::StringSpecial => None, 87 | Highlight::Tag => None, 88 | Highlight::Type => Some(&self.blue1), 89 | Highlight::TypeBuiltin => None, 90 | Highlight::Variable => None, 91 | Highlight::VariableBuiltin => Some(&self.red), 92 | Highlight::VariableParameter => None, 93 | } 94 | } 95 | } 96 | 97 | define_theme!( 98 | GithubDark, 99 | (bg "#0d1117"), 100 | (fg_dark "#4d5566"), 101 | (fg "#c9d1d9"), 102 | (comment "#8b949e"), 103 | (constant "#79c0ff"), 104 | (string "#A5D6FF"), 105 | (func "#d2a8ff"), 106 | (func_param "#c9d1d9"), 107 | (variable "#FFA657"), 108 | (keyword "#ff7b72") 109 | ); 110 | 111 | impl Theme for GithubDark { 112 | #[inline] 113 | fn bg(&self) -> &Color { 114 | &self.bg 115 | } 116 | 117 | #[inline] 118 | fn fg(&self) -> &Color { 119 | &self.fg 120 | } 121 | 122 | #[inline] 123 | fn highlight(&self, highlight: Highlight) -> Option<&Color> { 124 | match highlight { 125 | Highlight::Attribute => None, 126 | Highlight::Constant => Some(&self.constant), 127 | Highlight::Constructor => Some(&self.fg), 128 | Highlight::Comment => Some(&self.comment), 129 | Highlight::FunctionBuiltin => None, 130 | Highlight::Function => Some(&self.func), 131 | Highlight::Keyword => Some(&self.keyword), 132 | // Highlight::Label => Some(&self.blue), 133 | Highlight::Operator => Some(&self.keyword), 134 | Highlight::Property => Some(&self.fg), 135 | Highlight::Punctuation => None, 136 | Highlight::PunctuationBracket => Some(&self.fg_dark), 137 | Highlight::PunctuationDelimiter => Some(&self.keyword), 138 | Highlight::PunctuationSpecial => Some(&self.variable), 139 | Highlight::String => Some(&self.string), 140 | Highlight::StringSpecial => None, 141 | Highlight::Tag => None, 142 | Highlight::Type => Some(&self.variable), 143 | Highlight::TypeBuiltin => None, 144 | Highlight::Variable => Some(&self.variable), 145 | Highlight::VariableBuiltin => Some(&self.keyword), 146 | Highlight::VariableParameter => None, 147 | _ => None, 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /glyph/editor/src/gl_program.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{CStr, CString}; 2 | 3 | use gl::types::GLint; 4 | 5 | pub struct GLProgram { 6 | id: gl::types::GLuint, 7 | } 8 | 9 | impl GLProgram { 10 | pub fn from_shaders(shaders: &[Shader]) -> Result { 11 | let program_id = unsafe { gl::CreateProgram() }; 12 | 13 | for shader in shaders { 14 | unsafe { 15 | gl::AttachShader(program_id, shader.id()); 16 | } 17 | } 18 | 19 | unsafe { 20 | gl::LinkProgram(program_id); 21 | } 22 | 23 | let mut success: gl::types::GLint = 1; 24 | unsafe { 25 | gl::GetProgramiv(program_id, gl::LINK_STATUS, &mut success); 26 | } 27 | 28 | if success == 0 { 29 | let mut len: gl::types::GLint = 0; 30 | unsafe { 31 | gl::GetProgramiv(program_id, gl::INFO_LOG_LENGTH, &mut len); 32 | } 33 | 34 | let error = create_whitespace_cstring_with_len(len as usize); 35 | 36 | unsafe { 37 | gl::GetProgramInfoLog( 38 | program_id, 39 | len, 40 | std::ptr::null_mut(), 41 | error.as_ptr() as *mut gl::types::GLchar, 42 | ); 43 | } 44 | 45 | return Err(error.to_string_lossy().into_owned()); 46 | } 47 | 48 | for shader in shaders { 49 | unsafe { 50 | gl::DetachShader(program_id, shader.id()); 51 | } 52 | } 53 | 54 | Ok(GLProgram { id: program_id }) 55 | } 56 | 57 | pub fn id(&self) -> gl::types::GLuint { 58 | self.id 59 | } 60 | 61 | pub fn set_used(&self) { 62 | unsafe { 63 | gl::UseProgram(self.id); 64 | } 65 | } 66 | 67 | pub fn attrib(&self, name: &str) -> Option { 68 | let loc = unsafe { 69 | let c_name = CString::new(name).unwrap(); 70 | gl::GetAttribLocation(self.id, c_name.as_ptr()) 71 | }; 72 | if loc == -1 { 73 | None 74 | } else { 75 | Some(loc) 76 | } 77 | } 78 | 79 | pub fn uniform(&self, name: &str) -> Option { 80 | let loc = unsafe { 81 | let c_name = CString::new(name).unwrap(); 82 | gl::GetUniformLocation(self.id, c_name.as_ptr()) 83 | }; 84 | if loc == -1 { 85 | None 86 | } else { 87 | Some(loc) 88 | } 89 | } 90 | } 91 | 92 | impl Drop for GLProgram { 93 | fn drop(&mut self) { 94 | unsafe { 95 | gl::DeleteProgram(self.id); 96 | } 97 | } 98 | } 99 | 100 | pub struct Shader { 101 | id: gl::types::GLuint, 102 | } 103 | 104 | impl Shader { 105 | pub fn from_source(source: &CStr, kind: gl::types::GLenum) -> Result { 106 | let id = shader_from_source(source, kind) 107 | .map_err(|s| format!("Failed to load {}: {}", source.to_str().unwrap(), s))?; 108 | 109 | Ok(Shader { id }) 110 | } 111 | 112 | pub fn from_vert_source(source: &CStr) -> Result { 113 | Shader::from_source(source, gl::VERTEX_SHADER) 114 | } 115 | 116 | pub fn from_frag_source(source: &CStr) -> Result { 117 | Shader::from_source(source, gl::FRAGMENT_SHADER) 118 | } 119 | 120 | pub fn id(&self) -> gl::types::GLuint { 121 | self.id 122 | } 123 | } 124 | 125 | impl Drop for Shader { 126 | fn drop(&mut self) { 127 | unsafe { 128 | gl::DeleteShader(self.id); 129 | } 130 | } 131 | } 132 | 133 | fn shader_from_source(source: &CStr, kind: gl::types::GLenum) -> Result { 134 | let id = unsafe { gl::CreateShader(kind) }; 135 | unsafe { 136 | gl::ShaderSource(id, 1, &source.as_ptr(), std::ptr::null()); 137 | gl::CompileShader(id); 138 | } 139 | 140 | let mut success: gl::types::GLint = 1; 141 | unsafe { 142 | gl::GetShaderiv(id, gl::COMPILE_STATUS, &mut success); 143 | } 144 | 145 | if success == 0 { 146 | let mut len: gl::types::GLint = 0; 147 | unsafe { 148 | gl::GetShaderiv(id, gl::INFO_LOG_LENGTH, &mut len); 149 | } 150 | 151 | let error = create_whitespace_cstring_with_len(len as usize); 152 | 153 | unsafe { 154 | gl::GetShaderInfoLog( 155 | id, 156 | len, 157 | std::ptr::null_mut(), 158 | error.as_ptr() as *mut gl::types::GLchar, 159 | ); 160 | } 161 | 162 | return Err(error.to_string_lossy().into_owned()); 163 | } 164 | 165 | Ok(id) 166 | } 167 | 168 | fn create_whitespace_cstring_with_len(len: usize) -> CString { 169 | // allocate buffer of correct size 170 | let mut buffer: Vec = Vec::with_capacity(len + 1); 171 | // fill it with len spaces 172 | buffer.extend([b' '].iter().cycle().take(len)); 173 | // convert buffer to CString 174 | unsafe { CString::from_vec_unchecked(buffer) } 175 | } 176 | -------------------------------------------------------------------------------- /glyph/editor/src/atlas.rs: -------------------------------------------------------------------------------- 1 | use std::ptr::null; 2 | 3 | use gl::types::{GLint, GLuint, GLvoid}; 4 | 5 | use crate::constants::MAX_WIDTH; 6 | 7 | #[derive(Clone)] 8 | pub struct Glyph { 9 | pub advance_x: f32, 10 | pub advance_y: f32, 11 | pub bitmap_w: f32, 12 | pub bitmap_h: f32, 13 | pub bitmap_l: f32, 14 | pub bitmap_t: f32, 15 | pub tx: f32, // x offset of glyph in texture coordinates 16 | pub ty: f32, // y offset of glyph in texture coordinates 17 | } 18 | 19 | impl Default for Glyph { 20 | fn default() -> Self { 21 | Glyph { 22 | advance_x: 0.0, 23 | advance_y: 0.0, 24 | bitmap_w: 0.0, 25 | bitmap_h: 0.0, 26 | bitmap_l: 0.0, 27 | bitmap_t: 0.0, 28 | tx: 0.0, 29 | ty: 0.0, 30 | } 31 | } 32 | } 33 | 34 | pub struct Atlas { 35 | pub tex: GLuint, 36 | pub w: u32, 37 | pub h: u32, 38 | pub max_h: f32, 39 | pub max_w: f32, 40 | pub glyphs: Vec, 41 | } 42 | 43 | const CHAR_END: usize = 128; 44 | 45 | impl Atlas { 46 | pub fn new(font_path: &str, height: u32, uniform_tex: GLint) -> Result { 47 | let ft_lib = freetype::Library::init().unwrap(); 48 | let face = ft_lib.new_face(font_path, 0).unwrap(); 49 | let mut tex: GLuint = 0; 50 | 51 | face.set_pixel_sizes(0, height).map_err(|e| e.to_string())?; 52 | 53 | let g = face.glyph(); 54 | 55 | let mut glyphs: Vec = vec![Default::default(); CHAR_END]; 56 | 57 | let mut roww: u32 = 0; 58 | let mut rowh: u32 = 0; 59 | let mut w: u32 = 0; 60 | let mut h: u32 = 0; 61 | 62 | let mut max_w = 0u32; 63 | 64 | // Find minimum size for a texture holding all visible ASCII characters 65 | for i in 32..CHAR_END { 66 | face.load_char(i, freetype::face::LoadFlag::RENDER) 67 | .map_err(|e| e.to_string())?; 68 | 69 | if roww + g.bitmap().width() as u32 + 1 >= MAX_WIDTH { 70 | w = std::cmp::max(w, roww); 71 | h += rowh; 72 | roww = 0; 73 | rowh = 0; 74 | } 75 | 76 | max_w = std::cmp::max(max_w, g.bitmap().width() as u32); 77 | 78 | roww += g.bitmap().width() as u32 + 1; 79 | rowh = std::cmp::max(rowh, g.bitmap().rows() as u32); 80 | } 81 | let max_h: f32 = rowh as f32; 82 | 83 | w = std::cmp::max(w, roww); 84 | h += rowh; 85 | 86 | unsafe { 87 | // Create a texture that will be used to hold all ASCII glyphs 88 | gl::ActiveTexture(gl::TEXTURE0); 89 | gl::GenTextures(1, &mut tex as *mut GLuint); 90 | gl::BindTexture(gl::TEXTURE_2D, tex); 91 | gl::Uniform1i(uniform_tex, 0); 92 | 93 | gl::TexImage2D( 94 | gl::TEXTURE_2D, 95 | 0, 96 | gl::ALPHA as i32, 97 | w as i32, 98 | h as i32, 99 | 0, 100 | gl::ALPHA, 101 | // gl::RED, 102 | gl::UNSIGNED_BYTE, 103 | null(), 104 | ); 105 | 106 | // We require 1 byte alignment when uploading texture data 107 | gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1); 108 | 109 | // Clamping to edges is important to prevent artifacts when scaling 110 | gl::TexParameteri( 111 | gl::TEXTURE_2D, 112 | gl::TEXTURE_WRAP_S, 113 | gl::CLAMP_TO_EDGE as GLint, 114 | ); 115 | gl::TexParameteri( 116 | gl::TEXTURE_2D, 117 | gl::TEXTURE_WRAP_T, 118 | gl::CLAMP_TO_EDGE as GLint, 119 | ); 120 | 121 | // Linear filtering usually looks best for text 122 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as GLint); 123 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as GLint); 124 | } 125 | 126 | // Paste all glyph bitmaps into the texture, remembering the offset 127 | let mut ox: i32 = 0; 128 | let mut oy: i32 = 0; 129 | 130 | rowh = 0; 131 | 132 | for i in 32..CHAR_END { 133 | face.load_char(i, freetype::face::LoadFlag::RENDER) 134 | .map_err(|e| e.to_string())?; 135 | 136 | if ox + g.bitmap().width() + 1 >= MAX_WIDTH as i32 { 137 | ox = 0; 138 | oy += rowh as i32; 139 | rowh = 0; 140 | } 141 | 142 | unsafe { 143 | gl::TexSubImage2D( 144 | gl::TEXTURE_2D, 145 | 0, 146 | ox as i32, 147 | oy as i32, 148 | g.bitmap().width() as i32, 149 | g.bitmap().rows() as i32, 150 | gl::ALPHA, 151 | gl::UNSIGNED_BYTE, 152 | g.bitmap().buffer().as_ptr() as *const GLvoid, 153 | ); 154 | } 155 | 156 | glyphs[i] = Glyph { 157 | bitmap_w: g.bitmap().width() as f32, 158 | bitmap_h: g.bitmap().rows() as f32, 159 | bitmap_l: g.bitmap_left() as f32, 160 | bitmap_t: g.bitmap_top() as f32, 161 | tx: ox as f32 / w as f32, 162 | ty: oy as f32 / h as f32, 163 | // 1 unit = 1/64 pixels so bitshift 164 | // by 6 to get advance in pixels 165 | advance_x: (g.advance().x >> 6) as f32, 166 | advance_y: (g.advance().y >> 6) as f32, 167 | }; 168 | 169 | rowh = std::cmp::max(rowh, g.bitmap().rows() as u32); 170 | ox += g.bitmap().width() + 1; 171 | } 172 | 173 | // println!( 174 | // "Generated a {} x {} ({} kb) texture atlas\n", 175 | // w, 176 | // h, 177 | // w * h / 1024 178 | // ); 179 | 180 | Ok(Self { 181 | tex, 182 | w, 183 | h, 184 | glyphs, 185 | max_h, 186 | max_w: max_w as f32, 187 | }) 188 | } 189 | } 190 | 191 | impl Drop for Atlas { 192 | fn drop(&mut self) { 193 | unsafe { 194 | gl::DeleteTextures(1, &self.tex); 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /glyph/lsp/src/rpc.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Error, Result}; 2 | 3 | use bytes::{Buf, BytesMut}; 4 | use combine::{easy, parser::combinator::AnySendPartialState, stream::PartialStream}; 5 | use jsonrpc_core::{ 6 | serde_from_str, Notification as JsonNotification, Request as JsonRequest, 7 | Response as JsonResponse, 8 | }; 9 | use macros::{make_notification, make_request}; 10 | use serde::{de::DeserializeOwned, Serialize}; 11 | use serde_json::Value; 12 | 13 | use crate::parse; 14 | 15 | const JSONRPC_VERSION: &str = "v2"; 16 | 17 | pub struct LanguageServerDecoder { 18 | state: AnySendPartialState, 19 | } 20 | 21 | impl LanguageServerDecoder { 22 | pub fn new() -> Self { 23 | Self { 24 | state: Default::default(), 25 | } 26 | } 27 | 28 | pub fn decode(&mut self, buf: &mut BytesMut) -> Result> { 29 | let (opt, removed_len) = combine::stream::decode( 30 | parse::decode_header(), 31 | &mut easy::Stream(PartialStream(&buf[..])), 32 | &mut self.state, 33 | ) 34 | .map_err(|err| { 35 | let err = err 36 | .map_range(|r| { 37 | std::str::from_utf8(r) 38 | .ok() 39 | .map_or_else(|| format!("{:?}", r), |s| s.to_string()) 40 | }) 41 | .map_position(|p| p.translate_position(&buf[..])); 42 | anyhow!("{}\nIn input: `{}`", err, std::str::from_utf8(buf).unwrap()) 43 | })?; 44 | 45 | buf.advance(removed_len); 46 | 47 | match opt { 48 | None => Ok(None), 49 | Some(output) => { 50 | let value = String::from_utf8(output)?; 51 | Ok(Some(value)) 52 | } 53 | } 54 | } 55 | } 56 | 57 | impl Default for LanguageServerDecoder { 58 | fn default() -> Self { 59 | Self::new() 60 | } 61 | } 62 | 63 | pub enum ServerResponse { 64 | Request(JsonRequest), 65 | Response(JsonResponse), 66 | Notification(JsonNotification), 67 | } 68 | 69 | impl LanguageServerDecoder { 70 | pub fn read_response(request_str: &str) -> Result { 71 | let val: Value = serde_from_str(request_str)?; 72 | match val { 73 | Value::Array(vals) => { 74 | todo!() 75 | } 76 | Value::Object(map) => { 77 | if map.contains_key("id") { 78 | if map.contains_key("result") { 79 | let res: JsonResponse = serde_json::from_value(Value::Object(map))?; 80 | Ok(ServerResponse::Response(res)) 81 | } else { 82 | let req: JsonRequest = serde_json::from_value(Value::Object(map))?; 83 | Ok(ServerResponse::Request(req)) 84 | } 85 | } else { 86 | let notif: JsonNotification = serde_json::from_value(Value::Object(map))?; 87 | Ok(ServerResponse::Notification(notif)) 88 | } 89 | } 90 | other => Err(anyhow::anyhow!("Unexpected json value: {:?}", other)), 91 | } 92 | } 93 | } 94 | 95 | #[derive(Clone, Copy)] 96 | pub enum MessageKind { 97 | Request(Request), 98 | Notification(Notification), 99 | Unknown, 100 | } 101 | 102 | pub trait Message { 103 | fn to_bytes(&self) -> Result, Error>; 104 | 105 | // Return ID and request type, used for 106 | // keeping track of responses for deserialization 107 | fn request(&self) -> Option; 108 | 109 | fn set_id(&mut self, id: u8); 110 | } 111 | 112 | #[derive(Serialize)] 113 | pub struct NotifMessage<'a, P> { 114 | jsonrpc: &'static str, 115 | method: &'a str, 116 | params: Option

, 117 | #[serde(skip_serializing)] 118 | pub kind: Notification, 119 | } 120 | 121 | impl<'a, P> Message for NotifMessage<'a, P> 122 | where 123 | P: DeserializeOwned + Serialize, 124 | { 125 | fn to_bytes(&self) -> Result, Error> { 126 | serialize_with_content_length(self) 127 | } 128 | 129 | fn request(&self) -> Option { 130 | None 131 | } 132 | 133 | fn set_id(&mut self, _: u8) {} 134 | } 135 | 136 | impl<'a, P> NotifMessage<'a, P> 137 | where 138 | P: DeserializeOwned + Serialize, 139 | { 140 | pub fn new(method: &'a str, params: Option

, kind: Notification) -> Self { 141 | Self { 142 | jsonrpc: JSONRPC_VERSION, 143 | method, 144 | params, 145 | kind, 146 | } 147 | } 148 | } 149 | 150 | #[derive(Serialize)] 151 | pub struct ReqMessage<'a, P> { 152 | jsonrpc: &'static str, 153 | method: &'a str, 154 | id: u8, 155 | params: P, 156 | #[serde(skip_serializing)] 157 | pub kind: Request, 158 | } 159 | 160 | impl<'a, P> Message for ReqMessage<'a, P> 161 | where 162 | P: DeserializeOwned + Serialize, 163 | { 164 | fn to_bytes(&self) -> Result, Error> { 165 | serialize_with_content_length(self) 166 | } 167 | 168 | fn request(&self) -> Option { 169 | Some(self.kind) 170 | } 171 | 172 | fn set_id(&mut self, id: u8) { 173 | self.id = id; 174 | } 175 | } 176 | 177 | impl<'a, P> ReqMessage<'a, P> 178 | where 179 | P: DeserializeOwned + Serialize, 180 | { 181 | pub fn new(method: &'a str, params: P, kind: Request) -> Self { 182 | Self { 183 | jsonrpc: JSONRPC_VERSION, 184 | id: 1, 185 | method, 186 | params, 187 | kind, 188 | } 189 | } 190 | 191 | pub fn new_with_id(id: u8, method: &'a str, params: P, kind: Request) -> Self { 192 | Self { 193 | jsonrpc: JSONRPC_VERSION, 194 | id, 195 | method, 196 | params, 197 | kind, 198 | } 199 | } 200 | 201 | pub fn to_bytes(&self) -> Result, Error> { 202 | serialize_with_content_length(self) 203 | } 204 | } 205 | 206 | pub fn serialize_with_content_length(val: &P) -> Result, Error> { 207 | let s = serde_json::to_string(&val)?; 208 | Ok( 209 | format!("Content-Length: {}\r\n\r\n{}", s.as_bytes().len(), s) 210 | .as_bytes() 211 | .to_vec(), 212 | ) 213 | } 214 | 215 | make_request!(Initialize, TextDocDefinition); 216 | make_notification!(Initialized, TextDocDidOpen, TextDocDidClose); 217 | -------------------------------------------------------------------------------- /glyph/editor/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "bitflags" 16 | version = "1.3.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 19 | 20 | [[package]] 21 | name = "cc" 22 | version = "1.0.71" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" 25 | 26 | [[package]] 27 | name = "cfg-if" 28 | version = "1.0.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 31 | 32 | [[package]] 33 | name = "cmake" 34 | version = "0.1.46" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "b7b858541263efe664aead4a5209a4ae5c5d2811167d4ed4ee0944503f8d2089" 37 | dependencies = [ 38 | "cc", 39 | ] 40 | 41 | [[package]] 42 | name = "freetype-rs" 43 | version = "0.28.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "8a96ca0b8ae3b1e198988c7609a264b355796356e6898a3b928af10e4ac3bc54" 46 | dependencies = [ 47 | "bitflags", 48 | "freetype-sys", 49 | "libc", 50 | ] 51 | 52 | [[package]] 53 | name = "freetype-sys" 54 | version = "0.14.0" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "b67cedeffcf3d18fd38769bb9323b374f09441934a0f9096b9d33efddf920fc7" 57 | dependencies = [ 58 | "cmake", 59 | "libc", 60 | "pkg-config", 61 | ] 62 | 63 | [[package]] 64 | name = "gl" 65 | version = "0.14.0" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "a94edab108827d67608095e269cf862e60d920f144a5026d3dbcfd8b877fb404" 68 | dependencies = [ 69 | "gl_generator", 70 | ] 71 | 72 | [[package]] 73 | name = "gl_generator" 74 | version = "0.14.0" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" 77 | dependencies = [ 78 | "khronos_api", 79 | "log", 80 | "xml-rs", 81 | ] 82 | 83 | [[package]] 84 | name = "glyph" 85 | version = "0.1.0" 86 | dependencies = [ 87 | "freetype-rs", 88 | "gl", 89 | "once_cell", 90 | "ropey", 91 | "sdl2", 92 | "syn", 93 | "tree-sitter", 94 | "tree-sitter-highlight", 95 | "tree-sitter-javascript", 96 | ] 97 | 98 | [[package]] 99 | name = "khronos_api" 100 | version = "3.1.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" 103 | 104 | [[package]] 105 | name = "lazy_static" 106 | version = "1.4.0" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 109 | 110 | [[package]] 111 | name = "libc" 112 | version = "0.2.107" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" 115 | 116 | [[package]] 117 | name = "log" 118 | version = "0.4.14" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 121 | dependencies = [ 122 | "cfg-if", 123 | ] 124 | 125 | [[package]] 126 | name = "memchr" 127 | version = "2.4.1" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 130 | 131 | [[package]] 132 | name = "once_cell" 133 | version = "1.8.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" 136 | 137 | [[package]] 138 | name = "pkg-config" 139 | version = "0.3.22" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" 142 | 143 | [[package]] 144 | name = "proc-macro2" 145 | version = "1.0.32" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" 148 | dependencies = [ 149 | "unicode-xid", 150 | ] 151 | 152 | [[package]] 153 | name = "quote" 154 | version = "1.0.10" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 157 | dependencies = [ 158 | "proc-macro2", 159 | ] 160 | 161 | [[package]] 162 | name = "regex" 163 | version = "1.5.4" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 166 | dependencies = [ 167 | "aho-corasick", 168 | "memchr", 169 | "regex-syntax", 170 | ] 171 | 172 | [[package]] 173 | name = "regex-syntax" 174 | version = "0.6.25" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 177 | 178 | [[package]] 179 | name = "ropey" 180 | version = "1.3.1" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "9150aff6deb25b20ed110889f070a678bcd1033e46e5e9d6fb1abeab17947f28" 183 | dependencies = [ 184 | "smallvec", 185 | ] 186 | 187 | [[package]] 188 | name = "sdl2" 189 | version = "0.35.1" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "f035f8e87735fa3a8437292be49fe6056450f7cbb13c230b4bcd1bdd7279421f" 192 | dependencies = [ 193 | "bitflags", 194 | "lazy_static", 195 | "libc", 196 | "sdl2-sys", 197 | ] 198 | 199 | [[package]] 200 | name = "sdl2-sys" 201 | version = "0.35.1" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "94cb479353c0603785c834e2307440d83d196bf255f204f7f6741358de8d6a2f" 204 | dependencies = [ 205 | "cfg-if", 206 | "libc", 207 | "version-compare", 208 | ] 209 | 210 | [[package]] 211 | name = "smallvec" 212 | version = "1.7.0" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" 215 | 216 | [[package]] 217 | name = "syn" 218 | version = "1.0.81" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" 221 | dependencies = [ 222 | "proc-macro2", 223 | "quote", 224 | "unicode-xid", 225 | ] 226 | 227 | [[package]] 228 | name = "thiserror" 229 | version = "1.0.30" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" 232 | dependencies = [ 233 | "thiserror-impl", 234 | ] 235 | 236 | [[package]] 237 | name = "thiserror-impl" 238 | version = "1.0.30" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" 241 | dependencies = [ 242 | "proc-macro2", 243 | "quote", 244 | "syn", 245 | ] 246 | 247 | [[package]] 248 | name = "tree-sitter" 249 | version = "0.20.0" 250 | dependencies = [ 251 | "cc", 252 | "regex", 253 | ] 254 | 255 | [[package]] 256 | name = "tree-sitter-highlight" 257 | version = "0.20.0" 258 | dependencies = [ 259 | "regex", 260 | "thiserror", 261 | "tree-sitter", 262 | ] 263 | 264 | [[package]] 265 | name = "tree-sitter-javascript" 266 | version = "0.20.0" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "2490fab08630b2c8943c320f7b63473cbf65511c8d83aec551beb9b4375906ed" 269 | dependencies = [ 270 | "cc", 271 | "tree-sitter", 272 | ] 273 | 274 | [[package]] 275 | name = "unicode-xid" 276 | version = "0.2.2" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 279 | 280 | [[package]] 281 | name = "version-compare" 282 | version = "0.1.0" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73" 285 | 286 | [[package]] 287 | name = "xml-rs" 288 | version = "0.8.4" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" 291 | -------------------------------------------------------------------------------- /glyph/lsp/src/nonblock.rs: -------------------------------------------------------------------------------- 1 | //! This code is adapted from https://github.com/anowell/nonblock-rs 2 | //! Read available data from file descriptors without blocking 3 | //! 4 | //! Useful for nonblocking reads from sockets, named pipes, and child stdout/stderr 5 | //! 6 | //! # Example 7 | //! 8 | //! ```no_run 9 | //! use std::io::Read; 10 | //! use std::process::{Command, Stdio}; 11 | //! use std::time::Duration; 12 | //! use lsp::nonblock::NonBlockingReader; 13 | //! 14 | //! let mut child = Command::new("some-executable") 15 | //! .stdout(Stdio::piped()) 16 | //! .spawn().unwrap(); 17 | //! let stdout = child.stdout.take().unwrap(); 18 | //! let mut noblock_stdout = NonBlockingReader::from_fd(stdout).unwrap(); 19 | //! while !noblock_stdout.is_eof() { 20 | //! let mut buf = String::new(); 21 | //! noblock_stdout.read_available_to_string(&mut buf).unwrap(); 22 | //! std::thread::sleep(Duration::from_secs(5)); 23 | //! } 24 | //! ``` 25 | extern crate libc; 26 | use bytes::{BufMut, BytesMut}; 27 | use libc::{fcntl, F_GETFL, F_SETFL, O_NONBLOCK}; 28 | use std::io::{self, ErrorKind, Read}; 29 | use std::os::unix::io::{AsRawFd, RawFd}; 30 | 31 | /// Simple non-blocking wrapper for reader types that implement AsRawFd 32 | pub struct NonBlockingReader { 33 | eof: bool, 34 | reader: R, 35 | } 36 | 37 | impl NonBlockingReader { 38 | /// Initialize a NonBlockingReader from the reader's file descriptor. 39 | /// 40 | /// The reader will be managed internally, 41 | /// and O_NONBLOCK will be set the file descriptor. 42 | pub fn from_fd(reader: R) -> io::Result> { 43 | let fd = reader.as_raw_fd(); 44 | set_blocking(fd, false)?; 45 | Ok(NonBlockingReader { reader, eof: false }) 46 | } 47 | 48 | /// Consume this NonBlockingReader and return the blocking version 49 | /// of the internally managed reader. 50 | /// 51 | /// This will disable O_NONBLOCK on the file descriptor, 52 | /// and any data read from the NonBlockingReader before calling `into_blocking` 53 | /// will already have been consumed from the reader. 54 | pub fn into_blocking(self) -> io::Result { 55 | let fd = self.reader.as_raw_fd(); 56 | set_blocking(fd, true)?; 57 | Ok(self.reader) 58 | } 59 | 60 | /// Indicates if EOF has been reached for the reader. 61 | /// 62 | /// Currently this defaults to false until one of the `read_available` methods is called, 63 | /// but this may change in the future if I stumble on a compelling way 64 | /// to check for EOF without consuming any of the internal reader. 65 | pub fn is_eof(&self) -> bool { 66 | self.eof 67 | } 68 | 69 | /// Reads any available data from the reader without blocking, placing them into `buf`. 70 | /// 71 | /// If successful, this function will return the total number of bytes read. 72 | /// 0 bytes read may indicate the EOF has been reached or that reading 73 | /// would block because there is not any data immediately available. 74 | /// Call `is_eof()` after this method to determine if EOF was reached. 75 | /// 76 | /// ## Errors 77 | /// 78 | /// If this function encounters an error of the kind `ErrorKind::Interrupted` 79 | /// then the error is ignored and the operation will continue. 80 | /// If it encounters `ErrorKind::WouldBlock`, then this function immediately returns 81 | /// the total number of bytes read so far. 82 | /// 83 | /// If any other read error is encountered then this function immediately returns. 84 | /// Any bytes which have already been read will be appended to buf. 85 | /// 86 | /// ## Examples 87 | /// ```no_run 88 | /// # use std::io::Read; 89 | /// # use std::net::TcpStream; 90 | /// # use std::time::Duration; 91 | /// # use lsp::nonblock::NonBlockingReader; 92 | /// # 93 | /// let client = TcpStream::connect("127.0.0.1:34567").unwrap(); 94 | /// let mut noblock_stdout = NonBlockingReader::from_fd(client).unwrap(); 95 | /// // let mut buf = Vec::new(); 96 | /// // noblock_stdout.read_available(&mut buf).unwrap(); 97 | /// ``` 98 | 99 | pub fn read_available(&mut self, buf: &mut BytesMut) -> io::Result { 100 | let mut buf_len = 0; 101 | loop { 102 | let mut bytes = [0u8; 1024]; 103 | match self.reader.read(&mut bytes[..]) { 104 | // EOF 105 | Ok(0) => { 106 | self.eof = true; 107 | break; 108 | } 109 | // Not EOF, but no more data currently available 110 | Err(ref err) if err.kind() == ErrorKind::WouldBlock => { 111 | self.eof = false; 112 | break; 113 | } 114 | // Ignore interruptions, continue reading 115 | Err(ref err) if err.kind() == ErrorKind::Interrupted => {} 116 | // bytes available 117 | Ok(len) => { 118 | buf_len += len; 119 | buf.put(&bytes[0..(len)]) 120 | } 121 | // IO Error encountered 122 | Err(err) => { 123 | return Err(err); 124 | } 125 | } 126 | } 127 | Ok(buf_len) 128 | } 129 | 130 | /// Reads any available data from the reader without blocking, placing them into `buf`. 131 | /// 132 | /// If successful, this function returns the number of bytes which were read and appended to buf. 133 | /// 134 | /// ## Errors 135 | /// 136 | /// This function inherits all the possible errors of `read_available()`. 137 | /// In the case of errors that occur after successfully reading some data, 138 | /// the successfully read data will still be parsed and appended to `buf`. 139 | /// 140 | /// Additionally, if the read data cannot be parsed as UTF-8, 141 | /// then `buf` will remain unmodified, and this method will return `ErrorKind::InvalidData` 142 | /// with the `FromUtf8Error` containing any data that was read. 143 | /// 144 | /// ## Examples 145 | /// ```no_run 146 | /// # use std::io::Read; 147 | /// # use std::process::{Command, Stdio}; 148 | /// # use std::time::Duration; 149 | /// # use lsp::nonblock::NonBlockingReader; 150 | /// # 151 | /// let mut child = Command::new("foo").stdout(Stdio::piped()).spawn().unwrap(); 152 | /// let stdout = child.stdout.take().unwrap(); 153 | /// let mut noblock_stdout = NonBlockingReader::from_fd(stdout).unwrap(); 154 | /// let mut buf = String::new(); 155 | /// noblock_stdout.read_available_to_string(&mut buf).unwrap(); 156 | /// ``` 157 | /// 158 | /// In theory, since this function only reads immediately available data, 159 | /// There may not be any guarantee that the data immediately available ends 160 | /// on a UTF-8 alignment, so it might be worth a bufferred wrapper 161 | /// that manages the captures a final non-UTF-8 character and prepends it to the next call, 162 | /// but in practice, this has worked as expected. 163 | pub fn read_available_to_string(&mut self, buf: &mut String) -> io::Result { 164 | let mut byte_buf = BytesMut::new(); 165 | let res = self.read_available(&mut byte_buf); 166 | match String::from_utf8(byte_buf.to_vec()) { 167 | Ok(utf8_buf) => { 168 | // append any read data before returning the `read_available` result 169 | buf.push_str(&utf8_buf); 170 | res 171 | } 172 | Err(err) => { 173 | // check for read error before returning the UTF8 Error 174 | let _ = res?; 175 | Err(io::Error::new(ErrorKind::InvalidData, err)) 176 | } 177 | } 178 | } 179 | } 180 | 181 | fn set_blocking(fd: RawFd, blocking: bool) -> io::Result<()> { 182 | let flags = unsafe { fcntl(fd, F_GETFL, 0) }; 183 | if flags < 0 { 184 | return Err(io::Error::last_os_error()); 185 | } 186 | 187 | let flags = if blocking { 188 | flags & !O_NONBLOCK 189 | } else { 190 | flags | O_NONBLOCK 191 | }; 192 | let res = unsafe { fcntl(fd, F_SETFL, flags) }; 193 | if res != 0 { 194 | return Err(io::Error::last_os_error()); 195 | } 196 | 197 | Ok(()) 198 | } 199 | 200 | #[cfg(test)] 201 | mod tests { 202 | use bytes::BytesMut; 203 | 204 | use super::NonBlockingReader; 205 | use std::io::Write; 206 | use std::net::{TcpListener, TcpStream}; 207 | use std::sync::mpsc::channel; 208 | use std::thread; 209 | 210 | #[test] 211 | fn it_works() { 212 | let server = TcpListener::bind("127.0.0.1:34567").unwrap(); 213 | let (tx, rx) = channel(); 214 | 215 | thread::spawn(move || { 216 | let (stream, _) = server.accept().unwrap(); 217 | tx.send(stream).unwrap(); 218 | }); 219 | 220 | let client = TcpStream::connect("127.0.0.1:34567").unwrap(); 221 | let mut stream = rx.recv().unwrap(); 222 | 223 | let mut nonblocking = NonBlockingReader::from_fd(client).unwrap(); 224 | let mut buf = BytesMut::new(); 225 | 226 | assert_eq!(nonblocking.read_available(&mut buf).unwrap(), 0); 227 | assert_eq!(buf.to_vec().as_slice(), b""); 228 | 229 | assert_eq!(stream.write(b"foo").unwrap(), 3); 230 | 231 | let mut read = nonblocking.read_available(&mut buf).unwrap(); 232 | while read == 0 && !nonblocking.is_eof() { 233 | read = nonblocking.read_available(&mut buf).unwrap(); 234 | } 235 | 236 | assert_eq!(read, 3); 237 | assert_eq!(buf.to_vec().as_slice(), b"foo"); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /glyph/lsp/src/client.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use colored::Colorize; 3 | use std::{ 4 | collections::HashMap, 5 | ffi::OsStr, 6 | io::Write, 7 | process::{Child, ChildStdin, ChildStdout, Command, Stdio}, 8 | sync::{ 9 | mpsc::{self, Receiver, Sender}, 10 | Arc, RwLock, 11 | }, 12 | thread::{self}, 13 | }; 14 | 15 | use bytes::BytesMut; 16 | use jsonrpc_core::{ 17 | Failure, Notification as JsonNotification, Output, Params, Response as JsonResponse, Success, 18 | Value, 19 | }; 20 | use lsp_types::{ 21 | ClientCapabilities, Diagnostic, InitializeParams, InitializeResult, InitializedParams, 22 | PublishDiagnosticsParams, Url, WorkspaceClientCapabilities, 23 | }; 24 | use serde::de::DeserializeOwned; 25 | 26 | use crate::{ 27 | nonblock::NonBlockingReader, LanguageServerDecoder, Message, NotifMessage, Notification, 28 | ReqMessage, Request, ServerResponse, 29 | }; 30 | 31 | pub enum Either { 32 | Left(L), 33 | Right(R), 34 | } 35 | 36 | #[derive(Clone)] 37 | pub struct LspSender { 38 | // TODO: Get rid of dynamic dispatch 39 | tx: Sender>, 40 | } 41 | 42 | impl LspSender { 43 | pub fn wrap(tx: Sender>) -> Self { 44 | Self { tx } 45 | } 46 | 47 | pub fn send_message(&self, data: Box) { 48 | self.tx.send(data).unwrap() 49 | } 50 | } 51 | 52 | #[derive(Debug)] 53 | pub struct Diagnostics { 54 | pub diagnostics: Vec, 55 | pub clock: u64, 56 | } 57 | 58 | impl Diagnostics { 59 | pub fn new() -> Self { 60 | Self { 61 | diagnostics: Vec::new(), 62 | clock: 1, 63 | } 64 | } 65 | 66 | pub fn update(&mut self, diagnostics: Vec) { 67 | self.diagnostics = diagnostics; 68 | self.clock += 1; 69 | } 70 | } 71 | 72 | impl Default for Diagnostics { 73 | fn default() -> Self { 74 | Self::new() 75 | } 76 | } 77 | 78 | pub struct Client { 79 | diagnostics: Arc>, 80 | tx: LspSender, 81 | in_thread_id: u64, 82 | out_thread_id: u64, 83 | child: Child, 84 | } 85 | 86 | impl Drop for Client { 87 | fn drop(&mut self) { 88 | unsafe { 89 | libc::pthread_kill(self.in_thread_id as usize, libc::SIGINT); 90 | libc::pthread_kill(self.out_thread_id as usize, libc::SIGINT); 91 | } 92 | self.child.kill().unwrap() 93 | } 94 | } 95 | 96 | impl Client { 97 | pub fn new>(cmd_path: T, cwd: &str) -> Self { 98 | let diagnostics = Arc::new(RwLock::new(Diagnostics::new())); 99 | 100 | let mut cmd = Command::new(cmd_path) 101 | .stdin(Stdio::piped()) 102 | .stdout(Stdio::piped()) 103 | .current_dir(cwd) 104 | .spawn() 105 | .unwrap(); 106 | 107 | let msg = Box::new(ReqMessage::new( 108 | "initialize", 109 | Self::initialize_params(cmd.id(), cwd), 110 | Request::Initialize, 111 | )); 112 | 113 | let (tx, rx) = mpsc::channel::>(); 114 | let tx = LspSender::wrap(tx); 115 | let stdin = cmd.stdin.take().unwrap(); 116 | let stdout = NonBlockingReader::from_fd(cmd.stdout.take().unwrap()).unwrap(); 117 | 118 | let inner = Inner { 119 | diagnostics: diagnostics.clone(), 120 | request_ids: Arc::new(RwLock::new(HashMap::new())), 121 | req_id_counter: Default::default(), 122 | tx: tx.clone(), 123 | }; 124 | let inner_clone = inner.clone(); 125 | 126 | let in_thread_id = thread::spawn(move || inner_clone.stdin(rx, stdin)) 127 | .thread() 128 | .id() 129 | .as_u64() 130 | .get(); 131 | let out_thread_id = thread::spawn(move || inner.stdout(stdout)) 132 | .thread() 133 | .id() 134 | .as_u64() 135 | .get(); 136 | 137 | let s = Self { 138 | diagnostics, 139 | tx, 140 | in_thread_id, 141 | out_thread_id, 142 | child: cmd, 143 | }; 144 | 145 | s.tx.send_message(msg); 146 | 147 | s 148 | } 149 | 150 | pub fn send_message(&self, data: Box) { 151 | self.tx.send_message(data) 152 | } 153 | 154 | fn initialize_params(process_id: u32, cwd: &str) -> InitializeParams { 155 | InitializeParams { 156 | process_id: Some(process_id), 157 | root_uri: Some(Url::parse(&format!("file://{}", cwd)).unwrap()), 158 | initialization_options: None, 159 | capabilities: ClientCapabilities { 160 | workspace: Some(WorkspaceClientCapabilities { 161 | apply_edit: Some(true), 162 | workspace_edit: None, 163 | did_change_configuration: None, 164 | did_change_watched_files: None, 165 | symbol: None, 166 | execute_command: None, 167 | workspace_folders: Some(true), 168 | configuration: Some(true), 169 | semantic_tokens: None, 170 | code_lens: None, 171 | file_operations: None, 172 | }), 173 | text_document: None, 174 | window: None, 175 | general: None, 176 | experimental: None, 177 | }, 178 | trace: None, 179 | workspace_folders: None, 180 | client_info: None, 181 | locale: None, 182 | root_path: None, 183 | } 184 | } 185 | 186 | pub fn diagnostics(&self) -> &Arc> { 187 | &self.diagnostics 188 | } 189 | 190 | pub fn sender(&self) -> &LspSender { 191 | &self.tx 192 | } 193 | } 194 | 195 | #[derive(Clone)] 196 | struct Inner { 197 | diagnostics: Arc>, 198 | request_ids: Arc>>, 199 | req_id_counter: Arc>, 200 | tx: LspSender, 201 | } 202 | 203 | // Functions that execute in threads 204 | impl Inner { 205 | fn stdin(&self, rx: Receiver>, mut stdin: ChildStdin) { 206 | for mut msg in rx { 207 | if let Some(req) = msg.request() { 208 | let mut req_ids = self.request_ids.write().unwrap(); 209 | let mut req_id_counter = self.req_id_counter.write().unwrap(); 210 | *req_id_counter += 1; 211 | msg.set_id(*req_id_counter as u8); 212 | req_ids.insert(*req_id_counter, req); 213 | } 214 | stdin.write_all(&msg.to_bytes().unwrap()).unwrap(); 215 | } 216 | } 217 | 218 | /// Reads LSP JSON RPC messages from stdout, dispatching 219 | /// on the method kind. 220 | fn stdout(&self, mut stdout: NonBlockingReader) { 221 | let mut decoder = LanguageServerDecoder::new(); 222 | let mut buf = BytesMut::new(); 223 | let mut read: usize; 224 | 225 | loop { 226 | read = match stdout.read_available(&mut buf) { 227 | Err(e) => panic!("Error from stdout: {:?}", e), 228 | Ok(r) => r, 229 | }; 230 | 231 | // 0 may indicate EOF or simply that there is no data 232 | // ready for reading yet 233 | if read == 0 && stdout.is_eof() { 234 | panic!("Got unexpected EOF from language server"); 235 | } 236 | 237 | if buf.len() > 5 { 238 | let title = String::from_utf8(buf.to_vec()).unwrap(); 239 | println!("{}", format!("F: {}", title).blue()); 240 | } 241 | 242 | match decoder.decode(&mut buf) { 243 | Ok(Some(s)) => match LanguageServerDecoder::read_response(&s) { 244 | Ok(ServerResponse::Response(res)) => match res { 245 | JsonResponse::Single(output) => self.handle_output(output), 246 | JsonResponse::Batch(outputs) => outputs 247 | .into_iter() 248 | .for_each(|output| self.handle_output(output)), 249 | }, 250 | Ok(ServerResponse::Notification(JsonNotification { 251 | method, params, .. 252 | })) => self.handle_notification(method, params), 253 | Ok(ServerResponse::Request(_)) => { 254 | todo!() 255 | } 256 | Err(e) => { 257 | panic!("Invalid JSON RPC message: {:?} {}", e, s.blue()) 258 | } 259 | }, 260 | Ok(None) => {} 261 | Err(e) => panic!("Error from decoder: {:?}", e), 262 | } 263 | } 264 | } 265 | 266 | fn handle_output(&self, output: Output) { 267 | match output { 268 | Output::Success(Success { 269 | result, 270 | id: jsonrpc_core::Id::Num(id), 271 | .. 272 | }) => self.handle_success(result, id), 273 | Output::Failure(Failure { id, error, .. }) => { 274 | eprintln!("Error: {:?} {:?}", id, error) 275 | } 276 | _ => eprintln!("Invalid output: {:?}", output), 277 | } 278 | } 279 | 280 | fn handle_success(&self, result: serde_json::Value, id: u64) { 281 | if id > u16::MAX as u64 { 282 | panic!("Invalid id: {}", id); 283 | } 284 | let req = { 285 | let request_ids = self.request_ids.read().unwrap(); 286 | request_ids.get(&(id as u16)).cloned() 287 | }; 288 | if let Some(req) = req { 289 | self.handle_request_response(result, req) 290 | } else { 291 | eprintln!("Request response with id ({}) has no mapping", id); 292 | } 293 | } 294 | } 295 | 296 | // Request responses 297 | impl Inner { 298 | fn handle_request_response(&self, result: serde_json::Value, request: Request) { 299 | match request { 300 | Request::Initialize => self.initialized(serde_json::from_value(result).unwrap()), 301 | Request::TextDocDefinition => todo!(), 302 | } 303 | } 304 | 305 | fn initialized(&self, _result: InitializeResult) { 306 | let msg = Box::new(NotifMessage::new( 307 | "initialized", 308 | Some(InitializedParams {}), 309 | Notification::Initialized, 310 | )); 311 | self.tx.send_message(msg); 312 | } 313 | } 314 | 315 | // Notifications 316 | impl Inner { 317 | fn handle_notification(&self, method: String, params: Params) { 318 | match method.as_str() { 319 | "textDocument/publishDiagnostics" => { 320 | self.handle_publish_diagnostics(params).unwrap(); 321 | } 322 | o => { 323 | println!("Unknown notification: {:?}", o); 324 | } 325 | } 326 | } 327 | fn handle_publish_diagnostics(&self, params: Params) -> Result<()> { 328 | let params: PublishDiagnosticsParams = Self::from_value(params)?; 329 | 330 | let mut diagnostics = self.diagnostics.write().unwrap(); 331 | diagnostics.update(params.diagnostics); 332 | 333 | println!("DIAGNOSTICS: {:?}", diagnostics.diagnostics); 334 | 335 | Ok(()) 336 | } 337 | } 338 | 339 | // Utility 340 | impl Inner { 341 | fn from_value(p: Params) -> Result { 342 | let res = match p { 343 | Params::Map(map) => serde_json::from_value::(Value::Object(map)), 344 | Params::Array(val) => serde_json::from_value::(Value::Array(val)), 345 | _ => todo!(), 346 | }; 347 | 348 | res.map_err(|e| anyhow::anyhow!("Serde error: {:?}", e)) 349 | } 350 | } 351 | 352 | pub fn transmute_u16s(bytes: Vec) -> Vec { 353 | // This operation is sound because u16 = 2 u8s 354 | // so there should be no alignment issues. 355 | let ret = unsafe { 356 | Vec::::from_raw_parts(bytes.as_ptr() as *mut u8, bytes.len() * 2, bytes.capacity()) 357 | }; 358 | 359 | bytes.leak(); 360 | 361 | ret 362 | } 363 | 364 | #[cfg(test)] 365 | mod test { 366 | use std::time::Duration; 367 | 368 | use lsp_types::{DidOpenTextDocumentParams, TextDocumentItem, Url}; 369 | 370 | use crate::{transmute_u16s, Client}; 371 | 372 | #[test] 373 | fn it_works() { 374 | let client = Client::new( 375 | "/usr/local/bin/rust-analyzer", 376 | "/Users/zackradisic/Desktop/Code/lsp-test-workspace", 377 | ); 378 | let _tx = &client.tx; 379 | 380 | let _f = DidOpenTextDocumentParams { 381 | text_document: TextDocumentItem::new( 382 | Url::parse("file://main.rs").unwrap(), 383 | "rust".into(), 384 | 0, 385 | "fn main() { println!(\"HELLO!\"); }".into(), 386 | ), 387 | }; 388 | // let notif = Message::new("textDocument/didOpen", f); 389 | 390 | // let json_str = serde_json::to_string(¬if).unwrap(); 391 | // println!("JSON: {}", json_str); 392 | // tx.send(json_str.into_bytes()).unwrap(); 393 | std::thread::sleep(Duration::from_millis(3000)); 394 | } 395 | 396 | #[test] 397 | fn transmute_u16s_works() { 398 | fn run(src: Vec, expect: Vec) { 399 | let out = transmute_u16s(src.clone()); 400 | assert_eq!(out, expect); 401 | assert_eq!( 402 | src, 403 | out.chunks(2) 404 | .into_iter() 405 | .map(|a| u16::from_ne_bytes([a[0], a[1]])) 406 | .collect::>() 407 | ) 408 | } 409 | 410 | run( 411 | vec![1, 2, 3], 412 | vec![ 413 | 1u16 as u8, 414 | (1u16 >> 8) as u8, 415 | 2u16 as u8, 416 | (2u16 >> 8) as u8, 417 | 3u16 as u8, 418 | (3u16 >> 8) as u8, 419 | ], 420 | ); 421 | 422 | run( 423 | vec![69, 420, 4200], 424 | vec![ 425 | 69_u8, 426 | (69 >> 8) as u8, 427 | (420 & 0b11111111) as u8, 428 | (420 >> 8) as u8, 429 | (4200 & 0b11111111) as u8, 430 | (4200 >> 8) as u8, 431 | ], 432 | ); 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /glyph/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.51" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" 19 | 20 | [[package]] 21 | name = "atty" 22 | version = "0.2.14" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 25 | dependencies = [ 26 | "hermit-abi", 27 | "libc", 28 | "winapi", 29 | ] 30 | 31 | [[package]] 32 | name = "bitflags" 33 | version = "1.3.2" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 36 | 37 | [[package]] 38 | name = "bytes" 39 | version = "1.1.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 42 | 43 | [[package]] 44 | name = "cc" 45 | version = "1.0.72" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" 48 | 49 | [[package]] 50 | name = "cfg-if" 51 | version = "1.0.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 54 | 55 | [[package]] 56 | name = "cmake" 57 | version = "0.1.46" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "b7b858541263efe664aead4a5209a4ae5c5d2811167d4ed4ee0944503f8d2089" 60 | dependencies = [ 61 | "cc", 62 | ] 63 | 64 | [[package]] 65 | name = "colored" 66 | version = "2.0.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" 69 | dependencies = [ 70 | "atty", 71 | "lazy_static", 72 | "winapi", 73 | ] 74 | 75 | [[package]] 76 | name = "combine" 77 | version = "4.6.2" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "b2b2f5d0ee456f3928812dfc8c6d9a1d592b98678f6d56db9b0cd2b7bc6c8db5" 80 | dependencies = [ 81 | "bytes", 82 | "memchr", 83 | ] 84 | 85 | [[package]] 86 | name = "form_urlencoded" 87 | version = "1.0.1" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 90 | dependencies = [ 91 | "matches", 92 | "percent-encoding", 93 | ] 94 | 95 | [[package]] 96 | name = "freetype-rs" 97 | version = "0.28.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "8a96ca0b8ae3b1e198988c7609a264b355796356e6898a3b928af10e4ac3bc54" 100 | dependencies = [ 101 | "bitflags", 102 | "freetype-sys", 103 | "libc", 104 | ] 105 | 106 | [[package]] 107 | name = "freetype-sys" 108 | version = "0.14.0" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "b67cedeffcf3d18fd38769bb9323b374f09441934a0f9096b9d33efddf920fc7" 111 | dependencies = [ 112 | "cmake", 113 | "libc", 114 | "pkg-config", 115 | ] 116 | 117 | [[package]] 118 | name = "futures" 119 | version = "0.3.18" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "8cd0210d8c325c245ff06fd95a3b13689a1a276ac8cfa8e8720cb840bfb84b9e" 122 | dependencies = [ 123 | "futures-channel", 124 | "futures-core", 125 | "futures-executor", 126 | "futures-io", 127 | "futures-sink", 128 | "futures-task", 129 | "futures-util", 130 | ] 131 | 132 | [[package]] 133 | name = "futures-channel" 134 | version = "0.3.18" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "7fc8cd39e3dbf865f7340dce6a2d401d24fd37c6fe6c4f0ee0de8bfca2252d27" 137 | dependencies = [ 138 | "futures-core", 139 | "futures-sink", 140 | ] 141 | 142 | [[package]] 143 | name = "futures-core" 144 | version = "0.3.18" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445" 147 | 148 | [[package]] 149 | name = "futures-executor" 150 | version = "0.3.18" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "7b808bf53348a36cab739d7e04755909b9fcaaa69b7d7e588b37b6ec62704c97" 153 | dependencies = [ 154 | "futures-core", 155 | "futures-task", 156 | "futures-util", 157 | ] 158 | 159 | [[package]] 160 | name = "futures-io" 161 | version = "0.3.18" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "e481354db6b5c353246ccf6a728b0c5511d752c08da7260546fc0933869daa11" 164 | 165 | [[package]] 166 | name = "futures-macro" 167 | version = "0.3.18" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "a89f17b21645bc4ed773c69af9c9a0effd4a3f1a3876eadd453469f8854e7fdd" 170 | dependencies = [ 171 | "proc-macro2", 172 | "quote", 173 | "syn", 174 | ] 175 | 176 | [[package]] 177 | name = "futures-sink" 178 | version = "0.3.18" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "996c6442437b62d21a32cd9906f9c41e7dc1e19a9579843fad948696769305af" 181 | 182 | [[package]] 183 | name = "futures-task" 184 | version = "0.3.18" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "dabf1872aaab32c886832f2276d2f5399887e2bd613698a02359e4ea83f8de12" 187 | 188 | [[package]] 189 | name = "futures-util" 190 | version = "0.3.18" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e" 193 | dependencies = [ 194 | "futures-channel", 195 | "futures-core", 196 | "futures-io", 197 | "futures-macro", 198 | "futures-sink", 199 | "futures-task", 200 | "memchr", 201 | "pin-project-lite", 202 | "pin-utils", 203 | "slab", 204 | ] 205 | 206 | [[package]] 207 | name = "gl" 208 | version = "0.14.0" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "a94edab108827d67608095e269cf862e60d920f144a5026d3dbcfd8b877fb404" 211 | dependencies = [ 212 | "gl_generator", 213 | ] 214 | 215 | [[package]] 216 | name = "gl_generator" 217 | version = "0.14.0" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" 220 | dependencies = [ 221 | "khronos_api", 222 | "log", 223 | "xml-rs", 224 | ] 225 | 226 | [[package]] 227 | name = "glyph" 228 | version = "0.1.0" 229 | dependencies = [ 230 | "freetype-rs", 231 | "gl", 232 | "lsp", 233 | "once_cell", 234 | "ropey", 235 | "sdl2", 236 | "syntax", 237 | ] 238 | 239 | [[package]] 240 | name = "hermit-abi" 241 | version = "0.1.19" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 244 | dependencies = [ 245 | "libc", 246 | ] 247 | 248 | [[package]] 249 | name = "idna" 250 | version = "0.2.3" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 253 | dependencies = [ 254 | "matches", 255 | "unicode-bidi", 256 | "unicode-normalization", 257 | ] 258 | 259 | [[package]] 260 | name = "itoa" 261 | version = "0.4.8" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 264 | 265 | [[package]] 266 | name = "jsonrpc-core" 267 | version = "18.0.0" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" 270 | dependencies = [ 271 | "futures", 272 | "futures-executor", 273 | "futures-util", 274 | "log", 275 | "serde", 276 | "serde_derive", 277 | "serde_json", 278 | ] 279 | 280 | [[package]] 281 | name = "khronos_api" 282 | version = "3.1.0" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" 285 | 286 | [[package]] 287 | name = "lazy_static" 288 | version = "1.4.0" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 291 | 292 | [[package]] 293 | name = "libc" 294 | version = "0.2.108" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" 297 | 298 | [[package]] 299 | name = "log" 300 | version = "0.4.14" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 303 | dependencies = [ 304 | "cfg-if", 305 | ] 306 | 307 | [[package]] 308 | name = "lsp" 309 | version = "0.1.0" 310 | dependencies = [ 311 | "anyhow", 312 | "bytes", 313 | "colored", 314 | "combine", 315 | "jsonrpc-core", 316 | "libc", 317 | "lsp-types", 318 | "macros", 319 | "serde", 320 | "serde_json", 321 | ] 322 | 323 | [[package]] 324 | name = "lsp-types" 325 | version = "0.91.1" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "2368312c59425dd133cb9a327afee65be0a633a8ce471d248e2202a48f8f68ae" 328 | dependencies = [ 329 | "bitflags", 330 | "serde", 331 | "serde_json", 332 | "serde_repr", 333 | "url", 334 | ] 335 | 336 | [[package]] 337 | name = "macros" 338 | version = "0.1.0" 339 | dependencies = [ 340 | "quote", 341 | "syn", 342 | ] 343 | 344 | [[package]] 345 | name = "matches" 346 | version = "0.1.9" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 349 | 350 | [[package]] 351 | name = "memchr" 352 | version = "2.4.1" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 355 | 356 | [[package]] 357 | name = "once_cell" 358 | version = "1.8.0" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" 361 | 362 | [[package]] 363 | name = "percent-encoding" 364 | version = "2.1.0" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 367 | 368 | [[package]] 369 | name = "pin-project-lite" 370 | version = "0.2.7" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" 373 | 374 | [[package]] 375 | name = "pin-utils" 376 | version = "0.1.0" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 379 | 380 | [[package]] 381 | name = "pkg-config" 382 | version = "0.3.22" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" 385 | 386 | [[package]] 387 | name = "proc-macro2" 388 | version = "1.0.32" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" 391 | dependencies = [ 392 | "unicode-xid", 393 | ] 394 | 395 | [[package]] 396 | name = "quote" 397 | version = "1.0.10" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 400 | dependencies = [ 401 | "proc-macro2", 402 | ] 403 | 404 | [[package]] 405 | name = "regex" 406 | version = "1.5.4" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 409 | dependencies = [ 410 | "aho-corasick", 411 | "memchr", 412 | "regex-syntax", 413 | ] 414 | 415 | [[package]] 416 | name = "regex-syntax" 417 | version = "0.6.25" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 420 | 421 | [[package]] 422 | name = "ropey" 423 | version = "1.3.1" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "9150aff6deb25b20ed110889f070a678bcd1033e46e5e9d6fb1abeab17947f28" 426 | dependencies = [ 427 | "smallvec", 428 | ] 429 | 430 | [[package]] 431 | name = "ryu" 432 | version = "1.0.6" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568" 435 | 436 | [[package]] 437 | name = "sdl2" 438 | version = "0.35.1" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "f035f8e87735fa3a8437292be49fe6056450f7cbb13c230b4bcd1bdd7279421f" 441 | dependencies = [ 442 | "bitflags", 443 | "lazy_static", 444 | "libc", 445 | "sdl2-sys", 446 | ] 447 | 448 | [[package]] 449 | name = "sdl2-sys" 450 | version = "0.35.1" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "94cb479353c0603785c834e2307440d83d196bf255f204f7f6741358de8d6a2f" 453 | dependencies = [ 454 | "cfg-if", 455 | "libc", 456 | "version-compare", 457 | ] 458 | 459 | [[package]] 460 | name = "serde" 461 | version = "1.0.130" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" 464 | dependencies = [ 465 | "serde_derive", 466 | ] 467 | 468 | [[package]] 469 | name = "serde_derive" 470 | version = "1.0.130" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" 473 | dependencies = [ 474 | "proc-macro2", 475 | "quote", 476 | "syn", 477 | ] 478 | 479 | [[package]] 480 | name = "serde_json" 481 | version = "1.0.72" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527" 484 | dependencies = [ 485 | "itoa", 486 | "ryu", 487 | "serde", 488 | ] 489 | 490 | [[package]] 491 | name = "serde_repr" 492 | version = "0.1.7" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" 495 | dependencies = [ 496 | "proc-macro2", 497 | "quote", 498 | "syn", 499 | ] 500 | 501 | [[package]] 502 | name = "slab" 503 | version = "0.4.5" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" 506 | 507 | [[package]] 508 | name = "smallvec" 509 | version = "1.7.0" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" 512 | 513 | [[package]] 514 | name = "syn" 515 | version = "1.0.81" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" 518 | dependencies = [ 519 | "proc-macro2", 520 | "quote", 521 | "unicode-xid", 522 | ] 523 | 524 | [[package]] 525 | name = "syntax" 526 | version = "0.1.0" 527 | dependencies = [ 528 | "macros", 529 | "once_cell", 530 | "tree-sitter", 531 | "tree-sitter-go", 532 | "tree-sitter-highlight", 533 | "tree-sitter-javascript", 534 | "tree-sitter-rust", 535 | "tree-sitter-typescript", 536 | ] 537 | 538 | [[package]] 539 | name = "thiserror" 540 | version = "1.0.30" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" 543 | dependencies = [ 544 | "thiserror-impl", 545 | ] 546 | 547 | [[package]] 548 | name = "thiserror-impl" 549 | version = "1.0.30" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" 552 | dependencies = [ 553 | "proc-macro2", 554 | "quote", 555 | "syn", 556 | ] 557 | 558 | [[package]] 559 | name = "tinyvec" 560 | version = "1.5.1" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" 563 | dependencies = [ 564 | "tinyvec_macros", 565 | ] 566 | 567 | [[package]] 568 | name = "tinyvec_macros" 569 | version = "0.1.0" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 572 | 573 | [[package]] 574 | name = "tree-sitter" 575 | version = "0.20.0" 576 | dependencies = [ 577 | "cc", 578 | "regex", 579 | ] 580 | 581 | [[package]] 582 | name = "tree-sitter-go" 583 | version = "0.19.1" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "71967701c8214be4aa77e0260e98361e6fd71ceec1d9d03abb37a22c9f60d0ff" 586 | dependencies = [ 587 | "cc", 588 | "tree-sitter", 589 | ] 590 | 591 | [[package]] 592 | name = "tree-sitter-highlight" 593 | version = "0.20.0" 594 | dependencies = [ 595 | "regex", 596 | "thiserror", 597 | "tree-sitter", 598 | ] 599 | 600 | [[package]] 601 | name = "tree-sitter-javascript" 602 | version = "0.20.0" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "2490fab08630b2c8943c320f7b63473cbf65511c8d83aec551beb9b4375906ed" 605 | dependencies = [ 606 | "cc", 607 | "tree-sitter", 608 | ] 609 | 610 | [[package]] 611 | name = "tree-sitter-rust" 612 | version = "0.19.1" 613 | dependencies = [ 614 | "cc", 615 | "tree-sitter", 616 | ] 617 | 618 | [[package]] 619 | name = "tree-sitter-typescript" 620 | version = "0.20.0" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "8935efd97c92067c9b2b6d7acb647607590996ba80f3a7be09a197f9c1fdab73" 623 | dependencies = [ 624 | "cc", 625 | "tree-sitter", 626 | ] 627 | 628 | [[package]] 629 | name = "unicode-bidi" 630 | version = "0.3.7" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" 633 | 634 | [[package]] 635 | name = "unicode-normalization" 636 | version = "0.1.19" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" 639 | dependencies = [ 640 | "tinyvec", 641 | ] 642 | 643 | [[package]] 644 | name = "unicode-xid" 645 | version = "0.2.2" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 648 | 649 | [[package]] 650 | name = "url" 651 | version = "2.2.2" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 654 | dependencies = [ 655 | "form_urlencoded", 656 | "idna", 657 | "matches", 658 | "percent-encoding", 659 | "serde", 660 | ] 661 | 662 | [[package]] 663 | name = "version-compare" 664 | version = "0.1.0" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73" 667 | 668 | [[package]] 669 | name = "winapi" 670 | version = "0.3.9" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 673 | dependencies = [ 674 | "winapi-i686-pc-windows-gnu", 675 | "winapi-x86_64-pc-windows-gnu", 676 | ] 677 | 678 | [[package]] 679 | name = "winapi-i686-pc-windows-gnu" 680 | version = "0.4.0" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 683 | 684 | [[package]] 685 | name = "winapi-x86_64-pc-windows-gnu" 686 | version = "0.4.0" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 689 | 690 | [[package]] 691 | name = "xml-rs" 692 | version = "0.8.4" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" 695 | -------------------------------------------------------------------------------- /glyph/editor/src/vim.rs: -------------------------------------------------------------------------------- 1 | use sdl2::{event::Event, keyboard::Keycode}; 2 | 3 | use crate::Mode; 4 | 5 | #[derive(Debug, PartialEq)] 6 | pub enum Cmd { 7 | Repeat { 8 | count: u16, 9 | cmd: Box, 10 | }, 11 | /// None is only valid in visual mode, means to apply 12 | /// to the selection 13 | Delete(Option), 14 | Change(Option), 15 | Yank(Option), 16 | 17 | Move(Move), 18 | SwitchMove(Move), 19 | SwitchMode(Mode), 20 | NewLine(NewLine), 21 | Undo, 22 | Redo, 23 | } 24 | 25 | impl Cmd { 26 | #[inline] 27 | pub fn is_movement(&self) -> bool { 28 | match self { 29 | Cmd::Move(_) => true, 30 | Cmd::Repeat { cmd, .. } => cmd.is_movement(), 31 | _ => false, 32 | } 33 | } 34 | } 35 | 36 | #[derive(Debug, PartialEq)] 37 | pub struct NewLine { 38 | pub up: bool, 39 | pub switch_mode: bool, 40 | } 41 | 42 | #[derive(Clone, Debug, PartialEq)] 43 | pub enum Move { 44 | Repeat { count: u16, mv: Box }, 45 | Left, 46 | Right, 47 | Up, 48 | Down, 49 | LineStart, 50 | LineEnd, 51 | // Bool is true if find in reverse 52 | Find(char, bool), 53 | ParagraphBegin, 54 | ParagraphEnd, 55 | Start, 56 | End, 57 | Word(bool), 58 | BeginningWord(bool), 59 | EndWord(bool), 60 | } 61 | 62 | #[derive(PartialEq, Debug, Clone)] 63 | pub enum Token { 64 | Start, 65 | End, 66 | Delete, 67 | Change, 68 | Yank, 69 | Find, 70 | FindReverse, 71 | Left, 72 | Right, 73 | Up, 74 | Undo, 75 | Redo, 76 | Down, 77 | LineStart, 78 | LineEnd, 79 | ParagraphBegin, 80 | ParagraphEnd, 81 | Number(u16), 82 | Char(char), 83 | Word(bool), 84 | BeginningWord(bool), 85 | EndWord(bool), 86 | } 87 | 88 | #[derive(Debug, PartialEq)] 89 | enum FailAction { 90 | Continue, 91 | // Reset stack 92 | Reset, 93 | } 94 | 95 | type Result = core::result::Result; 96 | 97 | fn digits_to_num(digits: Vec) -> u16 { 98 | let mut num = 0; 99 | for digit in digits { 100 | num = num * 10 + digit as u16; 101 | } 102 | num 103 | } 104 | 105 | pub struct Vim { 106 | cmd_stack: Vec, 107 | parsing_find: bool, 108 | parsing_start: bool, 109 | parse_idx: usize, 110 | mode: Mode, 111 | } 112 | 113 | impl Vim { 114 | pub fn new() -> Self { 115 | Self { 116 | cmd_stack: Vec::new(), 117 | parsing_find: false, 118 | parsing_start: false, 119 | parse_idx: 0, 120 | mode: Mode::Normal, 121 | } 122 | } 123 | 124 | pub fn event(&mut self, event: Event) -> Option { 125 | match event { 126 | Event::KeyDown { 127 | keycode: Some(key), .. 128 | } => match key { 129 | Keycode::Escape => { 130 | self.reset(); 131 | } 132 | Keycode::Num0 | Keycode::Kp0 => { 133 | match self.cmd_stack.last().cloned() { 134 | Some(Token::Number(n)) => { 135 | // self.cmd_stack.push(Token::Number(n * 10)); 136 | } 137 | _ => {} /* self.cmd_stack.push(Token::LineStart) */ 138 | }; 139 | } 140 | _ => {} 141 | }, 142 | Event::TextInput { text, .. } => { 143 | if self.parsing_start { 144 | if text.as_str() == "g" { 145 | self.cmd_stack.push(Token::Start); 146 | self.parsing_start = false; 147 | } else { 148 | self.reset(); 149 | } 150 | } else if self.parsing_find { 151 | self.cmd_stack 152 | .push(Token::Char(text.chars().next().unwrap())); 153 | self.parsing_find = false; 154 | } else { 155 | match text.as_str() { 156 | // Visual mode 157 | "v" => { 158 | self.reset(); 159 | return Some(Cmd::SwitchMode(Mode::Visual)); 160 | } 161 | // Basic movement 162 | "h" => self.cmd_stack.push(Token::Left), 163 | "j" => self.cmd_stack.push(Token::Down), 164 | "k" => self.cmd_stack.push(Token::Up), 165 | "l" => self.cmd_stack.push(Token::Right), 166 | // Ops 167 | "d" => self.cmd_stack.push(Token::Delete), 168 | "c" => self.cmd_stack.push(Token::Change), 169 | "y" => self.cmd_stack.push(Token::Yank), 170 | "u" => self.cmd_stack.push(Token::Undo), 171 | "r" => self.cmd_stack.push(Token::Redo), 172 | // Movement 173 | "F" => { 174 | self.cmd_stack.push(Token::FindReverse); 175 | self.parsing_find = true 176 | } 177 | "f" => { 178 | self.cmd_stack.push(Token::Find); 179 | self.parsing_find = true 180 | } 181 | "g" => { 182 | self.parsing_start = true; 183 | } 184 | "G" => self.cmd_stack.push(Token::End), 185 | "A" => { 186 | self.reset(); 187 | return Some(Cmd::SwitchMove(Move::LineEnd)); 188 | } 189 | "a" => { 190 | self.reset(); 191 | return Some(Cmd::SwitchMove(Move::Right)); 192 | } 193 | "O" => { 194 | self.reset(); 195 | return Some(Cmd::NewLine(NewLine { 196 | up: true, 197 | switch_mode: true, 198 | })); 199 | } 200 | "o" => { 201 | self.reset(); 202 | return Some(Cmd::NewLine(NewLine { 203 | up: false, 204 | switch_mode: true, 205 | })); 206 | } 207 | "i" => { 208 | self.reset(); 209 | return Some(Cmd::SwitchMode(Mode::Insert)); 210 | } 211 | "$" => self.cmd_stack.push(Token::LineEnd), 212 | "{" => self.cmd_stack.push(Token::ParagraphBegin), 213 | "}" => self.cmd_stack.push(Token::ParagraphEnd), 214 | "W" => self.cmd_stack.push(Token::Word(true)), 215 | "w" => self.cmd_stack.push(Token::Word(false)), 216 | "B" => self.cmd_stack.push(Token::BeginningWord(true)), 217 | "b" => self.cmd_stack.push(Token::BeginningWord(false)), 218 | "E" => self.cmd_stack.push(Token::EndWord(true)), 219 | "e" => self.cmd_stack.push(Token::EndWord(false)), 220 | r => { 221 | let c = r.chars().next().unwrap(); 222 | if c.is_numeric() { 223 | match self.cmd_stack.last() { 224 | Some(Token::Number(val)) => { 225 | let num = digits_to_num(vec![ 226 | *val as u16, 227 | c.to_digit(10).unwrap() as u16, 228 | ]); 229 | self.cmd_stack.pop(); 230 | self.cmd_stack.push(Token::Number(num)); 231 | } 232 | _ => { 233 | if c == '0' { 234 | self.cmd_stack.push(Token::LineStart); 235 | } else { 236 | self.cmd_stack 237 | .push(Token::Number(c.to_digit(10).unwrap() as u16)) 238 | } 239 | } 240 | } 241 | } else { 242 | self.reset(); 243 | } 244 | } 245 | } 246 | } 247 | } 248 | _ => {} 249 | }; 250 | 251 | if self.cmd_stack.is_empty() || self.parsing_start { 252 | return None; 253 | } 254 | 255 | let result = match self.parse_cmd() { 256 | Ok(cmd) => { 257 | self.reset(); 258 | Some(cmd) 259 | } 260 | Err(FailAction::Reset) => { 261 | self.reset(); 262 | None 263 | } 264 | Err(FailAction::Continue) => None, 265 | }; 266 | 267 | self.parse_idx = 0; 268 | 269 | result 270 | } 271 | } 272 | 273 | // Parsing 274 | impl Vim { 275 | fn parse_cmd(&mut self) -> Result { 276 | match self.mode { 277 | Mode::Normal => self.parse_cmd_normal_mode(), 278 | Mode::Visual => self.parse_cmd_visual_mode(), 279 | _ => unreachable!("Shouldn't handle cmds in insert mode"), 280 | } 281 | } 282 | 283 | /// Delete/Chank/Yank and movements are only valid in visual mode 284 | fn parse_cmd_visual_mode(&mut self) -> Result { 285 | match self.next().cloned() { 286 | None => Err(FailAction::Continue), 287 | Some(Token::Delete) => Ok(Cmd::Delete(None)), 288 | Some(Token::Change) => Ok(Cmd::Change(None)), 289 | Some(Token::Yank) => Ok(Cmd::Yank(None)), 290 | Some(Token::Number(count)) => { 291 | match self.parse_cmd()? { 292 | Cmd::Delete(None) => Ok(Cmd::Delete(None)), 293 | Cmd::Change(None) => Ok(Cmd::Change(None)), 294 | Cmd::Yank(None) => Ok(Cmd::Yank(None)), 295 | Cmd::Move(m) => Ok(Cmd::Repeat { 296 | count, 297 | cmd: Box::new(Cmd::Move(m)), 298 | }), 299 | _ => { 300 | // Only delete/yank/change or movements are valid repeated 301 | // cmds in visual mode 302 | Err(FailAction::Reset) 303 | } 304 | } 305 | } 306 | _ => { 307 | self.back(); 308 | Ok(Cmd::Move(self.parse_move()?)) 309 | } 310 | } 311 | } 312 | 313 | fn parse_cmd_normal_mode(&mut self) -> Result { 314 | match self.next().cloned() { 315 | None => Err(FailAction::Continue), 316 | Some(Token::Undo) => Ok(Cmd::Undo), 317 | Some(Token::Redo) => Ok(Cmd::Redo), 318 | Some(Token::Delete) => self.parse_op(Token::Delete).map(Cmd::Delete), 319 | Some(Token::Change) => self.parse_op(Token::Change).map(Cmd::Change), 320 | Some(Token::Yank) => self.parse_op(Token::Yank).map(Cmd::Yank), 321 | Some(Token::Number(count)) => self.parse_cmd().map(|cmd| Cmd::Repeat { 322 | count, 323 | cmd: Box::new(cmd), 324 | }), 325 | _ => { 326 | self.back(); 327 | Ok(Cmd::Move(self.parse_move()?)) 328 | } 329 | } 330 | } 331 | 332 | fn parse_op(&mut self, kind: Token) -> Result> { 333 | match self.next() { 334 | Some(tok) if tok.eq(&kind) => Ok(None), 335 | Some(_) => { 336 | self.back(); 337 | Ok(Some(self.parse_move()?)) 338 | } 339 | None => Err(FailAction::Continue), 340 | } 341 | } 342 | 343 | fn parse_move(&mut self) -> Result { 344 | match self.next().cloned() { 345 | None => Err(FailAction::Continue), 346 | Some(Token::Up) => Ok(Move::Up), 347 | Some(Token::Down) => Ok(Move::Down), 348 | Some(Token::Left) => Ok(Move::Left), 349 | Some(Token::Right) => Ok(Move::Right), 350 | Some(Token::LineEnd) => Ok(Move::LineEnd), 351 | Some(Token::LineStart) => Ok(Move::LineStart), 352 | Some(Token::ParagraphBegin) => Ok(Move::ParagraphBegin), 353 | Some(Token::ParagraphEnd) => Ok(Move::ParagraphEnd), 354 | Some(Token::Start) => Ok(Move::Start), 355 | Some(Token::End) => Ok(Move::End), 356 | Some(Token::Word(skip_punctuation)) => Ok(Move::Word(skip_punctuation)), 357 | Some(Token::BeginningWord(skip_punctuation)) => { 358 | Ok(Move::BeginningWord(skip_punctuation)) 359 | } 360 | Some(Token::EndWord(skip_punctuation)) => Ok(Move::EndWord(skip_punctuation)), 361 | Some(Token::Find) => match self.next() { 362 | Some(Token::Char(char)) => Ok(Move::Find(*char, false)), 363 | Some(_) => Err(FailAction::Reset), 364 | None => Err(FailAction::Continue), 365 | }, 366 | Some(Token::FindReverse) => match self.next() { 367 | Some(Token::Char(char)) => Ok(Move::Find(*char, true)), 368 | Some(_) => Err(FailAction::Reset), 369 | None => Err(FailAction::Continue), 370 | }, 371 | Some(Token::Number(count)) => self.parse_move().map(|mv| Move::Repeat { 372 | count, 373 | mv: Box::new(mv), 374 | }), 375 | _ => Err(FailAction::Reset), 376 | } 377 | } 378 | 379 | #[inline] 380 | fn reset(&mut self) { 381 | self.parsing_start = false; 382 | self.parsing_find = false; 383 | self.parse_idx = 0; 384 | self.cmd_stack.clear(); 385 | } 386 | 387 | #[inline] 388 | fn next(&mut self) -> Option<&Token> { 389 | if self.parse_idx >= self.cmd_stack.len() { 390 | return None; 391 | } 392 | let result = &self.cmd_stack[self.parse_idx]; 393 | self.parse_idx += 1; 394 | Some(result) 395 | } 396 | 397 | #[inline] 398 | fn back(&mut self) { 399 | if self.parse_idx > 0 { 400 | self.parse_idx -= 1; 401 | } 402 | } 403 | } 404 | 405 | // Utility 406 | impl Vim { 407 | #[inline] 408 | pub fn set_mode(&mut self, mode: Mode) { 409 | self.mode = mode; 410 | } 411 | } 412 | 413 | #[cfg(test)] 414 | mod tests { 415 | use sdl2::keyboard::Mod; 416 | 417 | use super::*; 418 | 419 | fn keydown(code: Keycode) -> Event { 420 | Event::KeyDown { 421 | timestamp: 0, 422 | window_id: 0, 423 | keycode: Some(code), 424 | scancode: None, 425 | keymod: Mod::NOMOD, 426 | repeat: false, 427 | } 428 | } 429 | 430 | fn text_input(input: &str) -> Event { 431 | Event::TextInput { 432 | timestamp: 0, 433 | window_id: 0, 434 | text: input.to_string(), 435 | } 436 | } 437 | 438 | fn is_reset(vim: &mut Vim) { 439 | assert!(!vim.parsing_find); 440 | assert_eq!(vim.parse_idx, 0); 441 | assert_eq!(vim.cmd_stack.len(), 0); 442 | } 443 | 444 | #[cfg(test)] 445 | mod ops { 446 | use super::*; 447 | 448 | #[test] 449 | fn basic_ops() { 450 | let mut vim = Vim::new(); 451 | let basic = vec![Keycode::H, Keycode::J, Keycode::K, Keycode::L]; 452 | let basic_moves = vec![Move::Left, Move::Down, Move::Up, Move::Right]; 453 | let basic_input = vec!["d", "c", "y"]; 454 | 455 | for (i, input) in basic_input.into_iter().enumerate() { 456 | assert_eq!(vim.event(text_input(input)), None); 457 | assert_eq!( 458 | vim.event(keydown(basic[i])), 459 | Some(match input { 460 | "d" => Cmd::Delete(Some(basic_moves[i].clone())), 461 | "c" => Cmd::Change(Some(basic_moves[i].clone())), 462 | "y" => Cmd::Yank(Some(basic_moves[i].clone())), 463 | _ => unreachable!(), 464 | }) 465 | ); 466 | is_reset(&mut vim); 467 | } 468 | } 469 | 470 | #[test] 471 | fn repeated_ops() { 472 | let mut vim = Vim::new(); 473 | let counts = vec![3, 4, 2]; 474 | let basic = vec![Keycode::H, Keycode::J, Keycode::K, Keycode::L]; 475 | let basic_moves = vec![Move::Left, Move::Down, Move::Up, Move::Right]; 476 | let basic_input = vec!["d", "c", "y"]; 477 | 478 | for (i, input) in basic_input.into_iter().enumerate() { 479 | assert_eq!(vim.event(text_input(&counts[i].to_string())), None); 480 | assert_eq!(vim.event(text_input(input)), None); 481 | let repeated = Cmd::Repeat { 482 | count: counts[i], 483 | cmd: Box::new(match input { 484 | "d" => Cmd::Delete(Some(basic_moves[i].clone())), 485 | "c" => Cmd::Change(Some(basic_moves[i].clone())), 486 | "y" => Cmd::Yank(Some(basic_moves[i].clone())), 487 | _ => unreachable!(), 488 | }), 489 | }; 490 | assert_eq!(vim.event(keydown(basic[i])), Some(repeated)); 491 | is_reset(&mut vim); 492 | } 493 | } 494 | 495 | #[test] 496 | fn complex() { 497 | let mut vim = Vim::new(); 498 | assert_eq!(vim.event(text_input("2")), None); 499 | assert_eq!(vim.event(text_input("d")), None); 500 | assert_eq!(vim.event(text_input("2")), None); 501 | assert_eq!(vim.event(text_input("f")), None); 502 | assert_eq!( 503 | vim.event(text_input("e")), 504 | Some(Cmd::Repeat { 505 | count: 2, 506 | cmd: Box::new(Cmd::Delete(Some(Move::Repeat { 507 | count: 2, 508 | mv: Box::new(Move::Find('e', false)) 509 | }))) 510 | }) 511 | ); 512 | } 513 | } 514 | 515 | #[cfg(test)] 516 | mod movement { 517 | use super::*; 518 | 519 | #[test] 520 | fn basic_movement() { 521 | let mut vim = Vim::new(); 522 | assert_eq!(vim.event(keydown(Keycode::H)), Some(Cmd::Move(Move::Left))); 523 | is_reset(&mut vim); 524 | assert_eq!(vim.event(keydown(Keycode::K)), Some(Cmd::Move(Move::Up))); 525 | is_reset(&mut vim); 526 | assert_eq!(vim.event(keydown(Keycode::J)), Some(Cmd::Move(Move::Down))); 527 | is_reset(&mut vim); 528 | assert_eq!(vim.event(keydown(Keycode::L)), Some(Cmd::Move(Move::Right))); 529 | is_reset(&mut vim); 530 | 531 | assert_eq!(vim.event(text_input("0")), Some(Cmd::Move(Move::LineStart))); 532 | is_reset(&mut vim); 533 | 534 | assert_eq!(vim.event(text_input("$")), Some(Cmd::Move(Move::LineEnd))); 535 | is_reset(&mut vim); 536 | 537 | assert_eq!(vim.event(text_input("f")), None); 538 | assert!(vim.parsing_find); 539 | assert_eq!( 540 | vim.event(text_input(";")), 541 | Some(Cmd::Move(Move::Find(';', false))) 542 | ); 543 | is_reset(&mut vim); 544 | } 545 | 546 | #[test] 547 | fn repeated_movement() { 548 | let mut vim = Vim::new(); 549 | assert_eq!(vim.event(text_input("2")), None); 550 | assert_eq!( 551 | vim.event(keydown(Keycode::K)), 552 | Some(Cmd::Repeat { 553 | count: 2, 554 | cmd: Box::new(Cmd::Move(Move::Up)) 555 | }) 556 | ); 557 | is_reset(&mut vim); 558 | 559 | assert_eq!(vim.event(text_input("2")), None); 560 | assert_eq!(vim.event(text_input("f")), None); 561 | assert_eq!( 562 | vim.event(text_input("k")), 563 | Some(Cmd::Repeat { 564 | count: 2, 565 | cmd: Box::new(Cmd::Move(Move::Find('k', false))) 566 | }) 567 | ); 568 | is_reset(&mut vim); 569 | } 570 | } 571 | } 572 | -------------------------------------------------------------------------------- /glyph/editor/src/window.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{c_void, CString}, 3 | mem, 4 | ptr::null, 5 | sync::{Arc, RwLock}, 6 | }; 7 | 8 | use gl::types::{GLint, GLsizeiptr, GLuint, GLvoid}; 9 | use lsp::{Client, Diagnostics, LspSender}; 10 | use once_cell::sync::Lazy; 11 | use sdl2::{ 12 | event::Event, 13 | keyboard::{Keycode, Mod}, 14 | }; 15 | use syntax::tree_sitter_highlight::{HighlightConfiguration, HighlightEvent, Highlighter}; 16 | use syntax::Highlight; 17 | 18 | use crate::{ 19 | atlas::Atlas, Color, Editor, EditorEvent, EventResult, GLProgram, Shader, ThemeType, 20 | WindowFrameKind, ERROR_RED, SCREEN_HEIGHT, SCREEN_WIDTH, 21 | }; 22 | 23 | #[repr(C)] 24 | struct Point { 25 | x: f32, 26 | y: f32, 27 | s: f32, 28 | t: f32, 29 | } 30 | #[derive(Clone, Debug)] 31 | #[repr(C)] 32 | struct Point3 { 33 | // x == f32::MAX signifies Point3 is null 34 | x: f32, 35 | y: f32, 36 | z: f32, 37 | } 38 | 39 | impl Point3 { 40 | #[inline] 41 | fn is_null(&self) -> bool { 42 | self.x == f32::MAX 43 | } 44 | 45 | #[inline] 46 | fn null() -> Point3 { 47 | Point3 { 48 | x: f32::MAX, 49 | y: f32::MAX, 50 | z: f32::MAX, 51 | } 52 | } 53 | } 54 | 55 | impl Default for Point3 { 56 | fn default() -> Self { 57 | Self { 58 | x: f32::MAX, 59 | y: Default::default(), 60 | z: Default::default(), 61 | } 62 | } 63 | } 64 | 65 | const SX: f32 = 0.8 / SCREEN_WIDTH as f32; 66 | const SY: f32 = 0.8 / SCREEN_HEIGHT as f32; 67 | 68 | const START_X: f32 = -1f32 + 8f32 * SX; 69 | const START_Y: f32 = 1f32 - 50f32 * SY; 70 | 71 | pub struct Window<'theme, 'highlight> { 72 | // Graphics 73 | atlas: Atlas, 74 | text_shader: TextShaderProgram, 75 | cursor_shader: CursorShaderProgram, 76 | highlight_shader: HighlightShaderProgram, 77 | diagnostic_shader: DiagnosticShaderProgram, 78 | editor: Editor, 79 | text_coords: Vec, 80 | text_colors: Vec, 81 | cursor_coords: [Point3; 6], 82 | highlight_coords: Vec, 83 | diagnostics_coords: Vec, 84 | diagnostics_colors: Vec, 85 | y_offset: f32, 86 | x_offset: f32, 87 | text_height: f32, 88 | text_width: f32, 89 | last_stroke: u32, // Time since last stroke in ms 90 | 91 | // Syntax highlighting 92 | theme: &'theme ThemeType, 93 | highlighter: Highlighter, 94 | highlight_cfg: &'highlight Lazy, 95 | text_changed: bool, 96 | cursor_changed: bool, 97 | 98 | // LSP 99 | diagnostics: Arc>, 100 | lsp_send: LspSender, 101 | last_clock: u64, 102 | } 103 | 104 | impl<'theme, 'highlight> Window<'theme, 'highlight> { 105 | pub fn new( 106 | initial_text: Option, 107 | theme: &'theme ThemeType, 108 | lsp_client: &Client, 109 | ) -> Self { 110 | let font_path = "./fonts/FiraCode.ttf"; 111 | 112 | let text_shader = TextShaderProgram::default(); 113 | let atlas = Atlas::new(font_path, 48, text_shader.uniform_tex).unwrap(); 114 | let cursor_shader = CursorShaderProgram::default(); 115 | let highlight_shader = HighlightShaderProgram::default(); 116 | let diagnostic_shader = DiagnosticShaderProgram::default(); 117 | 118 | let highlighter = Highlighter::new(); 119 | 120 | let mut editor = Editor::with_text(initial_text); 121 | editor.configure_lsp(lsp_client); 122 | 123 | Self { 124 | atlas, 125 | text_shader, 126 | cursor_shader, 127 | highlight_shader, 128 | diagnostic_shader, 129 | editor, 130 | text_coords: Vec::new(), 131 | text_colors: Vec::new(), 132 | cursor_coords: Default::default(), 133 | highlight_coords: Default::default(), 134 | diagnostics_coords: Default::default(), 135 | diagnostics_colors: Vec::new(), 136 | y_offset: 0.0, 137 | x_offset: 0.0, 138 | text_height: 0.0, 139 | text_width: 0.0, 140 | last_stroke: 0, 141 | 142 | theme, 143 | highlighter, 144 | highlight_cfg: &syntax::RUST_CFG, 145 | text_changed: false, 146 | cursor_changed: false, 147 | 148 | diagnostics: lsp_client.diagnostics().clone(), 149 | lsp_send: lsp_client.sender().clone(), 150 | last_clock: 0, 151 | } 152 | } 153 | 154 | pub fn event(&mut self, event: Event, time: u32) -> EventResult { 155 | match event { 156 | Event::Quit { .. } => EventResult::Quit, 157 | Event::KeyDown { 158 | keycode: Some(Keycode::C), 159 | keymod, 160 | .. 161 | } if keymod == Mod::LCTRLMOD => EventResult::Quit, 162 | Event::MouseWheel { x, y, .. } => { 163 | if x.abs() > y.abs() { 164 | self.scroll_x(x as f32 * -4.0); 165 | } else { 166 | self.scroll_y(y as f32); 167 | } 168 | self.queue_cursor(); 169 | EventResult::Scroll 170 | } 171 | _ => { 172 | let evt = self.editor.event(event); 173 | self.handle_editor_event(evt, time) 174 | } 175 | } 176 | } 177 | } 178 | 179 | // This impl contains utilities 180 | impl<'theme, 'highlight> Window<'theme, 'highlight> { 181 | fn scroll_y(&mut self, mut amount: f32) { 182 | let pix_amount = amount * self.atlas.max_h; 183 | amount *= -1.0; 184 | match pix_amount > 0.0 { 185 | // Scrolling up 186 | true => { 187 | if self.y_offset + pix_amount >= 0.0 { 188 | self.y_offset = 0.0; 189 | } else { 190 | self.y_offset += pix_amount; 191 | self.editor.incr_line(amount as i32) 192 | } 193 | } 194 | // Scrolling down 195 | false => { 196 | if -1.0 * (self.y_offset + pix_amount) >= self.text_height { 197 | self.y_offset = self.text_height * -1.0; 198 | let len = self.editor.lines().len(); 199 | if len == 0 { 200 | self.editor.incr_line(0) 201 | } else { 202 | self.editor.set_line(len - 1); 203 | } 204 | } else { 205 | self.y_offset += pix_amount; 206 | self.editor.incr_line(amount as i32) 207 | } 208 | } 209 | } 210 | } 211 | 212 | fn scroll_x(&mut self, amount: f32) { 213 | match amount > 0.0 { 214 | true => { 215 | if self.x_offset + amount >= 0.0 { 216 | self.x_offset = 0.0; 217 | } else { 218 | self.x_offset += amount; 219 | } 220 | } 221 | false => { 222 | if -1.0 * (self.x_offset + amount) >= self.text_width { 223 | self.x_offset = self.text_width * -1.0; 224 | } else { 225 | self.x_offset += amount; 226 | } 227 | } 228 | } 229 | } 230 | } 231 | 232 | // This impl contains graphics functions 233 | impl<'theme, 'highlight> Window<'theme, 'highlight> { 234 | #[inline] 235 | fn handle_editor_event(&mut self, evt: EditorEvent, time: u32) -> EventResult { 236 | match evt { 237 | EditorEvent::DrawText => { 238 | self.text_changed = true; 239 | self.last_stroke = time; 240 | self.render_text(); 241 | EventResult::Draw 242 | } 243 | EditorEvent::DrawCursor => { 244 | self.cursor_changed = true; 245 | self.adjust_scroll(); 246 | self.queue_cursor(); 247 | EventResult::Draw 248 | } 249 | EditorEvent::DrawSelection => { 250 | self.queue_selection(START_X, START_Y, SX, SY); 251 | EventResult::Draw 252 | } 253 | EditorEvent::Multiple => { 254 | let evts = self.editor.take_multiple_event_data(); 255 | let mut draw = false; 256 | 257 | for evt in evts.into_iter() { 258 | if matches!(self.handle_editor_event(evt, time), EventResult::Draw) { 259 | draw = true; 260 | } 261 | } 262 | 263 | if draw { 264 | EventResult::Draw 265 | } else { 266 | EventResult::Nothing 267 | } 268 | } 269 | 270 | _ => EventResult::Nothing, 271 | } 272 | } 273 | 274 | pub fn render_text(&mut self) { 275 | self.adjust_scroll(); 276 | self.queue_cursor(); 277 | let colors = self.queue_highlights(); 278 | self.queue_text(colors, -1f32 + 8f32 * SX, 1f32 - 50f32 * SY, SX, SY); 279 | self.queue_selection(-1f32 + 8f32 * SX, 1f32 - 50f32 * SY, SX, SY) 280 | } 281 | 282 | pub fn queue_cursor(&mut self) { 283 | let w = self.atlas.max_w * SX; 284 | let real_h = self.atlas.max_h * SY; 285 | let h = (self.atlas.max_h/*+ 5f32*/) * SY; 286 | 287 | let x = (-1f32 + 8f32 * SX) 288 | + (self.editor.cursor() as f32 * (w/*+ self.atlas.glyphs[35].advance_x * SX*/)); 289 | let y = ((1f32 - 50f32 * SY) + real_h) - (self.editor.line() as f32 * real_h); 290 | 291 | self.cursor_coords = [ 292 | // // bottom left 293 | Point3 { 294 | x, 295 | y: y - h, 296 | z: 0.0, 297 | }, 298 | // top left 299 | Point3 { x, y, z: 0.0 }, 300 | // top right 301 | Point3 { 302 | x: x + w, 303 | y, 304 | z: 0.0, 305 | }, 306 | // bottom right 307 | Point3 { 308 | x: x + w, 309 | y: y - h, 310 | z: 0.0, 311 | }, 312 | // top right, 313 | Point3 { 314 | x: x + w, 315 | y, 316 | z: 0.0, 317 | }, 318 | // bottom leff 319 | Point3 { 320 | x, 321 | y: y - h, 322 | z: 0.0, 323 | }, 324 | ]; 325 | } 326 | 327 | pub fn frame(&mut self, kind: WindowFrameKind, ticks_ms: u32) { 328 | let draw = matches!(kind, WindowFrameKind::Draw); 329 | self.text_shader.set_used(); 330 | 331 | // Draw text 332 | unsafe { 333 | // TODO: X and Y translation can be global (make it a uniform) 334 | gl::VertexAttrib1f(self.text_shader.attrib_ytranslate, SY * self.y_offset); 335 | gl::VertexAttrib1f(self.text_shader.attrib_xtranslate, self.x_offset * SX); 336 | 337 | // Use the texture containing the atlas 338 | gl::BindTexture(gl::TEXTURE_2D, self.atlas.tex); 339 | gl::Uniform1i(self.text_shader.uniform_tex, 0); 340 | 341 | // Set up the VBO for our vertex data 342 | gl::BindBuffer(gl::ARRAY_BUFFER, self.text_shader.vbo); 343 | gl::VertexAttribPointer( 344 | self.text_shader.attrib_coord, 345 | 4, 346 | gl::FLOAT, 347 | gl::FALSE, 348 | 0, 349 | null(), 350 | ); 351 | gl::EnableVertexAttribArray(self.text_shader.attrib_coord); 352 | if draw { 353 | gl::BufferData( 354 | gl::ARRAY_BUFFER, 355 | (self.text_coords.len() * mem::size_of::()) as GLsizeiptr, 356 | self.text_coords.as_ptr() as *const GLvoid, 357 | gl::DYNAMIC_DRAW, 358 | ); 359 | } 360 | 361 | gl::BindBuffer(gl::ARRAY_BUFFER, self.text_shader.vbo_color); 362 | gl::VertexAttribPointer( 363 | self.text_shader.attrib_v_color, 364 | 4, 365 | gl::UNSIGNED_BYTE, 366 | gl::TRUE, 367 | 0, 368 | null(), 369 | ); 370 | gl::EnableVertexAttribArray(self.text_shader.attrib_v_color); 371 | if draw { 372 | gl::BufferData( 373 | gl::ARRAY_BUFFER, 374 | (self.text_colors.len() * mem::size_of::()) as GLsizeiptr, 375 | self.text_colors.as_ptr() as *const GLvoid, 376 | gl::DYNAMIC_DRAW, 377 | ); 378 | } 379 | 380 | gl::DrawArrays(gl::TRIANGLES, 0, self.text_coords.len() as i32); 381 | gl::DisableVertexAttribArray(self.text_shader.attrib_v_color); 382 | gl::DisableVertexAttribArray(self.text_shader.attrib_coord); 383 | } 384 | 385 | // Draw highlight 386 | { 387 | self.highlight_shader.set_used(); 388 | let attrib_ptr = self.highlight_shader.attrib_apos; 389 | unsafe { 390 | gl::VertexAttrib1f(self.highlight_shader.attrib_ytranslate, self.y_offset * SY); 391 | gl::VertexAttrib1f(self.highlight_shader.attrib_xtranslate, self.x_offset * SX); 392 | 393 | gl::BindBuffer(gl::ARRAY_BUFFER, self.highlight_shader.vbo); 394 | if draw { 395 | gl::BufferData( 396 | gl::ARRAY_BUFFER, 397 | (self.highlight_coords.len() * mem::size_of::()) as isize, 398 | self.highlight_coords.as_ptr() as *const c_void, 399 | gl::DYNAMIC_DRAW, 400 | ); 401 | } 402 | gl::VertexAttribPointer( 403 | attrib_ptr, 404 | 3, 405 | gl::FLOAT, 406 | gl::FALSE, 407 | mem::size_of::() as i32, 408 | null(), 409 | ); 410 | gl::EnableVertexAttribArray(0); 411 | 412 | gl::DrawArrays(gl::TRIANGLES, 0, self.highlight_coords.len() as i32); 413 | gl::DisableVertexAttribArray(0); 414 | } 415 | } 416 | // Draw diagnostics 417 | { 418 | self.diagnostic_shader.set_used(); 419 | unsafe { 420 | gl::VertexAttrib1f(self.diagnostic_shader.attrib_ytranslate, self.y_offset * SY); 421 | gl::VertexAttrib1f(self.diagnostic_shader.attrib_xtranslate, self.x_offset * SX); 422 | 423 | // Coords 424 | gl::BindBuffer(gl::ARRAY_BUFFER, self.diagnostic_shader.vbo); 425 | if draw { 426 | gl::BufferData( 427 | gl::ARRAY_BUFFER, 428 | (self.diagnostics_coords.len() * mem::size_of::()) as isize, 429 | self.diagnostics_coords.as_ptr() as *const c_void, 430 | gl::DYNAMIC_DRAW, 431 | ); 432 | } 433 | gl::VertexAttribPointer( 434 | self.diagnostic_shader.attrib_apos, 435 | 3, 436 | gl::FLOAT, 437 | gl::FALSE, 438 | mem::size_of::() as i32, 439 | null(), 440 | ); 441 | // Color 442 | gl::BindBuffer(gl::ARRAY_BUFFER, self.diagnostic_shader.vbo_color); 443 | if draw { 444 | gl::BufferData( 445 | gl::ARRAY_BUFFER, 446 | (self.diagnostics_colors.len() * mem::size_of::()) as isize, 447 | self.diagnostics_colors.as_ptr() as *const c_void, 448 | gl::DYNAMIC_DRAW, 449 | ); 450 | } 451 | gl::VertexAttribPointer( 452 | self.diagnostic_shader.attrib_color, 453 | 4, 454 | gl::UNSIGNED_BYTE, 455 | gl::TRUE, 456 | 0, 457 | null(), 458 | ); 459 | 460 | gl::EnableVertexAttribArray(self.diagnostic_shader.attrib_apos); 461 | gl::EnableVertexAttribArray(self.diagnostic_shader.attrib_color); 462 | gl::DrawArrays(gl::TRIANGLES, 0, self.diagnostics_coords.len() as i32); 463 | gl::DisableVertexAttribArray(self.diagnostic_shader.attrib_apos); 464 | gl::DisableVertexAttribArray(self.diagnostic_shader.attrib_color); 465 | } 466 | } 467 | 468 | // Draw cursor 469 | { 470 | self.cursor_shader.set_used(); 471 | unsafe { 472 | gl::VertexAttrib1f(self.cursor_shader.attrib_ytranslate, self.y_offset * SY); 473 | gl::VertexAttrib1f(self.cursor_shader.attrib_xtranslate, self.x_offset * SX); 474 | gl::Uniform1f( 475 | self.cursor_shader.uniform_laststroke, 476 | self.last_stroke as f32 / 1000.0, 477 | ); 478 | gl::Uniform1i( 479 | self.cursor_shader.uniform_is_blinking, 480 | if self.editor.is_insert() { 1 } else { 0 }, 481 | ); 482 | gl::Uniform1f(self.cursor_shader.uniform_time, ticks_ms as f32 / 1000.0); 483 | } 484 | 485 | let attrib_ptr = self.cursor_shader.attrib_apos; 486 | unsafe { 487 | gl::BindBuffer(gl::ARRAY_BUFFER, self.cursor_shader.vbo); 488 | 489 | // gl::BlendFunc(gl::SRC_ALPHA, gl::ONE); 490 | // gl::BlendEquation(gl::FUNC_SUBTRACT); 491 | 492 | if draw { 493 | gl::BufferData( 494 | gl::ARRAY_BUFFER, 495 | (self.cursor_coords.len() * mem::size_of::()) as isize, 496 | self.cursor_coords.as_ptr() as *const c_void, 497 | gl::DYNAMIC_DRAW, 498 | ); 499 | } 500 | 501 | gl::VertexAttribPointer( 502 | attrib_ptr, 503 | 3, 504 | gl::FLOAT, 505 | gl::FALSE, 506 | 3 * mem::size_of::() as i32, 507 | null(), 508 | ); 509 | gl::EnableVertexAttribArray(0); 510 | gl::DrawArrays(gl::TRIANGLES, 0, 6); 511 | gl::DisableVertexAttribArray(0); 512 | 513 | gl::Enable(gl::BLEND); 514 | gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); 515 | gl::BlendEquation(gl::FUNC_ADD); 516 | } 517 | } 518 | } 519 | 520 | pub fn queue_diagnostics(&mut self) { 521 | let d = self.diagnostics.read().unwrap(); 522 | if self.last_clock != d.clock { 523 | let mut coords: Vec = Vec::new(); 524 | let mut colors: Vec = Vec::new(); 525 | 526 | let mut col = 0; 527 | for diag in &d.diagnostics { 528 | let max_w = self.atlas.max_w * SX; 529 | let max_h = self.atlas.max_h; 530 | 531 | let mut x = START_X; 532 | let mut y = START_Y; 533 | 534 | let mut top_left: Point3 = Point3::null(); 535 | let mut bot_left: Point3 = Point3::null(); 536 | 537 | let lsp::Range { 538 | start: start_pos, 539 | end: end_pos, 540 | } = diag.range; 541 | let start = self.editor.line_idx(start_pos.line as usize); 542 | let end = self 543 | .editor 544 | .line_char_idx(end_pos.line as usize, end_pos.character as usize); 545 | 546 | let within_range = |i: usize| -> bool { 547 | (i + start) >= (start + start_pos.character as usize) && (i + start) < end 548 | }; 549 | 550 | for (i, ch) in self.editor.text(start..(end + 1)).chars().enumerate() { 551 | let c = ch as usize; 552 | 553 | // Calculate the vertex and texture coordinates 554 | let x2 = x + (col as f32 * max_w); 555 | // let x2 = x + max_w; 556 | let y2 = -y; 557 | let width = self.atlas.glyphs[c].bitmap_w * SX; 558 | let height = self.atlas.glyphs[c].bitmap_h * SY; 559 | 560 | // Skip glyphs that have no pixels 561 | if (width == 0.0 || height == 0.0) && !within_range(i) { 562 | match ch as u8 { 563 | 32 => { 564 | col += 1; 565 | } 566 | // Tab 567 | 9 => { 568 | x += self.atlas.max_w * SY * 4f32; 569 | col += 4; 570 | } 571 | // New line 572 | 10 => { 573 | y -= max_h; 574 | x = START_X; 575 | if !top_left.is_null() { 576 | let bot_right = Point3 { 577 | x: x2, 578 | y: -y2 + max_h, 579 | z: 0.0, 580 | }; 581 | let top_right = Point3 { 582 | x: x2, 583 | y: -y2, 584 | z: 0.0, 585 | }; 586 | // First triangle 587 | coords.push(top_left.clone()); 588 | coords.push(bot_left.clone()); 589 | coords.push(bot_right.clone()); 590 | // Second triangle 591 | coords.push(top_left.clone()); 592 | coords.push(top_right); 593 | coords.push(bot_right); 594 | colors.push(ERROR_RED); 595 | colors.push(ERROR_RED); 596 | colors.push(ERROR_RED); 597 | colors.push(ERROR_RED); 598 | colors.push(ERROR_RED); 599 | colors.push(ERROR_RED); 600 | 601 | top_left = Point3::null(); 602 | bot_left = Point3::null(); 603 | } 604 | col = 0; 605 | } 606 | _ => {} 607 | } 608 | continue; 609 | } 610 | 611 | if top_left.is_null() && within_range(i) { 612 | top_left = Point3 { 613 | x: x2, 614 | y: -y2, 615 | z: 0.0, 616 | }; 617 | bot_left = Point3 { 618 | x: x2, 619 | y: -y2 + max_h, 620 | z: 0.0, 621 | }; 622 | } else if !top_left.is_null() && !within_range(i) { 623 | let bot_right = Point3 { 624 | x: x2, 625 | y: -y2 + max_h, 626 | z: 0.0, 627 | }; 628 | let top_right = Point3 { 629 | x: x2, 630 | y: -y2, 631 | z: 0.0, 632 | }; 633 | // First triangle 634 | coords.push(top_left.clone()); 635 | coords.push(bot_left.clone()); 636 | coords.push(bot_right.clone()); 637 | // Second triangle 638 | coords.push(top_left.clone()); 639 | coords.push(top_right); 640 | coords.push(bot_right); 641 | colors.push(ERROR_RED); 642 | colors.push(ERROR_RED); 643 | colors.push(ERROR_RED); 644 | colors.push(ERROR_RED); 645 | colors.push(ERROR_RED); 646 | colors.push(ERROR_RED); 647 | break; 648 | } else if i + start >= end { 649 | println!("BREKING"); 650 | if !top_left.is_null() { 651 | let bot_right = Point3 { 652 | x: x2, 653 | y: -y2 + max_h, 654 | z: 0.0, 655 | }; 656 | let top_right = Point3 { 657 | x: x2, 658 | y: -y2, 659 | z: 0.0, 660 | }; 661 | // First triangle 662 | coords.push(top_left.clone()); 663 | coords.push(bot_left.clone()); 664 | coords.push(bot_right.clone()); 665 | // Second triangle 666 | coords.push(top_left.clone()); 667 | coords.push(top_right); 668 | coords.push(bot_right); 669 | colors.push(ERROR_RED); 670 | colors.push(ERROR_RED); 671 | colors.push(ERROR_RED); 672 | colors.push(ERROR_RED); 673 | colors.push(ERROR_RED); 674 | colors.push(ERROR_RED); 675 | } 676 | break; 677 | } 678 | col += 1; 679 | } 680 | } 681 | 682 | self.diagnostics_coords = coords; 683 | self.diagnostics_colors = colors; 684 | self.last_clock = d.clock; 685 | } 686 | } 687 | 688 | fn queue_selection(&mut self, mut x: f32, mut y: f32, sx: f32, sy: f32) { 689 | if self.editor.selection().is_none() { 690 | self.highlight_coords.clear(); 691 | return; 692 | } 693 | 694 | let mut hl_coords: Vec = Vec::new(); 695 | 696 | let starting_x = x; 697 | let max_w = self.atlas.max_w * sx; 698 | let max_h = self.atlas.max_h * sy; 699 | 700 | let mut top_left: Point3 = Point3::null(); 701 | let mut bot_left: Point3 = Point3::null(); 702 | 703 | let mut col: u32 = 0; 704 | for (i, ch) in self.editor.text_all().chars().enumerate() { 705 | let c = ch as usize; 706 | 707 | // Calculate the vertex and texture coordinates 708 | let x2 = x + (col as f32 * max_w); 709 | // let x2 = x + max_w; 710 | let y2 = -y; 711 | let width = self.atlas.glyphs[c].bitmap_w * sx; 712 | let height = self.atlas.glyphs[c].bitmap_h * sy; 713 | 714 | // Skip glyphs that have no pixels 715 | if (width == 0.0 || height == 0.0) && !self.editor.past_selection(i as u32) { 716 | match ch as u8 { 717 | 32 => { 718 | col += 1; 719 | } 720 | // Tab 721 | 9 => { 722 | x += self.atlas.max_w * sy * 4f32; 723 | col += 4; 724 | } 725 | // New line 726 | 10 => { 727 | y -= max_h; 728 | x = starting_x; 729 | if !top_left.is_null() { 730 | let bot_right = Point3 { 731 | x: x2, 732 | y: -y2 + max_h, 733 | z: 0.0, 734 | }; 735 | let top_right = Point3 { 736 | x: x2, 737 | y: -y2, 738 | z: 0.0, 739 | }; 740 | // First triangle 741 | hl_coords.push(top_left.clone()); 742 | hl_coords.push(bot_left.clone()); 743 | hl_coords.push(bot_right.clone()); 744 | // Second triangle 745 | hl_coords.push(top_left.clone()); 746 | hl_coords.push(top_right); 747 | hl_coords.push(bot_right); 748 | 749 | top_left = Point3::null(); 750 | bot_left = Point3::null(); 751 | } 752 | col = 0; 753 | } 754 | _ => {} 755 | } 756 | continue; 757 | } 758 | 759 | if top_left.is_null() && self.editor.within_selection(i as u32) { 760 | top_left = Point3 { 761 | x: x2, 762 | y: -y2, 763 | z: 0.0, 764 | }; 765 | bot_left = Point3 { 766 | x: x2, 767 | y: -y2 + max_h, 768 | z: 0.0, 769 | }; 770 | } else if !top_left.is_null() && !self.editor.within_selection(i as u32) { 771 | let bot_right = Point3 { 772 | x: x2, 773 | y: -y2 + max_h, 774 | z: 0.0, 775 | }; 776 | let top_right = Point3 { 777 | x: x2, 778 | y: -y2, 779 | z: 0.0, 780 | }; 781 | // First triangle 782 | hl_coords.push(top_left.clone()); 783 | hl_coords.push(bot_left.clone()); 784 | hl_coords.push(bot_right.clone()); 785 | // Second triangle 786 | hl_coords.push(top_left.clone()); 787 | hl_coords.push(top_right); 788 | hl_coords.push(bot_right); 789 | break; 790 | } else if self.editor.past_selection(i as u32) { 791 | break; 792 | } 793 | col += 1; 794 | } 795 | 796 | self.highlight_coords = hl_coords; 797 | } 798 | 799 | fn queue_text(&mut self, colors: Vec<&Color>, mut x: f32, mut y: f32, sx: f32, sy: f32) { 800 | let text = self.editor.text_all(); 801 | let starting_x = x; 802 | 803 | // TODO: Cache this 804 | let mut coords: Vec = Vec::with_capacity(6 * text.len_chars()); 805 | let mut colors_vertex: Vec = Vec::with_capacity(coords.capacity()); 806 | 807 | let mut text_height = 0.0; 808 | let mut line_width = 0.0; 809 | 810 | for (i, ch) in text.chars().enumerate() { 811 | let c = ch as usize; 812 | 813 | // Calculate the vertex and texture coordinates 814 | let x2 = x + self.atlas.glyphs[c].bitmap_l * sx; 815 | let y2 = -y - self.atlas.glyphs[c].bitmap_t * sy; 816 | let width = self.atlas.glyphs[c].bitmap_w * sx; 817 | let height = self.atlas.glyphs[c].bitmap_h * sy; 818 | 819 | // Advance the cursor to the start of the next character 820 | x += self.atlas.glyphs[c].advance_x * sx; 821 | y += self.atlas.glyphs[c].advance_y * sy; 822 | 823 | line_width += self.atlas.glyphs[c].advance_x; 824 | 825 | // Skip glyphs that have no pixels 826 | if width == 0.0 || height == 0.0 { 827 | match ch as u8 { 828 | // Tab 829 | 9 => { 830 | x += self.atlas.max_w * sy * 4f32; 831 | } 832 | // New line 833 | 10 => { 834 | y -= self.atlas.max_h * sy; 835 | text_height += self.atlas.max_h; 836 | self.text_height = self.text_height.max(text_height); 837 | line_width = 0.0; 838 | x = starting_x; 839 | } 840 | _ => {} 841 | } 842 | continue; 843 | } 844 | 845 | coords.push(Point { 846 | x: x2, 847 | y: -y2, 848 | s: self.atlas.glyphs[c].tx, 849 | t: self.atlas.glyphs[c].ty, 850 | }); 851 | coords.push(Point { 852 | x: x2 + width, 853 | y: -y2, 854 | s: self.atlas.glyphs[c].tx + self.atlas.glyphs[c].bitmap_w / self.atlas.w as f32, 855 | t: self.atlas.glyphs[c].ty, 856 | }); 857 | coords.push(Point { 858 | x: x2, 859 | y: -y2 - height, 860 | s: self.atlas.glyphs[c].tx, 861 | t: self.atlas.glyphs[c].ty + self.atlas.glyphs[c].bitmap_h / self.atlas.h as f32, 862 | }); 863 | coords.push(Point { 864 | x: x2 + width, 865 | y: -y2, 866 | s: self.atlas.glyphs[c].tx + self.atlas.glyphs[c].bitmap_w / self.atlas.w as f32, 867 | t: self.atlas.glyphs[c].ty, 868 | }); 869 | coords.push(Point { 870 | x: x2, 871 | y: -y2 - height, 872 | s: self.atlas.glyphs[c].tx, 873 | t: self.atlas.glyphs[c].ty + self.atlas.glyphs[c].bitmap_h / self.atlas.h as f32, 874 | }); 875 | coords.push(Point { 876 | x: x2 + width, 877 | y: -y2 - height, 878 | s: self.atlas.glyphs[c].tx + self.atlas.glyphs[c].bitmap_w / self.atlas.w as f32, 879 | t: self.atlas.glyphs[c].ty + self.atlas.glyphs[c].bitmap_h / self.atlas.h as f32, 880 | }); 881 | 882 | colors_vertex.push(*colors[i]); 883 | colors_vertex.push(*colors[i]); 884 | colors_vertex.push(*colors[i]); 885 | colors_vertex.push(*colors[i]); 886 | colors_vertex.push(*colors[i]); 887 | colors_vertex.push(*colors[i]); 888 | } 889 | 890 | // TODO: It's faster to directly mutate these vecs instead of making 891 | // new ones and replacing them. Also if we're only appending new text we don't need to 892 | // rebuild vecs in entirety 893 | self.text_coords = coords; 894 | self.text_colors = colors_vertex; 895 | 896 | self.text_height = text_height; 897 | self.text_width = self.text_width.max(line_width); 898 | } 899 | 900 | fn queue_highlights(&mut self) -> Vec<&'theme Color> { 901 | // TODO: Rope buffer is very inexpensive to clone (taking O(1) time), 902 | // so we should just do that here. 903 | let src: Vec = self.editor.text_all().bytes().collect(); 904 | 905 | // Assume chars are 1 byte long (ascii) 906 | let mut text_colors: Vec<&Color> = vec![self.theme.fg(); src.len()]; 907 | 908 | let highlights = self 909 | .highlighter 910 | .highlight(self.highlight_cfg, &src, None, |_| None) 911 | .unwrap(); 912 | 913 | let mut color_stack: Vec<&Color> = Vec::new(); 914 | 915 | for event in highlights { 916 | match event.unwrap() { 917 | HighlightEvent::Source { start, end } => { 918 | if let Some(color) = color_stack.last() { 919 | (start..end).for_each(|i| { 920 | text_colors[i] = color; 921 | }); 922 | } 923 | } 924 | HighlightEvent::HighlightStart(s) => { 925 | if let Some(highlight) = Highlight::from_u8(s.0 as u8) { 926 | color_stack.push( 927 | self.theme 928 | .highlight(highlight) 929 | .unwrap_or_else(|| self.theme.fg()), 930 | ); 931 | } else { 932 | color_stack.push(self.theme.fg()) 933 | } 934 | } 935 | HighlightEvent::HighlightEnd => { 936 | color_stack.pop(); 937 | } 938 | } 939 | } 940 | 941 | text_colors 942 | } 943 | 944 | fn adjust_scroll(&mut self) { 945 | let oy = self.line_y_offset(self.editor.line()); 946 | let scrolled_h = SCREEN_HEIGHT as f32 * 2.0 + (self.y_offset * -1.0); 947 | 948 | // Multiply by two because retina display on Mac 949 | if oy >= scrolled_h || oy < self.y_offset * -1.0 { 950 | self.y_offset = oy * -1.0; 951 | } 952 | } 953 | } 954 | 955 | // This impl contains small utilities 956 | impl<'theme, 'highlight> Window<'theme, 'highlight> { 957 | pub fn theme(&self) -> &ThemeType { 958 | self.theme 959 | } 960 | 961 | // Get the y offset (scroll pos) for the given line 962 | #[inline] 963 | fn line_y_offset(&self, line: usize) -> f32 { 964 | (self.atlas.max_h as f32 * line as f32) - START_Y 965 | } 966 | } 967 | 968 | pub struct TextShaderProgram { 969 | program: GLProgram, 970 | attrib_coord: GLuint, 971 | attrib_ytranslate: GLuint, 972 | attrib_xtranslate: GLuint, 973 | attrib_v_color: GLuint, 974 | uniform_tex: GLint, 975 | vbo: GLuint, 976 | vbo_color: GLuint, 977 | } 978 | 979 | impl TextShaderProgram { 980 | pub fn new() -> Self { 981 | let shaders = vec![ 982 | Shader::from_source( 983 | &CString::new(include_str!("../shaders/text.v.glsl")).unwrap(), 984 | gl::VERTEX_SHADER, 985 | ) 986 | .unwrap(), 987 | Shader::from_source( 988 | &CString::new(include_str!("../shaders/text.f.glsl")).unwrap(), 989 | gl::FRAGMENT_SHADER, 990 | ) 991 | .unwrap(), 992 | ]; 993 | 994 | let program = GLProgram::from_shaders(&shaders).unwrap(); 995 | 996 | let mut vbo: GLuint = 0; 997 | unsafe { 998 | gl::GenBuffers(1, &mut vbo as *mut GLuint); 999 | } 1000 | 1001 | let mut vbo_color: GLuint = 0; 1002 | unsafe { 1003 | gl::GenBuffers(1, &mut vbo_color as *mut GLuint); 1004 | } 1005 | 1006 | Self { 1007 | attrib_coord: program.attrib("coord").unwrap() as u32, 1008 | attrib_ytranslate: program.attrib("y_translate").unwrap() as u32, 1009 | attrib_xtranslate: program.attrib("x_translate").unwrap() as u32, 1010 | attrib_v_color: program.attrib("vertex_color").unwrap() as u32, 1011 | uniform_tex: program.uniform("tex").unwrap(), 1012 | vbo, 1013 | vbo_color, 1014 | program, 1015 | } 1016 | } 1017 | 1018 | #[inline] 1019 | pub fn set_used(&self) { 1020 | self.program.set_used() 1021 | } 1022 | } 1023 | 1024 | impl Default for TextShaderProgram { 1025 | fn default() -> Self { 1026 | Self::new() 1027 | } 1028 | } 1029 | 1030 | pub struct CursorShaderProgram { 1031 | program: GLProgram, 1032 | attrib_ytranslate: GLuint, 1033 | attrib_xtranslate: GLuint, 1034 | uniform_time: GLint, 1035 | uniform_laststroke: GLint, 1036 | uniform_is_blinking: GLint, 1037 | attrib_apos: GLuint, 1038 | vbo: GLuint, 1039 | } 1040 | 1041 | impl CursorShaderProgram { 1042 | pub fn new() -> Self { 1043 | let shaders = vec![ 1044 | Shader::from_source( 1045 | &CString::new(include_str!("../shaders/cursor.v.glsl")).unwrap(), 1046 | gl::VERTEX_SHADER, 1047 | ) 1048 | .unwrap(), 1049 | Shader::from_source( 1050 | &CString::new(include_str!("../shaders/cursor.f.glsl")).unwrap(), 1051 | gl::FRAGMENT_SHADER, 1052 | ) 1053 | .unwrap(), 1054 | ]; 1055 | 1056 | let program = GLProgram::from_shaders(&shaders).unwrap(); 1057 | 1058 | let mut vbo: GLuint = 0; 1059 | unsafe { gl::GenBuffers(1, &mut vbo as *mut GLuint) } 1060 | 1061 | Self { 1062 | attrib_apos: program.attrib("aPos").unwrap() as u32, 1063 | attrib_ytranslate: program.attrib("y_translate").unwrap() as u32, 1064 | attrib_xtranslate: program.attrib("x_translate").unwrap() as u32, 1065 | uniform_time: program.uniform("time").unwrap(), 1066 | uniform_laststroke: program.uniform("last_stroke").unwrap(), 1067 | uniform_is_blinking: program.uniform("is_blinking").unwrap(), 1068 | program, 1069 | vbo, 1070 | } 1071 | } 1072 | 1073 | #[inline] 1074 | pub fn set_used(&self) { 1075 | self.program.set_used() 1076 | } 1077 | } 1078 | 1079 | impl Default for CursorShaderProgram { 1080 | fn default() -> Self { 1081 | Self::new() 1082 | } 1083 | } 1084 | 1085 | pub struct HighlightShaderProgram { 1086 | program: GLProgram, 1087 | attrib_ytranslate: GLuint, 1088 | attrib_xtranslate: GLuint, 1089 | attrib_apos: GLuint, 1090 | vbo: GLuint, 1091 | } 1092 | 1093 | impl HighlightShaderProgram { 1094 | pub fn new() -> Self { 1095 | let shaders = vec![ 1096 | Shader::from_source( 1097 | &CString::new(include_str!("../shaders/highlight.v.glsl")).unwrap(), 1098 | gl::VERTEX_SHADER, 1099 | ) 1100 | .unwrap(), 1101 | Shader::from_source( 1102 | &CString::new(include_str!("../shaders/highlight.f.glsl")).unwrap(), 1103 | gl::FRAGMENT_SHADER, 1104 | ) 1105 | .unwrap(), 1106 | ]; 1107 | 1108 | let program = GLProgram::from_shaders(&shaders).unwrap(); 1109 | 1110 | let mut vbo: GLuint = 0; 1111 | unsafe { gl::GenBuffers(1, &mut vbo as *mut GLuint) } 1112 | 1113 | Self { 1114 | attrib_apos: program.attrib("aPos").unwrap() as u32, 1115 | attrib_ytranslate: program.attrib("y_translate").unwrap() as u32, 1116 | attrib_xtranslate: program.attrib("x_translate").unwrap() as u32, 1117 | program, 1118 | vbo, 1119 | } 1120 | } 1121 | 1122 | #[inline] 1123 | pub fn set_used(&self) { 1124 | self.program.set_used() 1125 | } 1126 | } 1127 | 1128 | impl Default for HighlightShaderProgram { 1129 | fn default() -> Self { 1130 | Self::new() 1131 | } 1132 | } 1133 | 1134 | pub struct DiagnosticShaderProgram { 1135 | program: GLProgram, 1136 | attrib_color: GLuint, 1137 | attrib_ytranslate: GLuint, 1138 | attrib_xtranslate: GLuint, 1139 | attrib_apos: GLuint, 1140 | vbo: GLuint, 1141 | vbo_color: GLuint, 1142 | } 1143 | 1144 | impl DiagnosticShaderProgram { 1145 | pub fn new() -> Self { 1146 | let shaders = vec![ 1147 | Shader::from_source( 1148 | &CString::new(include_str!("../shaders/diagnostic.v.glsl")).unwrap(), 1149 | gl::VERTEX_SHADER, 1150 | ) 1151 | .unwrap(), 1152 | Shader::from_source( 1153 | &CString::new(include_str!("../shaders/diagnostic.f.glsl")).unwrap(), 1154 | gl::FRAGMENT_SHADER, 1155 | ) 1156 | .unwrap(), 1157 | ]; 1158 | 1159 | let program = GLProgram::from_shaders(&shaders).unwrap(); 1160 | 1161 | let mut vbo = 0; 1162 | let mut vbo_color = 0; 1163 | unsafe { 1164 | gl::GenBuffers(1, &mut vbo as *mut GLuint); 1165 | gl::GenBuffers(1, &mut vbo_color as *mut GLuint); 1166 | } 1167 | 1168 | Self { 1169 | attrib_apos: program.attrib("aPos").unwrap() as u32, 1170 | attrib_color: program.attrib("vertex_color").unwrap() as u32, 1171 | attrib_ytranslate: program.attrib("y_translate").unwrap() as u32, 1172 | attrib_xtranslate: program.attrib("x_translate").unwrap() as u32, 1173 | program, 1174 | vbo, 1175 | vbo_color, 1176 | } 1177 | } 1178 | 1179 | #[inline] 1180 | pub fn set_used(&self) { 1181 | self.program.set_used() 1182 | } 1183 | } 1184 | 1185 | impl Default for DiagnosticShaderProgram { 1186 | fn default() -> Self { 1187 | Self::new() 1188 | } 1189 | } 1190 | -------------------------------------------------------------------------------- /glyph/editor/src/editor.rs: -------------------------------------------------------------------------------- 1 | use lsp::{Client, LspSender}; 2 | use ropey::{Rope, RopeSlice}; 3 | use sdl2::{event::Event, keyboard::Keycode}; 4 | use std::{cell::Cell, cmp::Ordering, ops::Range}; 5 | 6 | use crate::{ 7 | vim::{Cmd, NewLine}, 8 | vim::{Move, Vim}, 9 | EditorEvent, MoveWord, MoveWordKind, 10 | }; 11 | 12 | #[derive(Copy, Clone, Debug, PartialEq)] 13 | pub enum Mode { 14 | Insert, 15 | Normal, 16 | Visual, 17 | } 18 | 19 | #[derive(Clone, Debug)] 20 | pub enum Edit { 21 | Insertion { start: Cell, str_idx: u32 }, 22 | Deletion { start: Cell, str_idx: u32 }, 23 | } 24 | 25 | impl Edit { 26 | pub fn invert(&self) -> Self { 27 | match self { 28 | Edit::Insertion { 29 | start, 30 | str_idx: str, 31 | } => Edit::Deletion { 32 | start: start.clone(), 33 | str_idx: *str, 34 | }, 35 | Edit::Deletion { 36 | start, 37 | str_idx: str, 38 | } => Edit::Insertion { 39 | start: start.clone(), 40 | str_idx: *str, 41 | }, 42 | } 43 | } 44 | } 45 | 46 | pub struct Editor { 47 | // In insert mode this is the next position to be written (1 + self.lines[line]). 48 | cursor: usize, 49 | line: usize, 50 | // TODO: Deleting/adding lines inbetween others is an O(n) operation, maybe be better to use the lines 51 | // provided by the rope buffer, this is has the trade off of always doing the O(logn) calculation, vs. 52 | // the O(1) access of a vec 53 | lines: Vec, 54 | text: Rope, 55 | mode: Mode, 56 | 57 | // Vim stuff 58 | vim: Vim, 59 | selection: Option<(u32, u32)>, 60 | 61 | // Undo/redo 62 | had_space: bool, 63 | edits: Vec, 64 | redos: Vec, 65 | edit_vecs: Vec>, 66 | 67 | /// Store EditorEvent::Multiple data here instead of the enum because 68 | /// it bloats the enum's size: 1 byte -> 16 bytes!!! 69 | multiple_events_data: [EditorEvent; 3], 70 | 71 | lsp_sender: Option, 72 | } 73 | 74 | fn text_to_lines(text: I) -> Vec 75 | where 76 | I: Iterator, 77 | { 78 | let mut lines = Vec::new(); 79 | 80 | let mut count = 0; 81 | let mut last = 'a'; 82 | for c in text { 83 | last = c; 84 | if c == '\n' { 85 | lines.push(count); 86 | count = 0; 87 | } else { 88 | count += 1; 89 | } 90 | } 91 | 92 | if last != '\n' { 93 | lines.push(count); 94 | } else { 95 | lines.push(0); 96 | } 97 | 98 | lines 99 | } 100 | 101 | impl Editor { 102 | pub fn with_text(initial_text: Option) -> Self { 103 | let (lines, text) = match initial_text { 104 | Some(text) => (text_to_lines(text.chars()), Rope::from_str(&text)), 105 | None => (vec![0], Rope::new()), 106 | }; 107 | Self { 108 | cursor: 0, 109 | lines, 110 | line: 0, 111 | text, 112 | mode: Mode::Insert, 113 | vim: Vim::new(), 114 | selection: None, 115 | had_space: false, 116 | edits: Vec::new(), 117 | redos: Vec::new(), 118 | edit_vecs: Vec::new(), 119 | multiple_events_data: [EditorEvent::Nothing; 3], 120 | lsp_sender: None, 121 | } 122 | } 123 | 124 | pub fn new() -> Self { 125 | Editor::with_text(None) 126 | } 127 | 128 | pub fn configure_lsp(&mut self, lsp_client: &Client) { 129 | self.lsp_sender = Some(lsp_client.sender().clone()) 130 | } 131 | 132 | pub fn event(&mut self, event: Event) -> EditorEvent { 133 | // println!( 134 | // "Abs={} Cursor={} Line={} Lines={:?}", 135 | // self.pos(), 136 | // self.cursor, 137 | // self.line, 138 | // self.lines 139 | // ); 140 | match self.mode { 141 | Mode::Normal => self.normal_mode(event), 142 | Mode::Insert => self.insert_mode(event), 143 | Mode::Visual => self.visual_mode(event), 144 | } 145 | } 146 | } 147 | 148 | // This impl contains utilities for visual mode 149 | impl Editor { 150 | /// Visual mode is identical to normal mode except: 151 | /// * movements adjust the selection start and end 152 | /// * Change/Delete/Yank don't have any modifiers and instead apply to the selection 153 | fn visual_mode(&mut self, event: Event) -> EditorEvent { 154 | match self.vim.event(event) { 155 | None => EditorEvent::Nothing, 156 | Some(cmd) => { 157 | let start = self 158 | .selection() 159 | .map_or_else(|| self.pos(), |(start, _)| start as usize); 160 | let result = self.handle_cmd(&cmd); 161 | let end = self.pos(); 162 | 163 | if start == end { 164 | self.selection = Some((start as u32, start as u32)); 165 | self.set_multiple_event_data([ 166 | EditorEvent::DrawSelection, 167 | result, 168 | EditorEvent::Nothing, 169 | ]); 170 | EditorEvent::Multiple 171 | } else { 172 | if let Some(ref mut selection) = self.selection { 173 | match start.cmp(&end) { 174 | Ordering::Equal => {} 175 | Ordering::Less | Ordering::Greater => { 176 | selection.1 = end as u32; 177 | } 178 | } 179 | } else if matches!(self.mode, Mode::Visual) { 180 | unreachable!("Selection should be set when entering visual mode"); 181 | } 182 | 183 | self.set_multiple_event_data([ 184 | EditorEvent::DrawSelection, 185 | result, 186 | EditorEvent::Nothing, 187 | ]); 188 | EditorEvent::Multiple 189 | } 190 | } 191 | } 192 | } 193 | } 194 | 195 | // This impl contains utilities for insert mode 196 | impl Editor { 197 | fn insert_mode(&mut self, event: Event) -> EditorEvent { 198 | match event { 199 | Event::KeyDown { 200 | keycode: Some(Keycode::Tab), 201 | .. 202 | } => { 203 | self.insert(" "); 204 | EditorEvent::DrawText 205 | } 206 | Event::KeyDown { 207 | keycode: Some(Keycode::Escape), 208 | .. 209 | } => { 210 | self.switch_mode(Mode::Normal); 211 | EditorEvent::DrawCursor 212 | } 213 | Event::KeyDown { 214 | keycode: Some(Keycode::Backspace), 215 | .. 216 | } => self.backspace(), 217 | Event::KeyDown { 218 | keycode: Some(Keycode::Return), 219 | .. 220 | } => { 221 | self.enter(); 222 | EditorEvent::DrawText 223 | } 224 | Event::TextInput { text, .. } => { 225 | if let Mode::Insert = self.mode { 226 | self.insert(&text); 227 | EditorEvent::DrawText 228 | } else { 229 | EditorEvent::Nothing 230 | } 231 | } 232 | _ => EditorEvent::Nothing, 233 | } 234 | } 235 | } 236 | 237 | // This impl contains utilities for normal mode 238 | impl Editor { 239 | fn normal_mode(&mut self, event: Event) -> EditorEvent { 240 | match self.vim.event(event) { 241 | None => EditorEvent::Nothing, 242 | Some(cmd) => self.handle_cmd(&cmd), 243 | } 244 | } 245 | 246 | fn handle_cmd(&mut self, cmd: &Cmd) -> EditorEvent { 247 | match self.mode { 248 | Mode::Normal => self.handle_cmd_normal(cmd), 249 | Mode::Visual => self.handle_cmd_visual(cmd), 250 | _ => panic!("Vim commands should only be executed in normal or visual mode"), 251 | } 252 | } 253 | 254 | fn handle_cmd_visual(&mut self, cmd: &Cmd) -> EditorEvent { 255 | match cmd { 256 | Cmd::SwitchMode(Mode::Insert) => { 257 | self.switch_mode(Mode::Insert); 258 | EditorEvent::Nothing 259 | } 260 | Cmd::SwitchMode(Mode::Visual) => { 261 | self.switch_mode(Mode::Normal); 262 | EditorEvent::Nothing 263 | } 264 | Cmd::Change(None) | Cmd::Delete(None) => { 265 | self.delete_selection(); 266 | if matches!(cmd, Cmd::Change(None)) { 267 | self.switch_mode(Mode::Insert); 268 | } else { 269 | self.switch_mode(Mode::Normal); 270 | } 271 | EditorEvent::DrawText 272 | } 273 | Cmd::Yank(None) => { 274 | todo!() 275 | } 276 | // Command parser should only return repeated movement commands 277 | Cmd::Repeat { count, cmd } => self.repeated_cmd(*count, cmd), 278 | Cmd::Move(mv) => { 279 | self.movement(mv); 280 | EditorEvent::DrawCursor 281 | } 282 | _ => panic!( 283 | "Only Delete/Change/Yank/Repetition/Movement commands are valid in visual mode" 284 | ), 285 | } 286 | } 287 | 288 | fn handle_cmd_normal(&mut self, cmd: &Cmd) -> EditorEvent { 289 | match cmd { 290 | Cmd::Undo => { 291 | self.undo(); 292 | EditorEvent::DrawText 293 | } 294 | Cmd::Redo => { 295 | self.redo(); 296 | EditorEvent::DrawText 297 | } 298 | Cmd::SwitchMode(mode) => { 299 | self.switch_mode(*mode); 300 | EditorEvent::DrawCursor 301 | } 302 | Cmd::Repeat { count, cmd } => self.repeated_cmd(*count, cmd), 303 | Cmd::Delete(None) => { 304 | self.delete_line(self.line); 305 | EditorEvent::DrawText 306 | } 307 | Cmd::Delete(Some(mv)) => { 308 | self.delete_mv(mv); 309 | EditorEvent::DrawText 310 | } 311 | Cmd::Change(None) => { 312 | self.switch_mode(Mode::Insert); 313 | self.delete_line(self.line); 314 | EditorEvent::DrawText 315 | } 316 | Cmd::Change(Some(mv)) => { 317 | self.switch_mode(Mode::Insert); 318 | self.delete_mv(mv); 319 | EditorEvent::DrawText 320 | } 321 | Cmd::Move(mv) => { 322 | self.movement(mv); 323 | EditorEvent::DrawCursor 324 | } 325 | Cmd::NewLine(NewLine { up, switch_mode }) => { 326 | if *switch_mode { 327 | self.switch_mode(Mode::Insert); 328 | } 329 | 330 | if !up { 331 | self.new_line(); 332 | } else { 333 | self.new_line_before(); 334 | } 335 | 336 | EditorEvent::DrawText 337 | } 338 | Cmd::SwitchMove(mv) => { 339 | self.switch_mode(Mode::Insert); 340 | // Doing `a` at the last char at the end should have same behaviour 341 | // as doing `A`, meaning we should put cursor under the new-line character (next pos) 342 | if self.movement(mv) { 343 | self.move_pos(usize::MAX); 344 | } 345 | EditorEvent::DrawCursor 346 | } 347 | r => todo!("Unimplemented: {:?}", r), 348 | } 349 | } 350 | 351 | fn repeated_cmd(&mut self, count: u16, cmd: &Cmd) -> EditorEvent { 352 | let mut ret = EditorEvent::DrawCursor; 353 | for _ in 0..count { 354 | ret = self.handle_cmd(cmd); 355 | } 356 | ret 357 | } 358 | 359 | /// Returns true if the movement was truncated (it exceeded the end of the line 360 | /// and stopped). 361 | fn movement(&mut self, mv: &Move) -> bool { 362 | match mv { 363 | Move::Word(skip_punctuation) => self.next_word( 364 | MoveWord { 365 | kind: MoveWordKind::Next, 366 | skip_punctuation: *skip_punctuation, 367 | }, 368 | self.line, 369 | self.cursor, 370 | false, 371 | ), 372 | Move::BeginningWord(skip_punctuation) => self.next_word( 373 | MoveWord { 374 | kind: MoveWordKind::Prev, 375 | skip_punctuation: *skip_punctuation, 376 | }, 377 | self.line, 378 | self.cursor, 379 | false, 380 | ), 381 | Move::EndWord(skip_punctuation) => self.next_word( 382 | MoveWord { 383 | kind: MoveWordKind::End, 384 | skip_punctuation: *skip_punctuation, 385 | }, 386 | self.line, 387 | self.cursor, 388 | false, 389 | ), 390 | Move::Start => { 391 | self.cursor = 0; 392 | self.line = 0; 393 | } 394 | Move::End => { 395 | self.line = if self.lines.is_empty() { 396 | 0 397 | } else { 398 | self.lines.len() - 1 399 | }; 400 | self.cursor = 0; 401 | } 402 | Move::Up => self.up(1), 403 | Move::Down => self.down(1), 404 | Move::Left => self.left(1), 405 | Move::Right => return self.right(1), 406 | Move::LineStart => self.move_pos(0), 407 | Move::LineEnd => self.move_pos(usize::MAX), 408 | Move::Repeat { count, mv } => { 409 | // TODO: We can be smarter about this and pass 410 | // the count into the movement, ex. `10l` -> `self.right(10). 411 | // 412 | // Additionally, we can stop early for movements like `$` or `0` 413 | // where repetitions don't affect the cursor anymore. 414 | for _ in 0..*count { 415 | if self.movement(mv) { 416 | return true; 417 | } 418 | } 419 | } 420 | Move::Find(c, reverse) => { 421 | self.cursor = self.find_line(*c, !reverse).unwrap_or(self.cursor); 422 | } 423 | Move::ParagraphBegin => { 424 | self.line = self.prev_paragraph(); 425 | self.sync_line_cursor(); 426 | } 427 | Move::ParagraphEnd => { 428 | self.line = self.next_paragraph(); 429 | self.sync_line_cursor(); 430 | } 431 | }; 432 | false 433 | } 434 | } 435 | 436 | // This impl contains text changing utilities 437 | impl Editor { 438 | fn delete_selection(&mut self) { 439 | if let Some((start, end)) = self.selection { 440 | use Ordering::*; 441 | 442 | match start.cmp(&end) { 443 | Equal | Less => self.delete_range(start as usize..end as usize), 444 | Greater => self.delete_range(end as usize..start as usize), 445 | } 446 | } 447 | } 448 | 449 | fn delete_mv(&mut self, mv: &Move) { 450 | let cursor = self.cursor; 451 | let line = self.line; 452 | let start = self.pos(); 453 | let truncated_eol = self.movement(mv); 454 | let mut end = self.pos(); 455 | 456 | if truncated_eol { 457 | end = self.pos() + 1; 458 | } 459 | 460 | match start.cmp(&end) { 461 | Ordering::Equal => self.delete_range(start..(start + 1)), 462 | Ordering::Less => self.delete_range(start..end), 463 | Ordering::Greater => self.delete_range(end..start), 464 | } 465 | 466 | // Return cursor back to starting position 467 | // TODO: This breaks if we delete backwards for example `d{` 468 | self.cursor = cursor; 469 | self.line = line; 470 | } 471 | 472 | fn insert(&mut self, text: &str) { 473 | let pos = self.pos(); 474 | 475 | self.text.insert(pos, text); 476 | self.cursor += text.len(); 477 | self.lines[self.line] += text.len() as u32; 478 | 479 | let char = text.chars().next().unwrap(); 480 | match self.edits.last_mut() { 481 | _ if self.had_space => { 482 | let vec = vec![char]; 483 | self.edit_vecs.push(vec); 484 | let idx = self.edit_vecs.len() - 1; 485 | self.edits.push(Edit::Insertion { 486 | start: Cell::new(pos as u32), 487 | str_idx: idx as u32, 488 | }); 489 | self.had_space = false; 490 | } 491 | Some(Edit::Insertion { str_idx: str, .. }) => { 492 | let is_space = text == " "; 493 | self.edit_vecs[*str as usize].push(char); 494 | if is_space { 495 | self.had_space = true; 496 | } 497 | } 498 | None | Some(Edit::Deletion { .. }) => { 499 | self.edit_vecs.push(vec![char]); 500 | self.edits.push(Edit::Insertion { 501 | start: Cell::new(pos as u32), 502 | str_idx: self.edit_vecs.len() as u32 - 1, 503 | }) 504 | } 505 | } 506 | // Invalidate redo stack if we make an edit 507 | if !self.redos.is_empty() { 508 | self.redos.clear() 509 | } 510 | } 511 | 512 | fn backspace(&mut self) -> EditorEvent { 513 | if self.cursor == 0 && self.line == 0 { 514 | return EditorEvent::Nothing; 515 | } 516 | 517 | let pos = self.pos(); 518 | let removed: Option = if self.text.len_chars() > 0 { 519 | let c = self.text.char(if pos == 0 { 0 } else { pos - 1 }); 520 | self.text.remove(pos - 1..pos); 521 | Some(c) 522 | } else { 523 | None 524 | }; 525 | self.cursor = if self.cursor > 0 { 526 | self.lines[self.line] -= 1; 527 | self.cursor - 1 528 | } else if self.line > 0 { 529 | // Backspacing into previous line 530 | let merge_line = self.lines.remove(self.line); 531 | self.line -= 1; 532 | self.lines[self.line] += merge_line; 533 | self.lines[self.line] as usize 534 | } else { 535 | 0 536 | }; 537 | if let Some(c) = removed { 538 | match self.edits.last_mut() { 539 | Some(Edit::Deletion { start, str_idx }) => { 540 | let val = start.get(); 541 | if val > 0 { 542 | start.set(val - 1) 543 | } 544 | self.edit_vecs[*str_idx as usize].push(c); 545 | } 546 | None | Some(Edit::Insertion { .. }) => { 547 | self.edit_vecs.push(vec![c]); 548 | self.edits.push(Edit::Deletion { 549 | start: Cell::new(pos as u32 - 1), 550 | str_idx: self.edit_vecs.len() as u32 - 1, 551 | }); 552 | } 553 | } 554 | } 555 | EditorEvent::DrawText 556 | } 557 | 558 | /// Delete chars in a range. 559 | /// 560 | /// ### Normal mode 561 | /// If the range spans multiple lines then we just apply it to the entire line, 562 | /// this is the same behaviour demonstrated by Vim. For example, try the command 563 | /// `3dj` this will delete the next 3 lines in totality. It doesn't split the lines up. 564 | /// 565 | /// ### Visual mode 566 | /// Behaves as expected, cutting and splicing lines instead of deleting them in totality 567 | #[inline] 568 | fn delete_range(&mut self, range: Range) { 569 | let (start, end) = match self.mode { 570 | // Start and ending lines 571 | Mode::Normal => ( 572 | self.text.char_to_line(range.start), 573 | self.text.char_to_line(range.end), 574 | ), 575 | Mode::Visual => (range.start, range.end), 576 | Mode::Insert => panic!("delete_range should not be called in insert mode"), 577 | }; 578 | 579 | if start == end { 580 | self.text.remove(range); 581 | self.lines[start] = self.line_count(start) as u32; 582 | } else if matches!(self.mode, Mode::Normal) { 583 | let start = self.text.line_to_char(start); 584 | let end = self.text.line_to_char(end) + self.text.line(end).len_chars(); 585 | 586 | self.text.remove(start..end); 587 | 588 | let mut i = start; 589 | for _ in start..(end + 1) { 590 | if self.lines.is_empty() { 591 | break; 592 | } 593 | self.lines.remove(i); 594 | if i >= self.lines.len() && !self.lines.is_empty() { 595 | i -= 1; 596 | } 597 | } 598 | } else { 599 | let line_pos = self.text.char_to_line(start); 600 | 601 | self.text.remove(start..end); 602 | 603 | // TODO: Be smarter about this and only compute the lines affected 604 | self.lines = text_to_lines(self.text.chars()); 605 | 606 | self.line = line_pos; 607 | self.cursor = start - self.text.line_to_char(line_pos); 608 | } 609 | } 610 | 611 | fn delete_line(&mut self, line: usize) { 612 | let pos = self.line_pos(); 613 | if self.lines.len() > 1 { 614 | let len = 615 | // Include new line character, except if we one the last line which doesn't have it 616 | if line == (self.lines.len() - 1) { 0 } else { 1 } + self.lines.remove(line); 617 | 618 | self.text.remove(pos..(pos + len as usize)) 619 | } else { 620 | self.lines[0] = 0; 621 | // Including \n from the last line 622 | self.text.remove(0..self.text.len_chars()); 623 | self.cursor = 0; 624 | } 625 | } 626 | 627 | /// Insert a new line and splitting the current one based on the cursor position 628 | fn enter(&mut self) { 629 | match self.lines[self.line] { 630 | 0 => { 631 | if self.cursor == 0 { 632 | self.new_line(); 633 | return; 634 | } 635 | } 636 | r => { 637 | if self.cursor == r as usize { 638 | self.new_line(); 639 | return; 640 | } 641 | } 642 | } 643 | let pos = self.pos(); 644 | self.text.insert(pos, "\n"); 645 | 646 | let new_line_count = self.lines[self.line] as usize - self.cursor; 647 | self.lines[self.line] = self.cursor as u32; 648 | 649 | self.line += 1; 650 | 651 | if self.line >= self.lines.len() { 652 | self.lines.push(new_line_count as u32) 653 | } else { 654 | self.lines.insert(self.line, new_line_count as u32); 655 | } 656 | 657 | self.cursor = 0; 658 | } 659 | 660 | fn add_whitespace(&mut self, pos: usize, count: usize) { 661 | for i in 0..count { 662 | self.text.insert_char(pos + i, ' '); 663 | } 664 | } 665 | 666 | // Insert a new line 667 | fn new_line(&mut self) { 668 | let is_last = self.line == self.lines.len() - 1; 669 | let mut pos = 670 | self.line_pos() + self.lines[self.line] as usize + if is_last { 0 } else { 1 }; 671 | if is_last { 672 | self.text.insert(pos, "\n"); 673 | pos += 1; 674 | } 675 | let count = self 676 | .text 677 | .line(self.line) 678 | .chars() 679 | .enumerate() 680 | .find_map(|(i, c)| if c != ' ' { Some(i) } else { None }) 681 | .unwrap_or(0); 682 | self.add_whitespace(pos, count); 683 | if !is_last { 684 | self.text.insert(pos + count, "\n"); 685 | } 686 | 687 | self.cursor = count; 688 | self.line += 1; 689 | 690 | if self.line >= self.lines.len() { 691 | self.lines.push(count as u32) 692 | } else { 693 | self.lines.insert(self.line, count as u32); 694 | } 695 | } 696 | 697 | fn new_line_before(&mut self) { 698 | let pos = self.line_pos(); 699 | // The new line character of previous line 700 | let pos = if pos == 0 { 0 } else { pos }; 701 | 702 | let count = self 703 | .text 704 | .line(self.line) 705 | .chars() 706 | .enumerate() 707 | .find_map(|(i, c)| if c != ' ' { Some(i) } else { None }) 708 | .unwrap_or(0); 709 | 710 | self.cursor = count; 711 | 712 | self.add_whitespace(pos, count); 713 | self.text.insert(pos + count, "\n"); 714 | 715 | self.line = if self.line == 0 { 0 } else { self.line }; 716 | 717 | self.lines.insert(self.line, count as u32); 718 | } 719 | } 720 | 721 | // This impl contains movement utilities 722 | impl Editor { 723 | fn word_indicies( 724 | &mut self, 725 | mut start: usize, 726 | mut end: usize, 727 | chars: Vec, 728 | skip_punctuation: bool, 729 | ) -> Vec<(usize, usize)> { 730 | let len = chars.len(); 731 | let mut idxs: Vec<(usize, usize)> = Vec::new(); 732 | let mut searching_start = false; 733 | 734 | while end < len && start < len { 735 | if searching_start { 736 | if chars[start] == ' ' { 737 | start += 1; 738 | } else { 739 | searching_start = false; 740 | end = start + 1; 741 | } 742 | } else { 743 | if Editor::is_word_separator(chars[end], skip_punctuation) { 744 | idxs.push((start, end)); 745 | searching_start = true; 746 | start = end; 747 | } 748 | end += 1; 749 | } 750 | } 751 | 752 | if !searching_start { 753 | idxs.push((start, end)); 754 | } 755 | 756 | idxs 757 | } 758 | 759 | fn next_word(&mut self, mv: MoveWord, line: usize, mut cursor: usize, match_first_word: bool) { 760 | use MoveWordKind::*; 761 | let is_not_last = match mv.kind { 762 | Next | End => line < (self.lines.len() - 1), 763 | Prev => line > 0, 764 | }; 765 | 766 | if self.lines[line] == 0 { 767 | if is_not_last { 768 | match mv.kind { 769 | Next | End => self.next_word(mv, line + 1, 0, true), 770 | Prev => self.next_word(mv, line - 1, usize::MAX, true), 771 | } 772 | } 773 | return; 774 | } 775 | 776 | let chars: Vec = match mv.kind { 777 | Next | End => self.text.line(line).chars().collect(), 778 | Prev => { 779 | let mut chars: Vec = self.text.line(line).chars().collect(); 780 | chars.reverse(); 781 | chars 782 | } 783 | }; 784 | let len = chars.len(); 785 | if cursor > len { 786 | cursor = len - 1; 787 | } 788 | 789 | let start = self 790 | .text 791 | .line(line) 792 | .chars() 793 | .enumerate() 794 | .skip(if matches!(mv.kind, Prev) { 795 | len - cursor 796 | } else { 797 | cursor 798 | }) 799 | .find_map(|(i, c)| { 800 | if Editor::is_word_separator(c, mv.skip_punctuation) { 801 | None 802 | } else { 803 | Some(i) 804 | } 805 | }); 806 | 807 | if start.is_none() { 808 | if is_not_last { 809 | match mv.kind { 810 | Next | End => self.next_word(mv, line + 1, 0, true), 811 | Prev => self.next_word(mv, line - 1, usize::MAX, true), 812 | }; 813 | } 814 | return; 815 | } 816 | 817 | let start = unsafe { start.unwrap_unchecked() }; 818 | 819 | let end = start + 1; 820 | if end >= len { 821 | if is_not_last { 822 | match mv.kind { 823 | Next | End => self.next_word(mv, line + 1, 0, true), 824 | Prev => self.next_word(mv, line - 1, usize::MAX, true), 825 | }; 826 | } 827 | return; 828 | } 829 | 830 | let idxs: Vec<(usize, usize)> = { 831 | let idxs = self.word_indicies(start, end, chars, mv.skip_punctuation); 832 | if matches!(mv.kind, Prev) { 833 | idxs.into_iter() 834 | .map(|(start, end)| (len - end, len - start)) 835 | .collect() 836 | } else { 837 | idxs 838 | } 839 | }; 840 | 841 | match idxs.len() { 842 | // If no words on line move to first word of nex line 843 | 0 => { 844 | if is_not_last { 845 | match mv.kind { 846 | Next | End => self.next_word(mv, line + 1, 0, true), 847 | Prev => self.next_word(mv, line - 1, usize::MAX, true), 848 | } 849 | } 850 | } 851 | // If 1 words on line move to first word of next line if there are more lines, 852 | // otherwise move to last char of word 853 | 1 => { 854 | let (start, end) = idxs[0]; 855 | if cursor >= start && cursor < end { 856 | if is_not_last { 857 | match mv.kind { 858 | Next | End => self.next_word(mv, line + 1, 0, true), 859 | Prev => self.next_word(mv, line - 1, usize::MAX, true), 860 | } 861 | } else { 862 | self.cursor = end - 1; 863 | self.line = line; 864 | } 865 | } else { 866 | self.cursor = start; 867 | self.line = line; 868 | } 869 | } 870 | _ => { 871 | let (start, end) = idxs[0]; 872 | 873 | if match_first_word { 874 | self.cursor = start; 875 | self.line = line; 876 | } else if cursor >= start && cursor < end { 877 | self.cursor = if matches!(mv.kind, End) { 878 | let new = idxs[0].1 - 1; 879 | if self.cursor == new { 880 | idxs[1].1 - 1 881 | } else { 882 | new 883 | } 884 | } else { 885 | idxs[1].0 886 | }; 887 | self.line = line; 888 | } else { 889 | self.cursor = if matches!(mv.kind, End) { 890 | end - 1 891 | } else { 892 | start 893 | }; 894 | self.line = line; 895 | } 896 | } 897 | } 898 | } 899 | 900 | /// Return line of the previous paragraph 901 | #[inline] 902 | fn prev_paragraph(&mut self) -> usize { 903 | if self.line == 0 { 904 | return 0; 905 | } 906 | 907 | self.lines[0..self.line - 1] 908 | .iter() 909 | .enumerate() 910 | .rev() 911 | .find(|(_, c)| **c == 0) 912 | .map_or(0, |(l, _)| l as usize) 913 | } 914 | 915 | #[inline] 916 | fn next_paragraph(&mut self) -> usize { 917 | if self.line == self.lines.len() - 1 { 918 | return self.line; 919 | } 920 | 921 | self.lines 922 | .iter() 923 | .enumerate() 924 | .skip(self.line + 1) 925 | .find(|(_, c)| **c == 0) 926 | .map_or(self.lines.len() - 1, |(l, _)| l as usize) 927 | } 928 | 929 | #[inline] 930 | fn find_line(&mut self, char: char, forwards: bool) -> Option { 931 | if forwards { 932 | self.text 933 | .line(self.line) 934 | .chars() 935 | .skip(self.cursor + 1) 936 | .enumerate() 937 | .find(|(_, c)| *c == char) 938 | .map(|(pos, _)| self.cursor + pos + 1) 939 | } else { 940 | let chars: Vec = self.text.line(self.line).chars().collect(); 941 | for i in (0..self.cursor).rev() { 942 | if chars[i] == char { 943 | return Some(i); 944 | } 945 | } 946 | None 947 | } 948 | } 949 | 950 | #[inline] 951 | fn up(&mut self, count: usize) { 952 | if count > self.line { 953 | self.line = 0; 954 | } else { 955 | self.line -= count; 956 | } 957 | self.sync_line_cursor(); 958 | } 959 | 960 | #[inline] 961 | fn down(&mut self, count: usize) { 962 | if self.line + count >= self.lines.len() { 963 | self.line = self.lines.len() - 1; 964 | } else { 965 | self.line += count; 966 | } 967 | self.sync_line_cursor(); 968 | } 969 | 970 | /// Returns true if attempted to move more characters than the line has 971 | #[inline] 972 | fn right(&mut self, count: usize) -> bool { 973 | let c = self.lines[self.line] as usize; 974 | if self.cursor + count >= c { 975 | self.cursor = if c == 0 { 0 } else { c - 1 }; 976 | true 977 | } else { 978 | self.cursor += count; 979 | false 980 | } 981 | } 982 | 983 | fn move_pos(&mut self, pos: usize) { 984 | if pos > self.lines[self.line] as usize { 985 | // Put it on the newline char (the space after the last char of the line), 986 | // but only on insert mode. This is Vim behaviour 987 | self.cursor = self.lines[self.line] as usize; 988 | if matches!(self.mode, Mode::Normal) && self.lines[self.line] > 0 { 989 | self.cursor -= 1; 990 | } 991 | } else { 992 | self.cursor = pos; 993 | } 994 | } 995 | 996 | #[inline] 997 | fn left(&mut self, count: usize) { 998 | if count > self.cursor { 999 | self.cursor = 0; 1000 | } else { 1001 | self.cursor -= count; 1002 | } 1003 | } 1004 | 1005 | #[inline] 1006 | fn sync_line_cursor(&mut self) { 1007 | let line_count = self.lines[self.line] as usize; 1008 | if line_count == 0 { 1009 | self.cursor = 0; 1010 | } else if self.cursor >= line_count { 1011 | self.cursor = line_count - 1; 1012 | } 1013 | } 1014 | } 1015 | 1016 | // This impl contains undo/redo utility functions 1017 | impl Editor { 1018 | #[inline] 1019 | fn undo(&mut self) { 1020 | if let Some(edit) = self.edits.pop() { 1021 | let inversion = edit.invert(); 1022 | self.redos.push(edit); 1023 | self.apply_edit(inversion) 1024 | } 1025 | } 1026 | 1027 | #[inline] 1028 | fn redo(&mut self) { 1029 | if let Some(edit) = self.redos.pop() { 1030 | self.edits.push(edit.clone()); 1031 | self.apply_edit(edit); 1032 | } 1033 | } 1034 | 1035 | #[inline] 1036 | fn apply_edit(&mut self, edit: Edit) { 1037 | match edit { 1038 | Edit::Deletion { start, str_idx } => { 1039 | let len = self.edit_vecs[str_idx as usize].len(); 1040 | let start = start.get() as usize; 1041 | self.text.remove(start..(start + len)); 1042 | } 1043 | Edit::Insertion { start, str_idx } => { 1044 | let str = self.edit_vecs[str_idx as usize].iter().collect::(); 1045 | self.text.insert(start.get() as usize, &str); 1046 | } 1047 | }; 1048 | // TODO: Be smarter about this and only compute the lines affected 1049 | self.lines = text_to_lines(self.text.chars()); 1050 | } 1051 | } 1052 | 1053 | // This impl contains generic utility functions 1054 | impl Editor { 1055 | #[inline] 1056 | fn switch_mode(&mut self, mode: Mode) { 1057 | match (self.mode, mode) { 1058 | (Mode::Insert, Mode::Normal) => { 1059 | // If we are switching from insert to normal mode and we are on the new-line character, 1060 | // move it back since we disallow that in normal mode 1061 | if self.cursor == self.lines[self.line] as usize && self.cursor > 0 { 1062 | self.cursor -= 1; 1063 | } 1064 | self.mode = mode; 1065 | self.vim.set_mode(mode); 1066 | } 1067 | (Mode::Normal, Mode::Visual) => { 1068 | let pos = self.pos() as u32; 1069 | self.selection = Some((pos, pos)); 1070 | self.mode = mode; 1071 | self.vim.set_mode(mode); 1072 | } 1073 | // Hitting `v` in visual mode should return to normal mode 1074 | (Mode::Visual, Mode::Visual) => { 1075 | self.selection = None; 1076 | self.mode = Mode::Normal; 1077 | self.vim.set_mode(mode); 1078 | } 1079 | // Switching to visual mode only allowed from normal mode 1080 | (_, Mode::Visual) => {} 1081 | (Mode::Visual, _) => { 1082 | self.selection = None; 1083 | self.mode = mode; 1084 | self.vim.set_mode(mode); 1085 | } 1086 | (_, _) => { 1087 | self.mode = mode; 1088 | self.vim.set_mode(mode); 1089 | } 1090 | } 1091 | } 1092 | 1093 | #[inline] 1094 | pub fn within_selection(&self, i: u32) -> bool { 1095 | if let Some((start, end)) = self.selection { 1096 | match start.cmp(&end) { 1097 | Ordering::Less => i >= start && i <= end, 1098 | Ordering::Greater | Ordering::Equal => i >= end && i <= start, 1099 | } 1100 | } else { 1101 | false 1102 | } 1103 | } 1104 | 1105 | #[inline] 1106 | pub fn past_selection(&self, i: u32) -> bool { 1107 | if let Some((start, end)) = self.selection { 1108 | match start.cmp(&end) { 1109 | Ordering::Less => i > end, 1110 | Ordering::Greater | Ordering::Equal => i > start, 1111 | } 1112 | } else { 1113 | false 1114 | } 1115 | } 1116 | 1117 | #[inline] 1118 | pub fn selection(&self) -> Option<(u32, u32)> { 1119 | self.selection 1120 | } 1121 | 1122 | #[inline] 1123 | pub fn text(&self, range: Range) -> RopeSlice { 1124 | self.text.slice(range) 1125 | } 1126 | 1127 | #[inline] 1128 | pub fn text_line_col(&self, range_start: lsp::Position, range_end: lsp::Position) -> RopeSlice { 1129 | // Needs to be at the start of the line because when drawing diagnostics 1130 | // we need to calculate the width from beginning since some chars might 1131 | // have different widths 1132 | let start = self.text.line_to_char(range_start.line as usize); 1133 | let end = self.text.line_to_char(range_end.line as usize) + range_end.character as usize; 1134 | self.text.slice(start..end) 1135 | } 1136 | 1137 | #[inline] 1138 | pub fn text_all(&self) -> RopeSlice { 1139 | self.text.slice(0..self.text.len_chars()) 1140 | } 1141 | 1142 | #[inline] 1143 | fn text_str(&self) -> Option<&str> { 1144 | self.text_all().as_str() 1145 | } 1146 | 1147 | #[inline] 1148 | pub fn line(&self) -> usize { 1149 | self.line as usize 1150 | } 1151 | 1152 | #[inline] 1153 | pub fn lines(&self) -> &[u32] { 1154 | &self.lines 1155 | } 1156 | 1157 | #[inline] 1158 | pub fn set_line(&mut self, pos: usize) { 1159 | self.line = pos 1160 | } 1161 | 1162 | #[inline] 1163 | pub fn incr_line(&mut self, pos: i32) { 1164 | self.line += pos as usize; 1165 | } 1166 | 1167 | #[inline] 1168 | fn len(&self) -> usize { 1169 | self.text.len_chars() 1170 | } 1171 | 1172 | #[inline] 1173 | fn is_empty(&self) -> bool { 1174 | self.len() == 0 1175 | } 1176 | 1177 | #[inline] 1178 | pub fn cursor(&self) -> usize { 1179 | self.cursor 1180 | } 1181 | 1182 | #[inline] 1183 | fn pos(&self) -> usize { 1184 | self.line_pos() + self.cursor 1185 | } 1186 | 1187 | #[inline] 1188 | fn line_pos(&self) -> usize { 1189 | if self.lines.len() == 1 { 1190 | 0 1191 | } else { 1192 | // Summation of every line before it + 1 for the new line character 1193 | self.lines[0..self.line] 1194 | .iter() 1195 | .fold(0, |acc, line| acc + 1 + *line as usize) 1196 | } 1197 | } 1198 | 1199 | /// Calculate the amount of chars in the given line (excluding new line characters) 1200 | #[inline] 1201 | fn line_count(&self, idx: usize) -> usize { 1202 | if self.lines.is_empty() { 1203 | 0 1204 | } else if idx == self.lines.len() - 1 { 1205 | // If it's the last line then we don't need to subtract the newline character from the count 1206 | self.text.line(idx).len_chars() 1207 | } else { 1208 | // Subtract the new line character from the count 1209 | self.text.line(idx).len_chars() - 1 1210 | } 1211 | } 1212 | 1213 | #[inline] 1214 | pub fn is_insert(&self) -> bool { 1215 | matches!(self.mode, Mode::Insert) 1216 | } 1217 | 1218 | fn is_word_separator(c: char, skip_punctuation: bool) -> bool { 1219 | match c { 1220 | ' ' => true, 1221 | '_' => false, 1222 | _ if !skip_punctuation => !c.is_alphanumeric(), 1223 | _ => false, 1224 | } 1225 | } 1226 | 1227 | #[inline] 1228 | pub fn take_multiple_event_data(&mut self) -> [EditorEvent; 3] { 1229 | std::mem::replace(&mut self.multiple_events_data, [EditorEvent::Nothing; 3]) 1230 | } 1231 | 1232 | #[inline] 1233 | fn set_multiple_event_data(&mut self, evts: [EditorEvent; 3]) { 1234 | self.multiple_events_data = evts; 1235 | } 1236 | 1237 | #[inline] 1238 | pub fn line_idx(&self, line: usize) -> usize { 1239 | self.text.line_to_char(line) 1240 | } 1241 | 1242 | #[inline] 1243 | pub fn line_char_idx(&self, line: usize, char: usize) -> usize { 1244 | self.line_idx(line) + char 1245 | } 1246 | } 1247 | 1248 | impl Default for Editor { 1249 | fn default() -> Self { 1250 | Self::new() 1251 | } 1252 | } 1253 | 1254 | #[cfg(test)] 1255 | mod tests { 1256 | use super::*; 1257 | 1258 | #[cfg(test)] 1259 | mod text_to_lines { 1260 | use super::*; 1261 | 1262 | #[test] 1263 | fn empty_line() { 1264 | assert_eq!(vec![0], text_to_lines("".chars())); 1265 | } 1266 | 1267 | #[test] 1268 | fn single_line() { 1269 | let text = "one line"; 1270 | assert_eq!(vec![text.len() as u32], text_to_lines(text.chars())); 1271 | } 1272 | 1273 | #[test] 1274 | fn multiple_lines() { 1275 | let text = "line 1\nline 2"; 1276 | assert_eq!(vec![6, 6], text_to_lines(text.chars())); 1277 | } 1278 | 1279 | #[test] 1280 | fn trailing_newline() { 1281 | let text = "line 1\n"; 1282 | assert_eq!(vec![6, 0], text_to_lines(text.chars())); 1283 | } 1284 | 1285 | #[test] 1286 | fn leading_newline() { 1287 | let text = "\nline 1\n"; 1288 | assert_eq!(vec![0, 6, 0], text_to_lines(text.chars())); 1289 | } 1290 | } 1291 | 1292 | #[cfg(test)] 1293 | mod movement { 1294 | use super::*; 1295 | 1296 | #[test] 1297 | fn sync_lines() { 1298 | // Should not exceed line length 1299 | let mut editor = Editor::new(); 1300 | editor.insert("1"); 1301 | editor.insert("2"); 1302 | editor.enter(); 1303 | editor.insert("1"); 1304 | editor.insert("2"); 1305 | editor.insert("3"); 1306 | editor.up(1); 1307 | 1308 | assert_eq!(editor.cursor, 1); 1309 | } 1310 | } 1311 | 1312 | #[cfg(test)] 1313 | mod edit { 1314 | use super::*; 1315 | 1316 | #[cfg(test)] 1317 | mod delete_range { 1318 | use super::*; 1319 | 1320 | #[test] 1321 | fn single_line() { 1322 | let mut editor = Editor::new(); 1323 | editor.insert("1"); 1324 | editor.enter(); 1325 | editor.insert("1"); 1326 | let start = editor.pos(); 1327 | editor.insert("2"); 1328 | editor.insert("3"); 1329 | let end = editor.pos(); 1330 | editor.enter(); 1331 | editor.insert("1"); 1332 | editor.up(1); 1333 | editor.cursor = 0; 1334 | 1335 | editor.delete_range(start..end); 1336 | assert_eq!(editor.text_str().unwrap(), "1\n1\n1"); 1337 | assert_eq!(editor.lines, vec![1, 1, 1]); 1338 | } 1339 | 1340 | #[test] 1341 | fn single_line_full() { 1342 | let mut editor = Editor::new(); 1343 | editor.insert("1"); 1344 | editor.enter(); 1345 | let start = editor.pos(); 1346 | editor.insert("1"); 1347 | editor.insert("2"); 1348 | editor.insert("3"); 1349 | let end = editor.pos(); 1350 | editor.enter(); 1351 | editor.insert("1"); 1352 | editor.up(1); 1353 | editor.cursor = 0; 1354 | 1355 | editor.delete_range(start..end); 1356 | assert_eq!(editor.text_str().unwrap(), "1\n\n1"); 1357 | assert_eq!(editor.lines, vec![1, 0, 1]); 1358 | } 1359 | 1360 | #[test] 1361 | fn multi_line() { 1362 | let mut editor = Editor::new(); 1363 | editor.insert("1"); 1364 | editor.enter(); 1365 | editor.insert("1"); 1366 | editor.insert("2"); 1367 | editor.insert("3"); 1368 | editor.enter(); 1369 | editor.insert("1"); 1370 | editor.up(1); 1371 | let start = editor.pos(); 1372 | editor.down(1); 1373 | let end = editor.pos(); 1374 | 1375 | editor.delete_range(start..end); 1376 | assert_eq!(editor.text_str().unwrap(), "1\n"); 1377 | assert_eq!(editor.lines, vec![1]); 1378 | } 1379 | 1380 | #[test] 1381 | fn entire_text() { 1382 | let mut editor = Editor::new(); 1383 | editor.insert("1"); 1384 | editor.insert("2"); 1385 | editor.insert("3"); 1386 | editor.enter(); 1387 | editor.insert("1"); 1388 | editor.insert("2"); 1389 | editor.insert("3"); 1390 | editor.enter(); 1391 | editor.insert("1"); 1392 | editor.insert("2"); 1393 | editor.insert("3"); 1394 | 1395 | // move to start 1396 | editor.cursor = 0; 1397 | editor.line = 0; 1398 | let start = editor.pos(); 1399 | editor.line = editor.lines.len() - 1; 1400 | let end = editor.pos(); 1401 | 1402 | editor.delete_range(start..end); 1403 | assert_eq!(editor.text_str().unwrap(), ""); 1404 | assert_eq!(editor.lines, Vec::::new()); 1405 | } 1406 | } 1407 | 1408 | #[test] 1409 | fn delete_line_first() { 1410 | let mut editor = Editor::new(); 1411 | editor.insert("1"); 1412 | editor.enter(); 1413 | editor.insert("1"); 1414 | editor.enter(); 1415 | editor.insert("1"); 1416 | editor.up(2); 1417 | editor.delete_line(0); 1418 | 1419 | assert_eq!(editor.lines, vec![1, 1]); 1420 | } 1421 | 1422 | #[test] 1423 | fn delete_line_middle() { 1424 | let mut editor = Editor::new(); 1425 | editor.insert("1"); 1426 | editor.enter(); 1427 | editor.insert("1"); 1428 | editor.insert("1"); 1429 | editor.insert("1"); 1430 | editor.enter(); 1431 | editor.insert("1"); 1432 | editor.insert("2"); 1433 | editor.up(1); 1434 | editor.delete_line(1); 1435 | 1436 | assert_eq!(editor.lines, vec![1, 2]); 1437 | } 1438 | 1439 | #[test] 1440 | fn delete_line_last() { 1441 | let mut editor = Editor::new(); 1442 | editor.insert("1"); 1443 | editor.enter(); 1444 | editor.insert("1"); 1445 | editor.enter(); 1446 | editor.insert("1"); 1447 | editor.insert("2"); 1448 | editor.delete_line(2); 1449 | 1450 | assert_eq!(editor.lines, vec![1, 1]); 1451 | } 1452 | 1453 | #[test] 1454 | fn backspace_beginning_in_between_line() { 1455 | let mut editor = Editor::new(); 1456 | editor.insert("1"); 1457 | editor.insert("2"); 1458 | editor.insert("3"); 1459 | editor.enter(); 1460 | editor.insert("1"); 1461 | editor.enter(); 1462 | editor.insert("1"); 1463 | editor.up(1); 1464 | editor.left(1); 1465 | 1466 | assert_eq!(editor.backspace(), EditorEvent::DrawText); 1467 | assert_eq!(editor.lines, vec![4, 1]); 1468 | } 1469 | 1470 | #[test] 1471 | fn enter_in_between() { 1472 | let mut editor = Editor::new(); 1473 | editor.insert("1"); 1474 | editor.insert("2"); 1475 | editor.insert("3"); 1476 | editor.cursor = 2; 1477 | 1478 | editor.enter(); 1479 | assert_eq!(editor.lines, vec![2, 1]); 1480 | } 1481 | 1482 | #[test] 1483 | fn enter_beginning() { 1484 | let mut editor = Editor::new(); 1485 | editor.insert("1"); 1486 | editor.insert("2"); 1487 | editor.insert("3"); 1488 | editor.cursor = 0; 1489 | 1490 | editor.enter(); 1491 | assert_eq!(editor.lines, vec![0, 3]); 1492 | } 1493 | 1494 | #[test] 1495 | fn enter_end() { 1496 | let mut editor = Editor::new(); 1497 | editor.insert("1"); 1498 | editor.insert("2"); 1499 | editor.insert("3"); 1500 | editor.cursor = 3; 1501 | 1502 | editor.enter(); 1503 | assert_eq!(editor.lines, vec![3, 0]); 1504 | } 1505 | } 1506 | } 1507 | --------------------------------------------------------------------------------