├── src ├── core │ ├── workspace │ │ ├── mod.rs │ │ ├── build.rs │ │ └── project.rs │ ├── regex.rs │ ├── bindings │ │ ├── mod.rs │ │ └── performer.rs │ ├── syntax │ │ ├── mod.rs │ │ ├── style.rs │ │ ├── highlighter.rs │ │ ├── parser.rs │ │ ├── builder.rs │ │ └── scope.rs │ ├── settings.rs │ ├── command.rs │ ├── view.rs │ ├── mod.rs │ ├── menu.rs │ └── packages.rs ├── toolkit │ ├── mouse.rs │ ├── mod.rs │ ├── draw.rs │ ├── views.rs │ ├── palette.rs │ ├── keys.rs │ ├── style.rs │ └── canvas.rs ├── view │ ├── context │ │ ├── mod.rs │ │ ├── button.rs │ │ ├── group.rs │ │ ├── item.rs │ │ └── menu.rs │ ├── mod.rs │ ├── sidebar │ │ ├── mod.rs │ │ └── tree.rs │ ├── window.rs │ ├── editor.rs │ ├── modal.rs │ ├── theme.rs │ └── menubar.rs └── main.rs ├── packages ├── default │ ├── Syntax.sublime-menu │ ├── Side Bar Mount Point.sublime-menu │ ├── Widget Context.sublime-menu │ ├── Tab Context.sublime-menu │ ├── Find in Files.sublime-menu │ ├── Context.sublime-menu │ ├── Side Bar.sublime-menu │ ├── Indentation.sublime-menu │ ├── Default (OSX).sublime-mousemap │ ├── Default (Linux).sublime-mousemap │ ├── Default (Windows).sublime-mousemap │ └── Default.sublime-commands ├── themes │ ├── SpaceCadet.tmTheme │ ├── IDLE.tmTheme │ ├── All Hallow's Eve.tmTheme │ ├── iPlastic.tmTheme │ ├── LAZY.tmTheme │ ├── Slush & Poppies.tmTheme │ ├── Zenburnesque.tmTheme │ ├── Blackboard.tmTheme │ └── MagicWB (Amiga).tmTheme └── Rust │ └── Rust.JSON-tmLanguage ├── .gitignore ├── sublimate.sublime-project ├── Cargo.toml ├── LICENSE-MIT ├── README.md └── Cargo.lock /src/core/workspace/mod.rs: -------------------------------------------------------------------------------- 1 | mod project; 2 | mod build; 3 | 4 | pub use self::project::{Project, Folder}; 5 | -------------------------------------------------------------------------------- /src/core/regex.rs: -------------------------------------------------------------------------------- 1 | pub use onig::{Regex, Region, Error as RegexError, REGEX_OPTION_NONE, SEARCH_OPTION_NONE}; 2 | -------------------------------------------------------------------------------- /src/toolkit/mouse.rs: -------------------------------------------------------------------------------- 1 | 2 | // enum MouseButton { 3 | 4 | // } 5 | 6 | // struct MouseEvent { 7 | 8 | // } 9 | -------------------------------------------------------------------------------- /src/view/context/mod.rs: -------------------------------------------------------------------------------- 1 | mod button; 2 | mod group; 3 | mod item; 4 | mod menu; 5 | 6 | pub use self::menu::{ContextMenu, ContextMenuView}; 7 | -------------------------------------------------------------------------------- /packages/default/Syntax.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Syntax", 4 | "children": [ { "command": "$file_types" } ] 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /src/view/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod theme; 2 | pub mod menubar; 3 | pub mod context; 4 | pub mod window; 5 | pub mod modal; 6 | pub mod editor; 7 | pub mod sidebar; 8 | -------------------------------------------------------------------------------- /src/core/workspace/build.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct BuildSystem { 3 | cmd: Vec, 4 | file_regex: Option, 5 | selector: Option 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sublime-workspace 2 | 3 | # Compiled files 4 | *.o 5 | *.so 6 | *.rlib 7 | *.dll 8 | 9 | # Executables 10 | *.exe 11 | 12 | # Generated by Cargo 13 | /target/ 14 | -------------------------------------------------------------------------------- /src/core/bindings/mod.rs: -------------------------------------------------------------------------------- 1 | mod context; 2 | mod keymap; 3 | mod performer; 4 | 5 | pub use self::keymap::{Keymap, Key, ParseKeymapError}; 6 | pub use self::performer::HotkeyPerformer; 7 | -------------------------------------------------------------------------------- /packages/default/Side Bar Mount Point.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { "caption": "-", "id": "folder_commands" }, 3 | { "caption": "Remove Folder from Project", "command": "remove_folder", "args": { "dirs": []} } 4 | ] 5 | -------------------------------------------------------------------------------- /packages/default/Widget Context.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { "command": "copy" }, 3 | { "command": "cut" }, 4 | { "command": "paste" }, 5 | { "caption": "-" }, 6 | { "command": "select_all" } 7 | ] 8 | -------------------------------------------------------------------------------- /sublimate.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": "packages" 6 | }, 7 | { 8 | "path": "src" 9 | }, 10 | { 11 | "path": ".", 12 | "folder_exclude_patterns": ["*"] 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/toolkit/mod.rs: -------------------------------------------------------------------------------- 1 | mod style; 2 | pub mod draw; 3 | mod canvas; 4 | pub mod views; 5 | pub mod keys; 6 | pub mod mouse; 7 | pub mod palette; 8 | 9 | pub use toolkit::canvas::*; 10 | pub use toolkit::draw::*; 11 | pub use toolkit::style::*; 12 | pub use toolkit::views::*; 13 | pub use toolkit::mouse::*; 14 | pub use toolkit::palette::*; 15 | -------------------------------------------------------------------------------- /src/toolkit/draw.rs: -------------------------------------------------------------------------------- 1 | pub trait Drawing { 2 | fn char(&self, c: char, x: usize, y: usize); 3 | 4 | fn text(&self, s: &str, x: usize, y: usize); 5 | 6 | fn fill_char(&self, c: char); 7 | 8 | fn fill(&self); 9 | 10 | } 11 | 12 | pub trait HasSize { 13 | fn width(&self) -> usize; 14 | fn height(&self) -> usize; 15 | } 16 | 17 | pub trait HasChildren { 18 | type Item; 19 | fn children(&self) -> &[Self::Item]; 20 | } 21 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "sublimate" 4 | version = "0.0.1" 5 | authors = ["Ivan Ivaschenko "] 6 | 7 | [dependencies] 8 | ncurses = "*" 9 | bitflags = "0.4" 10 | weakjson = "0.0.7" 11 | rustc-serialize = "0.3" 12 | unicode-width = "0.1" 13 | log = "0.3" 14 | env_logger = "0.3" 15 | plist = "0.0.12" 16 | onig = "0.4" 17 | glob = "0.2.10" 18 | clap = "2" 19 | 20 | clippy = {version = "*", optional = true} 21 | 22 | [features] 23 | default = ["ncurses/panel"] 24 | -------------------------------------------------------------------------------- /packages/default/Tab Context.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { "command": "close_by_index", "args": { "group": -1, "index": -1 }, "caption": "Close" }, 3 | { "command": "close_others_by_index", "args": { "group": -1, "index": -1 }, "caption": "Close others" }, 4 | { "command": "close_to_right_by_index", "args": { "group": -1, "index": -1 }, "caption": "Close tabs to the right" }, 5 | { "caption": "-" }, 6 | { "command": "new_file" }, 7 | { "command": "prompt_open_file", "caption": "Open file" } 8 | ] 9 | -------------------------------------------------------------------------------- /packages/default/Find in Files.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { "command": "clear_location", "caption": "Clear" }, 3 | { "command": "add_directory", "caption": "Add Folder" }, 4 | { "command": "add_where_snippet", "args": {"snippet": "*.${0:txt}"}, "caption": "Add Include Filter" }, 5 | { "command": "add_where_snippet", "args": {"snippet": "-*.${0:txt}"}, "caption": "Add Exclude Filter" }, 6 | { "command": "add_where_snippet", "args": {"snippet": ""}, "caption": "Add Open Folders" }, 7 | { "command": "add_where_snippet", "args": {"snippet": ""}, "caption": "Add Open Files" } 8 | ] 9 | -------------------------------------------------------------------------------- /src/core/syntax/mod.rs: -------------------------------------------------------------------------------- 1 | mod definition; 2 | mod scope; 3 | mod parser; 4 | mod builder; 5 | mod theme; 6 | mod highlighter; 7 | mod style; 8 | 9 | pub use self::definition::{Syntax, ParseSyntaxError}; 10 | pub use self::scope::{Scope, ScopePath, ScopeSelector, ScopeSelectors, ScopeCommand, ParseScopeError}; 11 | pub use self::theme::{Theme, ParseThemeError}; 12 | pub use self::parser::{Parser, ParserState}; 13 | pub use self::highlighter::{Highlighter, HighlightIterator}; 14 | pub use self::style::{ 15 | Style, StyleModifier, FontStyle, Color, 16 | BLACK, WHITE, FONT_STYLE_BOLD, FONT_STYLE_UNDERLINE, FONT_STYLE_ITALIC 17 | }; 18 | -------------------------------------------------------------------------------- /packages/default/Context.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { "command": "copy" }, 3 | { "command": "cut" }, 4 | { "command": "paste" }, 5 | { "caption": "-", "id": "selection" }, 6 | { "command": "select_all" }, 7 | { "caption": "-", "id": "file" }, 8 | { "command": "open_in_browser", "caption": "Open in Browser" }, 9 | { "command": "open_dir", "args": {"dir": "$file_path", "file": "$file_name"}, "caption": "Open Containing Folder…" }, 10 | { "command": "copy_path", "caption": "Copy File Path" }, 11 | { "command": "reveal_in_side_bar", "caption": "Reveal in Side Bar" }, 12 | { "caption": "-", "id": "end" } 13 | ] 14 | -------------------------------------------------------------------------------- /src/view/sidebar/mod.rs: -------------------------------------------------------------------------------- 1 | use core::Core; 2 | 3 | mod tree; 4 | 5 | pub use self::tree::{ProjectEntries, ProjectEntriesView}; 6 | 7 | use toolkit::*; 8 | 9 | #[derive(Debug)] 10 | pub struct Sidebar { 11 | project_tree: ProjectEntries 12 | } 13 | 14 | 15 | impl Sidebar { 16 | pub fn new(core: &Core) -> Sidebar { 17 | Sidebar { 18 | project_tree: ProjectEntries::from_project(&core.project) 19 | } 20 | } 21 | } 22 | 23 | impl<'a> Widget<'a> for Sidebar { 24 | type Context = &'a Core; 25 | type View = ProjectEntriesView<'a>; 26 | 27 | fn view(&'a self, _: &'a Core) -> ProjectEntriesView<'a> { 28 | self.project_tree.view(()) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/default/Side Bar.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { "caption": "New File", "command": "new_file_at", "args": {"dirs": []} }, 3 | { "caption": "Rename…", "command": "rename_path", "args": {"paths": []} }, 4 | { "caption": "Delete File", "command": "delete_file", "args": {"files": []} }, 5 | { "caption": "Open Containing Folder…", "command": "open_containing_folder", "args": {"files": []} }, 6 | { "caption": "-", "id": "folder_commands" }, 7 | { "caption": "New Folder…", "command": "new_folder", "args": {"dirs": []} }, 8 | { "caption": "Delete Folder", "command": "delete_folder", "args": {"dirs": []} }, 9 | { "caption": "Find in Folder…", "command": "find_in_folder", "args": {"dirs": []} }, 10 | { "caption": "-", "id": "end" } 11 | ] 12 | -------------------------------------------------------------------------------- /src/toolkit/views.rs: -------------------------------------------------------------------------------- 1 | use toolkit::canvas::Canvas; 2 | use core::bindings::Key; 3 | 4 | pub trait Widget<'a> { 5 | type Context; 6 | type View: View + 'a; 7 | 8 | #[allow(unused_variables)] 9 | fn enabled(&self, context: Self::Context) -> bool { 10 | true 11 | } 12 | 13 | fn view(&'a self, context: Self::Context) -> Self::View; 14 | 15 | #[allow(unused_variables)] 16 | fn on_keypress(&'a mut self, context: Self::Context, canvas: Canvas, key: Key) -> bool { 17 | false 18 | } 19 | 20 | #[allow(unused_variables)] 21 | fn focus(&mut self, context: Self::Context) { 22 | } 23 | 24 | #[allow(unused_variables)] 25 | fn unfocus(&mut self, context: Self::Context) { 26 | } 27 | } 28 | 29 | pub trait View { 30 | fn width(&self) -> usize; 31 | fn height(&self) -> usize; 32 | fn render(&self, canvas: Canvas); 33 | } 34 | -------------------------------------------------------------------------------- /src/toolkit/palette.rs: -------------------------------------------------------------------------------- 1 | use std::cell::{Cell, RefCell}; 2 | use std::collections::HashMap; 3 | 4 | use toolkit::style::{Color, ColorPair}; 5 | use ncurses::init_pair; 6 | 7 | #[derive(Debug)] 8 | pub struct ColorPalette { 9 | pub index: Cell, 10 | pub end: u8, 11 | map: RefCell> 12 | } 13 | 14 | impl ColorPalette { 15 | pub fn new(from: u8, to: u8) -> ColorPalette { 16 | ColorPalette { 17 | index: Cell::new(from), 18 | end: to, 19 | map: RefCell::new(HashMap::new()) 20 | } 21 | } 22 | 23 | pub fn color_pair(&self, foreground: Color, background: Color) -> ColorPair { 24 | let index = self.index.get(); 25 | self.map.borrow_mut().entry((foreground, background)).or_insert_with(|| { 26 | if index < self.end { 27 | init_pair(index as i16, foreground.to_term(), background.to_term()); 28 | self.index.set(index + 1); 29 | } else { 30 | // TODO: warning 31 | } 32 | ColorPair(index) 33 | }).clone() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/view/window.rs: -------------------------------------------------------------------------------- 1 | use toolkit::*; 2 | use core::Core; 3 | 4 | use core::bindings::Key; 5 | 6 | use view::menubar::Menubar; 7 | use view::editor::Editor; 8 | use view::sidebar::Sidebar; 9 | 10 | #[derive(Debug)] 11 | pub struct Window { 12 | core: Core, 13 | menubar: Menubar, 14 | editor: Editor, 15 | sidebar: Sidebar, 16 | } 17 | 18 | impl Window { 19 | pub fn new(core: Core) -> Window { 20 | let menubar = Menubar::new(&core); 21 | let sidebar = Sidebar::new(&core); 22 | let editor = Editor::new(&core); 23 | Window { 24 | core: core, 25 | menubar: menubar, 26 | sidebar: sidebar, 27 | editor: editor 28 | } 29 | } 30 | 31 | pub fn on_keypress(&mut self, mut canvas: Canvas, key: Key) { 32 | self.menubar.on_keypress(&self.core, canvas.cut_top(1), key); 33 | } 34 | 35 | 36 | pub fn render(&self, mut canvas: Canvas) { 37 | self.menubar.view(&self.core).render(canvas.cut_top(1)); 38 | self.sidebar.view(&self.core).render(canvas.cut_left(30)); 39 | self.editor.view(&self.core).render(canvas); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ivan Ivashchenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/toolkit/keys.rs: -------------------------------------------------------------------------------- 1 | use core::bindings::Key; 2 | 3 | // Ctrl AZ: 1 - 26 4 | // az 97 122 5 | // Shift AZ: 65 90 6 | 7 | // backspace 127 8 | // 09 48 57 9 | 10 | // f1-f12 265 276 11 | 12 | // left 260 13 | // right 261 14 | // up 259 15 | // down 258 16 | 17 | // shift left 393 18 | // shift right 402 19 | // shift up 337 20 | // shift down 336 21 | 22 | // alt left 27 27 91 68 23 | // alt right 27 27 91 67 24 | // alt up 27 27 91 65 25 | // alt down 27 27 91 66 26 | 27 | // ctrl shift left 542 28 | // ctrl shift right 557 29 | // ctrl shift up 563 30 | // ctrl shift down 522 31 | 32 | // ctrl left 541 33 | // ctrl right 556 34 | // ctrl up 562 35 | // ctrl down 521 36 | 37 | // tab 9 38 | // shift tab 353 39 | 40 | impl Key { 41 | pub fn from_keycode(keycode: i32) -> Option { 42 | match keycode { 43 | 10 => Some(Key::Enter), 44 | 45 | 260 => Some(Key::Left), 46 | 261 => Some(Key::Right), 47 | 259 => Some(Key::Up), 48 | 258 => Some(Key::Down), 49 | _ => { 50 | 51 | // TODO: warning 52 | None 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/core/settings.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Seek}; 2 | use weakjson::from_reader as json_from_reader; 3 | use plist::{Plist, Error as PlistError}; 4 | use rustc_serialize::json::ParserError as JsonError; 5 | 6 | pub use rustc_serialize::json::Json as Settings; 7 | pub use rustc_serialize::json::Array as SettingsArray; 8 | pub use rustc_serialize::json::Object as SettingsObject; 9 | 10 | pub trait FromSettings : Sized { 11 | fn from_settings(settings: Settings) -> Self; 12 | } 13 | 14 | pub trait ParseSettings : Sized { 15 | type Error; 16 | fn parse_settings(settings: Settings) -> Result; 17 | } 18 | 19 | #[derive(Debug)] 20 | pub enum SettingsError { 21 | Plist(PlistError), 22 | Json(JsonError) 23 | } 24 | 25 | impl From for SettingsError { 26 | fn from(error: PlistError) -> SettingsError { 27 | SettingsError::Plist(error) 28 | } 29 | } 30 | 31 | impl From for SettingsError { 32 | fn from(error: JsonError) -> SettingsError { 33 | SettingsError::Json(error) 34 | } 35 | } 36 | 37 | pub fn read_json(mut reader: R) -> Result { 38 | Ok(try!(json_from_reader(&mut reader as &mut Read))) 39 | } 40 | 41 | pub fn read_plist(reader: R) -> Result { 42 | Ok(try!(Plist::read(reader)).into_rustc_serialize_json()) 43 | } 44 | -------------------------------------------------------------------------------- /src/core/syntax/style.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Copy)] 2 | pub struct Style { 3 | /// Foreground color. 4 | pub foreground: Color, 5 | /// Background color. 6 | pub background: Color, 7 | /// Style of the font. 8 | pub font_style: FontStyle 9 | } 10 | 11 | #[derive(Debug, Default, Clone, Copy)] 12 | pub struct StyleModifier { 13 | /// Foreground color. 14 | pub foreground: Option, 15 | /// Background color. 16 | pub background: Option, 17 | /// Style of the font. 18 | pub font_style: Option 19 | } 20 | 21 | pub const BLACK: Color = Color {r: 0x00, g: 0x00, b: 0x00, a: 0x00}; 22 | pub const WHITE: Color = Color {r: 0xFF, g: 0xFF, b: 0xFF, a: 0xFF}; 23 | 24 | #[derive(Debug, Clone, Copy)] 25 | pub struct Color { 26 | pub r: u8, 27 | pub g: u8, 28 | pub b: u8, 29 | pub a: u8 30 | } 31 | 32 | bitflags! { 33 | flags FontStyle: u8 { 34 | const FONT_STYLE_BOLD = 1, 35 | const FONT_STYLE_UNDERLINE = 2, 36 | const FONT_STYLE_ITALIC = 4, 37 | } 38 | } 39 | 40 | impl Style { 41 | pub fn apply(&self, modifier: StyleModifier) -> Style { 42 | Style { 43 | foreground: modifier.foreground.unwrap_or(self.foreground), 44 | background: modifier.background.unwrap_or(self.background), 45 | font_style: modifier.font_style.unwrap_or(self.font_style), 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/core/command.rs: -------------------------------------------------------------------------------- 1 | use std::hash::{Hash, Hasher}; 2 | use core::settings::{Settings, SettingsObject, ParseSettings}; 3 | 4 | use self::ParseCommandError::*; 5 | 6 | #[derive(Debug, PartialEq, Clone)] 7 | pub struct Command { 8 | pub name: String, 9 | pub args: SettingsObject 10 | } 11 | 12 | impl Eq for Command {} 13 | 14 | impl Hash for Command { 15 | fn hash(&self, state: &mut H) { 16 | self.name.hash(state); 17 | } 18 | } 19 | 20 | #[derive(Debug)] 21 | pub enum ParseCommandError { 22 | CommandIsNotObject, 23 | CommandNameIsNotString, 24 | CommandArgsIsNotObject, 25 | } 26 | 27 | impl ParseSettings for Command { 28 | type Error = ParseCommandError; 29 | fn parse_settings(settings: Settings) -> Result { 30 | let mut obj = match settings { 31 | Settings::Object(obj) => obj, 32 | _ => return Err(CommandIsNotObject), 33 | }; 34 | 35 | let name = match obj.remove("command") { 36 | Some(Settings::String(name)) => name, 37 | _ => return Err(CommandNameIsNotString), 38 | }; 39 | 40 | let args = match obj.remove("args") { 41 | Some(Settings::Object(args)) => args, 42 | None => SettingsObject::default(), 43 | _ => return Err(CommandArgsIsNotObject), 44 | }; 45 | 46 | // TODO: check obj is empty 47 | 48 | Ok(Command { 49 | name: name, 50 | args: args 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sublimate 2 | 3 | [![Gitter](https://badges.gitter.im/defuz/sublimate.svg)](https://gitter.im/defuz/sublimate?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | ## Dependencies 6 | 7 | You will need `oniguruma` (`libonig-dev`) and `ncurses` (`libncurses5-dev`) libraries. 8 | 9 | Installation on Ubuntu Linux: `$ sudo apt-get install libonig-dev libncurses5-dev` 10 | 11 | ## Compiling 12 | 13 | Once you install all dependencies - follow this instructions to compile `sublimate`. 14 | 15 | 1. Clone the project `$ git clone https://github.com/defuz/sublimate && cd sublimate` 16 | 2. Build the project `$ cargo build --release` (**NOTE:** There is a large performance differnce when compiling without optimizations, so I recommend alwasy using `--release` to enable to them) 17 | 3. Once complete, the binary will be located at `target/release/sublimate` 18 | 19 | ## Options 20 | 21 | `sublimate` has the following options: 22 | 23 | ``` 24 | USAGE: 25 | sublimate [FLAGS] --packages --project 26 | 27 | FLAGS: 28 | -h, --help Prints help information 29 | -V, --version Prints version information 30 | 31 | OPTIONS: 32 | --packages Sets packages path 33 | --project Sets path to sublime project 34 | 35 | ARGS: 36 | file Sets a path to viewing file 37 | ``` 38 | You can also obtain this help by runing `sublimate` with `-h` flag. 39 | 40 | ## License 41 | 42 | This project is dual-licensed under the terms of the MIT and Apache (version 2.0) licenses. 43 | -------------------------------------------------------------------------------- /packages/default/Indentation.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { "command": "toggle_setting", "args": {"setting": "translate_tabs_to_spaces"}, "caption": "Indent Using Spaces", "checkbox": true }, 3 | { "caption": "-" }, 4 | { "command": "set_setting", "args": {"setting": "tab_size", "value": 1}, "caption": "Tab Width: 1", "checkbox": true }, 5 | { "command": "set_setting", "args": {"setting": "tab_size", "value": 2}, "caption": "Tab Width: 2", "checkbox": true }, 6 | { "command": "set_setting", "args": {"setting": "tab_size", "value": 3}, "caption": "Tab Width: 3", "checkbox": true }, 7 | { "command": "set_setting", "args": {"setting": "tab_size", "value": 4}, "caption": "Tab Width: 4", "checkbox": true }, 8 | { "command": "set_setting", "args": {"setting": "tab_size", "value": 5}, "caption": "Tab Width: 5", "checkbox": true }, 9 | { "command": "set_setting", "args": {"setting": "tab_size", "value": 6}, "caption": "Tab Width: 6", "checkbox": true }, 10 | { "command": "set_setting", "args": {"setting": "tab_size", "value": 7}, "caption": "Tab Width: 7", "checkbox": true }, 11 | { "command": "set_setting", "args": {"setting": "tab_size", "value": 8}, "caption": "Tab Width: 8", "checkbox": true }, 12 | { "caption": "-" }, 13 | { "command": "detect_indentation", "caption": "Guess Settings From Buffer" }, 14 | { "caption": "-" }, 15 | { "command": "expand_tabs", "caption": "Convert Indentation to Spaces", "args": {"set_translate_tabs": true} }, 16 | { "command": "unexpand_tabs", "caption": "Convert Indentation to Tabs", "args": {"set_translate_tabs": true} } 17 | ] 18 | -------------------------------------------------------------------------------- /src/core/view.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::fs::File; 3 | use std::io::{BufReader, BufRead, Error as IoError}; 4 | 5 | use core::syntax::{Parser, ParserState, Highlighter, HighlightIterator}; 6 | 7 | #[derive(Debug)] 8 | pub struct View { 9 | path: Option, 10 | pub lines: Vec 11 | } 12 | 13 | #[derive(Debug)] 14 | pub struct Line { 15 | pub text: String, 16 | parser_state: ParserState, 17 | } 18 | 19 | impl Line { 20 | fn new(text: String) -> Line { 21 | Line { 22 | text: text, 23 | parser_state: ParserState::new() 24 | } 25 | } 26 | 27 | pub fn parse(&mut self, parser: &mut Parser, state: &mut ParserState) { 28 | self.parser_state = state.clone(); 29 | parser.parse(&self.text, state); 30 | self.parser_state.swap_changes(state); 31 | } 32 | 33 | pub fn highlight<'a>(&'a self, highlighter: &'a Highlighter) -> HighlightIterator<'a> { 34 | HighlightIterator::new( 35 | self.parser_state.scope_path.clone(), 36 | &self.parser_state.changes, 37 | &self.text, 38 | highlighter 39 | ) 40 | } 41 | } 42 | 43 | impl View { 44 | pub fn new() -> View { 45 | View { 46 | path: None, 47 | lines: Vec::new() 48 | } 49 | } 50 | 51 | pub fn open(path: PathBuf) -> Result { 52 | let mut lines = Vec::new(); 53 | for text in BufReader::new(try!(File::open(&path))).lines() { 54 | lines.push(Line::new(try!(text))); 55 | } 56 | Ok(View { 57 | path: Some(path), 58 | lines: lines 59 | }) 60 | } 61 | 62 | pub fn parse(&mut self, parser: &mut Parser) { 63 | let mut state = ParserState::new(); 64 | for line in &mut self.lines { 65 | line.parse(parser, &mut state); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod settings; 2 | pub mod menu; 3 | pub mod workspace; 4 | mod packages; 5 | pub mod command; 6 | pub mod syntax; 7 | pub mod regex; 8 | pub mod bindings; 9 | pub mod view; 10 | 11 | use std::path::PathBuf; 12 | 13 | use core::workspace::Project; 14 | use core::packages::{PackageRepository, PackageError}; 15 | use core::bindings::HotkeyPerformer; 16 | use core::view::View; 17 | use core::menu::Menu; 18 | use core::syntax::{Parser, Highlighter}; 19 | 20 | #[derive(Debug)] 21 | pub struct Core { 22 | pub package_repository: PackageRepository, 23 | pub project: Project, 24 | pub hotkeys: HotkeyPerformer, 25 | pub view: View 26 | } 27 | 28 | impl Core { 29 | 30 | pub fn load(packages_path_str: &str, file_path_str: &str, project_path_str: &str) -> Core { 31 | let packages_path = PathBuf::from(packages_path_str); 32 | let view_path = PathBuf::from(file_path_str); 33 | let project_path = PathBuf::from(project_path_str); 34 | let repository = PackageRepository::open(packages_path); 35 | let mut view = View::open(view_path).unwrap(); 36 | let syntax = repository.get_syntax("Rust/Rust.tmLanguage").unwrap(); 37 | let mut parser = Parser::from_syntax(syntax); 38 | view.parse(&mut parser); 39 | let mut hotkeys = HotkeyPerformer::new(); 40 | // TODO: fix unwrap 41 | hotkeys.add_keymap(repository.get_keymap("default/Default (OSX).sublime-keymap").unwrap()); 42 | Core { 43 | project: Project::open(project_path).unwrap(), 44 | package_repository: repository, 45 | hotkeys: hotkeys, 46 | view: view 47 | } 48 | } 49 | 50 | pub fn create_menu(&self) -> Menu { 51 | // todo: fix unwrap 52 | self.package_repository.get_menu("default/Main.sublime-menu").unwrap() 53 | } 54 | 55 | pub fn create_highlighter(&self) -> Result { 56 | let theme = try!(self.package_repository.get_theme("themes/Twilight.tmTheme")); 57 | let highlighter = Highlighter::new(theme); 58 | Ok(highlighter) 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/core/bindings/performer.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use core::Core; 4 | use core::command::Command; 5 | 6 | use super::keymap::{Keymap, Hotkey, HotkeySequence}; 7 | use super::context::{Context, Evaluate}; 8 | 9 | type PerformerNodeId = usize; 10 | 11 | #[derive(Default, Debug)] 12 | struct PerformerNode { 13 | children: HashMap, 14 | commands: Vec<(Command, Context)> 15 | } 16 | 17 | #[derive(Default, Debug)] 18 | pub struct HotkeyPerformer { 19 | node_id: PerformerNodeId, 20 | nodes: Vec, 21 | hotkeys: HashMap 22 | } 23 | 24 | impl HotkeyPerformer { 25 | pub fn new() -> HotkeyPerformer { 26 | HotkeyPerformer { 27 | node_id: 0, 28 | nodes: vec![PerformerNode::default()], 29 | hotkeys: HashMap::new() 30 | } 31 | } 32 | 33 | pub fn add_keymap(&mut self, keymap: Keymap) { 34 | let mut next_id = self.nodes.len(); 35 | for binding in keymap { 36 | self.hotkeys.insert(binding.command.clone(), binding.hotkeys.clone()); 37 | let mut node_id = 0; 38 | for hotkey in binding.hotkeys { 39 | node_id = *self.nodes[node_id].children.entry(hotkey).or_insert(next_id); 40 | if node_id == next_id { 41 | self.nodes.push(PerformerNode::default()); 42 | next_id += 1; 43 | } 44 | } 45 | self.nodes[node_id].commands.push((binding.command, binding.context)); 46 | } 47 | } 48 | 49 | pub fn get_hotkeys(&self, command: &Command) -> Option<&HotkeySequence> { 50 | self.hotkeys.get(command) 51 | } 52 | 53 | pub fn perform_hotkey(&mut self, core: &Core, hotkey: &Hotkey) -> Option { 54 | if let Some(&node_id) = self.nodes[self.node_id].children.get(hotkey) { 55 | // Check whether there are commands that binded to the current state 56 | // If yes, return it 57 | for &(ref command, ref context) in &self.nodes[node_id].commands { 58 | if context.evaluate(core) { 59 | return Some((*command).clone()); 60 | } 61 | } 62 | // If no, change current state 63 | self.node_id = node_id; 64 | } else { 65 | // There's no node for this hotkey, so we reset current status 66 | self.node_id = 0; 67 | } 68 | None 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/view/editor.rs: -------------------------------------------------------------------------------- 1 | use unicode_width::UnicodeWidthStr; 2 | 3 | use toolkit::*; 4 | 5 | use core::Core; 6 | use core::view::{View as CoreView}; 7 | use core::bindings::Key; 8 | use core::syntax::Highlighter; 9 | 10 | #[derive(Debug)] 11 | pub struct Editor { 12 | highlighter: Highlighter, 13 | palette: ColorPalette 14 | } 15 | 16 | pub struct EditorView<'a> { 17 | view: &'a CoreView, 18 | highlighter: &'a Highlighter, 19 | palette: &'a ColorPalette 20 | } 21 | 22 | impl Editor { 23 | pub fn new(core: &Core) -> Editor { 24 | Editor { 25 | highlighter: core.create_highlighter().unwrap(), 26 | palette: ColorPalette::new(32, 255) 27 | } 28 | } 29 | } 30 | 31 | impl<'a> Widget<'a> for Editor { 32 | type Context = &'a Core; 33 | type View = EditorView<'a>; 34 | 35 | fn view(&'a self, core: &'a Core) -> EditorView<'a> { 36 | EditorView { 37 | view: &core.view, 38 | highlighter: &self.highlighter, 39 | palette: &self.palette 40 | } 41 | } 42 | 43 | #[allow(unused_variables)] 44 | fn on_keypress(&mut self, core: &Core, canvas: Canvas, key: Key) -> bool { 45 | false 46 | } 47 | 48 | } 49 | 50 | impl<'a> View for EditorView<'a> { 51 | fn width(&self) -> usize { 52 | self.view.lines.iter().map(|line| line.text.width()).max().unwrap_or(0) 53 | } 54 | 55 | fn height(&self) -> usize { 56 | self.view.lines.len() 57 | } 58 | 59 | fn render(&self, mut canvas: Canvas) { 60 | for line in &self.view.lines { 61 | let mut canvas = canvas.cut_top(1); 62 | canvas.cut_left(2).fill(); 63 | for (style, text) in line.highlight(self.highlighter) { 64 | let foreground = Color::from_rgb256( 65 | style.foreground.r, 66 | style.foreground.g, 67 | style.foreground.b 68 | ); 69 | let background = Color::from_rgb256( 70 | style.background.r, 71 | style.background.g, 72 | style.background.b 73 | ); 74 | canvas.style(Style { 75 | colors: self.palette.color_pair(foreground, background), 76 | attrs: Attr::empty() // impl convert 77 | }); 78 | canvas.cut_left(text.width()).text(text, 0, 0); 79 | } 80 | canvas.fill(); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/core/menu.rs: -------------------------------------------------------------------------------- 1 | use super::settings::{Settings, ParseSettings}; 2 | use core::command::{Command, ParseCommandError}; 3 | 4 | use self::ParseMenuError::*; 5 | 6 | pub type Menu = Vec; 7 | 8 | #[derive(Debug)] 9 | pub enum MenuItem { 10 | Button(Option, Command, bool), 11 | Group(String, Menu), 12 | Divider, 13 | } 14 | 15 | #[derive(Debug)] 16 | pub enum ParseMenuError { 17 | MenuIsNotArray, 18 | ItemIsNotObject, 19 | CaptionIsNotString, 20 | CaptionIsNotDefinedForGroup, 21 | CheckboxIsNotBoolean, 22 | CommandError(ParseCommandError) 23 | } 24 | 25 | impl ParseSettings for MenuItem { 26 | type Error = ParseMenuError; 27 | fn parse_settings(settings: Settings) -> Result { 28 | let mut obj = match settings { 29 | Settings::Object(obj) => obj, 30 | _ => return Err(ItemIsNotObject), 31 | }; 32 | let caption = match obj.remove("caption") { 33 | Some(Settings::String(caption)) => { 34 | if caption == "-" { 35 | // TODO: check obj is empty 36 | return Ok(MenuItem::Divider); 37 | } 38 | Some(caption) 39 | }, 40 | None => None, 41 | _ => return Err(CaptionIsNotString) 42 | }; 43 | // parse group 44 | if let Some(settings) = obj.remove("children") { 45 | let caption = match caption { 46 | Some(caption) => caption, 47 | None => return Err(CaptionIsNotDefinedForGroup) 48 | }; 49 | // TODO: check obj is empty 50 | return Ok(MenuItem::Group(caption, try!(Menu::parse_settings(settings)))); 51 | }; 52 | // parse button 53 | let is_checkbox = match obj.remove("checkbox") { 54 | Some(Settings::Boolean(v)) => v, 55 | None => false, 56 | _ => return Err(CheckboxIsNotBoolean) 57 | }; 58 | let command = match Command::parse_settings(Settings::Object(obj)) { 59 | Ok(command) => command, 60 | Err(err) => return Err(CommandError(err)) 61 | }; 62 | // TODO: check obj is empty 63 | Ok(MenuItem::Button(caption, command, is_checkbox)) 64 | } 65 | } 66 | 67 | impl ParseSettings for Menu { 68 | type Error = ParseMenuError; 69 | 70 | fn parse_settings(settings: Settings) -> Result { 71 | let arr = match settings { 72 | Settings::Array(arr) => arr, 73 | _ => return Err(MenuIsNotArray) 74 | }; 75 | let mut menu = Menu::new(); 76 | for settings in arr { 77 | menu.push(try!(MenuItem::parse_settings(settings))) 78 | } 79 | Ok(menu) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/view/context/button.rs: -------------------------------------------------------------------------------- 1 | use core::Core; 2 | use core::command::Command; 3 | 4 | use toolkit::*; 5 | 6 | use unicode_width::UnicodeWidthStr; 7 | 8 | use view::theme::*; 9 | 10 | #[derive(Debug)] 11 | pub struct Button { 12 | caption: Option, 13 | command: Command, 14 | is_checkbox: bool 15 | } 16 | 17 | #[derive(Debug)] 18 | pub struct ButtonView<'a> { 19 | pub caption: &'a str, 20 | pub hotkey: String, 21 | pub state: ButtonState 22 | } 23 | 24 | #[derive(Debug)] 25 | pub enum ButtonState { 26 | Unfocused, 27 | Focused, 28 | Disabled 29 | } 30 | 31 | impl Button { 32 | pub fn new(caption: Option, command: Command, is_checkbox: bool) -> Button { 33 | Button { caption: caption, command: command, is_checkbox: is_checkbox } 34 | } 35 | } 36 | 37 | impl<'a> Widget<'a> for Button { 38 | type Context = (&'a Core, bool); 39 | type View = ButtonView<'a>; 40 | 41 | fn view(&'a self, (core, focused): Self::Context) -> ButtonView<'a> { 42 | ButtonView { 43 | caption: match self.caption { 44 | Some(ref s) => s, 45 | None => "No caption" 46 | }, 47 | hotkey: core.hotkeys.get_hotkeys(&self.command).map(|h| h.to_string()).unwrap_or_default(), 48 | state: if focused { 49 | ButtonState::Focused 50 | } else { 51 | ButtonState::Unfocused 52 | } 53 | } 54 | } 55 | } 56 | 57 | impl ButtonState { 58 | fn caption_style(&self) -> Style { 59 | match *self { 60 | ButtonState::Unfocused => MODAL_STYLE, 61 | ButtonState::Focused => MODAL_SELECTED_STYLE, 62 | ButtonState::Disabled => MODAL_DISABLED_STYLE 63 | } 64 | } 65 | 66 | fn hotkey_style(&self) -> Style { 67 | match *self { 68 | ButtonState::Unfocused => MODAL_LOW_STYLE, 69 | ButtonState::Focused => MODAL_SELECTED_LOW_STYLE, 70 | ButtonState::Disabled => MODAL_DISABLED_STYLE 71 | } 72 | } 73 | } 74 | 75 | impl<'a> View for ButtonView<'a> { 76 | fn width(&self) -> usize { 77 | self.caption.width() + self.hotkey.width() + 4 78 | } 79 | 80 | fn height(&self) -> usize { 81 | 1 82 | } 83 | 84 | fn render(&self, mut canvas: Canvas) { 85 | canvas.style(self.state.caption_style()); 86 | let left_canvas = canvas.cut_left(self.caption.width() + 1); 87 | let right_canvas = canvas.cut_right(self.hotkey.width() + 1); 88 | left_canvas.char(' ', 0, 0); 89 | left_canvas.text(self.caption, 0, 1); 90 | canvas.fill(); 91 | right_canvas.style(self.state.hotkey_style()); 92 | right_canvas.text(&self.hotkey, 0, 0); 93 | right_canvas.char(' ', 0, self.hotkey.width()); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /packages/default/Default (OSX).sublime-mousemap: -------------------------------------------------------------------------------- 1 | [ 2 | // Basic drag select 3 | { 4 | "button": "button1", "count": 1, 5 | "press_command": "drag_select" 6 | }, 7 | { 8 | // Select between selection and click location 9 | "button": "button1", "modifiers": ["shift"], 10 | "press_command": "drag_select", 11 | "press_args": {"extend": true} 12 | }, 13 | { 14 | "button": "button1", "count": 1, "modifiers": ["super"], 15 | "press_command": "drag_select", 16 | "press_args": {"additive": true} 17 | }, 18 | { 19 | "button": "button1", "count": 1, "modifiers": ["shift", "super"], 20 | "press_command": "drag_select", 21 | "press_args": {"subtractive": true} 22 | }, 23 | 24 | // Drag select by words 25 | { 26 | "button": "button1", "count": 2, 27 | "press_command": "drag_select", 28 | "press_args": {"by": "words"} 29 | }, 30 | { 31 | "button": "button1", "count": 2, "modifiers": ["super"], 32 | "press_command": "drag_select", 33 | "press_args": {"by": "words", "additive": true} 34 | }, 35 | { 36 | "button": "button1", "count": 2, "modifiers": ["shift", "super"], 37 | "press_command": "drag_select", 38 | "press_args": {"by": "words", "subtractive": true} 39 | }, 40 | 41 | // Drag select by lines 42 | { 43 | "button": "button1", "count": 3, 44 | "press_command": "drag_select", 45 | "press_args": {"by": "lines"} 46 | }, 47 | { 48 | "button": "button1", "count": 3, "modifiers": ["super"], 49 | "press_command": "drag_select", 50 | "press_args": {"by": "lines", "additive": true} 51 | }, 52 | { 53 | "button": "button1", "count": 3, "modifiers": ["shift", "super"], 54 | "press_command": "drag_select", 55 | "press_args": {"by": "lines", "subtractive": true} 56 | }, 57 | 58 | // Alt + Mouse 1 Column select 59 | { 60 | "button": "button1", "modifiers": ["alt"], 61 | "press_command": "drag_select", 62 | "press_args": {"by": "columns"} 63 | }, 64 | { 65 | "button": "button1", "modifiers": ["alt", "super"], 66 | "press_command": "drag_select", 67 | "press_args": {"by": "columns", "additive": true} 68 | }, 69 | { 70 | "button": "button1", "modifiers": ["alt", "shift", "super"], 71 | "press_command": "drag_select", 72 | "press_args": {"by": "columns", "subtractive": true} 73 | }, 74 | 75 | // Mouse 3 column select 76 | { 77 | "button": "button3", 78 | "press_command": "drag_select", 79 | "press_args": {"by": "columns"} 80 | }, 81 | { 82 | "button": "button3", "modifiers": ["super"], 83 | "press_command": "drag_select", 84 | "press_args": {"by": "columns", "additive": true} 85 | }, 86 | { 87 | "button": "button3", "modifiers": ["shift", "super"], 88 | "press_command": "drag_select", 89 | "press_args": {"by": "columns", "subtractive": true} 90 | }, 91 | 92 | // Switch files with buttons 4 and 5 93 | { "button": "button4", "modifiers": [], "command": "prev_view" }, 94 | { "button": "button5", "modifiers": [], "command": "next_view" }, 95 | 96 | { "button": "button2", "modifiers": [], "press_command": "context_menu" }, 97 | { "button": "button1", "count": 1, "modifiers": ["ctrl"], "press_command": "context_menu" } 98 | ] 99 | -------------------------------------------------------------------------------- /src/core/packages.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Error as IoError, BufReader}; 2 | use std::fs::File; 3 | use std::path::{Path, PathBuf}; 4 | 5 | use core::settings::{Settings, SettingsError, read_json, read_plist, ParseSettings}; 6 | use core::menu::{Menu, ParseMenuError}; 7 | use core::bindings::{Keymap, ParseKeymapError}; 8 | use core::syntax::{Syntax, ParseSyntaxError, Theme, ParseThemeError}; 9 | 10 | #[derive(Debug)] 11 | pub struct PackageRepository { 12 | path: PathBuf 13 | } 14 | 15 | #[derive(Debug)] 16 | pub enum PackageError { 17 | ReadSettings(SettingsError), 18 | ParseTheme(ParseThemeError), 19 | ParseSyntax(ParseSyntaxError), 20 | ParseKeymap(ParseKeymapError), 21 | ParseMenu(ParseMenuError), 22 | Io(IoError) 23 | } 24 | 25 | impl From for PackageError { 26 | fn from(error: SettingsError) -> PackageError { 27 | PackageError::ReadSettings(error) 28 | } 29 | } 30 | 31 | impl From for PackageError { 32 | fn from(error: ParseThemeError) -> PackageError { 33 | PackageError::ParseTheme(error) 34 | } 35 | } 36 | 37 | impl From for PackageError { 38 | fn from(error: ParseSyntaxError) -> PackageError { 39 | PackageError::ParseSyntax(error) 40 | } 41 | } 42 | 43 | impl From for PackageError { 44 | fn from(error: ParseKeymapError) -> PackageError { 45 | PackageError::ParseKeymap(error) 46 | } 47 | } 48 | 49 | impl From for PackageError { 50 | fn from(error: ParseMenuError) -> PackageError { 51 | PackageError::ParseMenu(error) 52 | } 53 | } 54 | 55 | impl From for PackageError { 56 | fn from(error: IoError) -> PackageError { 57 | PackageError::Io(error) 58 | } 59 | } 60 | 61 | impl PackageRepository { 62 | pub fn open(path: PathBuf) -> PackageRepository { 63 | PackageRepository { path: path } 64 | } 65 | 66 | pub fn read_file(&self, path: &Path) -> Result, PackageError> { 67 | let reader = try!(File::open(self.path.join(path))); 68 | Ok(BufReader::new(reader)) 69 | } 70 | 71 | pub fn read_json(&self, path: &Path) -> Result { 72 | Ok(try!(read_json(try!(self.read_file(path))))) 73 | } 74 | 75 | pub fn read_plist(&self, path: &Path) -> Result { 76 | Ok(try!(read_plist(try!(self.read_file(path))))) 77 | } 78 | 79 | pub fn get_menu>(&self, path: P) -> Result { 80 | Ok(try!(Menu::parse_settings(try!(self.read_json(path.as_ref()))))) 81 | } 82 | 83 | pub fn get_keymap>(&self, path: P) -> Result { 84 | Ok(try!(Keymap::parse_settings(try!(self.read_json(path.as_ref()))))) 85 | } 86 | 87 | pub fn get_theme>(&self, path: P) -> Result { 88 | Ok(try!(Theme::parse_settings(try!(self.read_plist(path.as_ref()))))) 89 | } 90 | 91 | pub fn get_syntax>(&self, path: P) -> Result { 92 | Ok(try!(Syntax::parse_settings(try!(self.read_plist(path.as_ref()))))) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/view/modal.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | 3 | use ncurses::{newwin, new_panel, PANEL, del_panel, update_panels, doupdate}; 4 | 5 | use core::bindings::Key; 6 | 7 | use toolkit::*; 8 | 9 | #[derive(Debug, Clone, Copy)] 10 | pub enum ModalPosition { 11 | AboveLeft, 12 | AboveRight, 13 | UnderLeft, 14 | UnderRight, 15 | RightTop, 16 | } 17 | 18 | #[derive(Debug)] 19 | pub struct Modal { 20 | position: ModalPosition, 21 | panel: Cell>, 22 | content: T 23 | } 24 | 25 | #[derive(Debug)] 26 | pub struct ModalView<'a, T: View> { 27 | position: ModalPosition, 28 | panel: &'a Cell>, 29 | view: T 30 | } 31 | 32 | impl Modal { 33 | pub fn new(content: T, position: ModalPosition) -> Modal { 34 | Modal { 35 | position: position, 36 | panel: Cell::new(None), 37 | content: content 38 | } 39 | } 40 | 41 | pub fn hide(&self) { 42 | if let Some((panel, _)) = self.panel.get() { 43 | del_panel(panel); 44 | self.panel.set(None) 45 | } 46 | } 47 | } 48 | 49 | impl<'a, T: Widget<'a>> Widget<'a> for Modal { 50 | type Context = T::Context; 51 | type View = ModalView<'a, T::View>; 52 | 53 | fn view(&'a self, context: Self::Context) -> Self::View { 54 | ModalView { 55 | position: self.position, 56 | panel: &self.panel, 57 | view: self.content.view(context) 58 | } 59 | } 60 | 61 | fn on_keypress(&'a mut self, core: Self::Context, _: Canvas, key: Key) -> bool { 62 | let r = self.content.on_keypress(core, self.panel.get().unwrap().1, key); 63 | if r { 64 | update_panels(); 65 | doupdate(); 66 | } 67 | r 68 | } 69 | 70 | fn focus(&mut self, context: Self::Context) { 71 | self.content.focus(context); 72 | } 73 | 74 | fn unfocus(&mut self, context: Self::Context) { 75 | self.content.unfocus(context); 76 | self.hide(); 77 | } 78 | } 79 | 80 | impl<'a, T: View> View for ModalView<'a, T> { 81 | fn width(&self) -> usize { 82 | self.view.width() 83 | } 84 | 85 | fn height(&self) -> usize { 86 | self.view.height() 87 | } 88 | 89 | fn render(&self, base: Canvas) { 90 | if let Some((panel, _)) = self.panel.get() { 91 | del_panel(panel); 92 | } 93 | let (canvas, panel) = self.position.get_window(base, self.width(), self.height()); 94 | self.panel.set(Some((panel, canvas))); 95 | self.view.render(canvas); 96 | update_panels(); 97 | doupdate(); 98 | } 99 | } 100 | 101 | impl ModalPosition { 102 | fn get_window(&self, base: Canvas, w: usize, h: usize) -> (Canvas, PANEL) { 103 | let (x, y) = match *self { 104 | ModalPosition::UnderLeft => (base.x0 + base.x1, base.y0 + base.y2), 105 | ModalPosition::RightTop => (base.x0 + base.x2, base.y0 + base.y1), 106 | _ => unimplemented!() 107 | }; 108 | let win = newwin(h as i32, w as i32, y as i32, x as i32); 109 | (Canvas {win: win, x0: x, y0: y, x1: 0, y1: 0, x2: w, y2: h}, new_panel(win)) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /packages/default/Default (Linux).sublime-mousemap: -------------------------------------------------------------------------------- 1 | [ 2 | // Basic drag select 3 | { 4 | "button": "button1", "count": 1, 5 | "press_command": "drag_select" 6 | }, 7 | { 8 | "button": "button1", "count": 1, "modifiers": ["ctrl"], 9 | "press_command": "drag_select", 10 | "press_args": {"additive": true} 11 | }, 12 | { 13 | "button": "button1", "count": 1, "modifiers": ["alt"], 14 | "press_command": "drag_select", 15 | "press_args": {"subtractive": true} 16 | }, 17 | 18 | // Select between selection and click location 19 | { 20 | "button": "button1", "modifiers": ["shift"], 21 | "press_command": "drag_select", 22 | "press_args": {"extend": true} 23 | }, 24 | { 25 | "button": "button1", "modifiers": ["shift", "ctrl"], 26 | "press_command": "drag_select", 27 | "press_args": {"additive": true, "extend": true} 28 | }, 29 | { 30 | "button": "button1", "modifiers": ["shift", "alt"], 31 | "press_command": "drag_select", 32 | "press_args": {"subtractive": true, "extend": true} 33 | }, 34 | 35 | // Drag select by words 36 | { 37 | "button": "button1", "count": 2, 38 | "press_command": "drag_select", 39 | "press_args": {"by": "words"} 40 | }, 41 | { 42 | "button": "button1", "count": 2, "modifiers": ["ctrl"], 43 | "press_command": "drag_select", 44 | "press_args": {"by": "words", "additive": true} 45 | }, 46 | { 47 | "button": "button1", "count": 2, "modifiers": ["alt"], 48 | "press_command": "drag_select", 49 | "press_args": {"by": "words", "subtractive": true} 50 | }, 51 | 52 | // Drag select by lines 53 | { 54 | "button": "button1", "count": 3, 55 | "press_command": "drag_select", 56 | "press_args": {"by": "lines"} 57 | }, 58 | { 59 | "button": "button1", "count": 3, "modifiers": ["ctrl"], 60 | "press_command": "drag_select", 61 | "press_args": {"by": "lines", "additive": true} 62 | }, 63 | { 64 | "button": "button1", "count": 3, "modifiers": ["alt"], 65 | "press_command": "drag_select", 66 | "press_args": {"by": "lines", "subtractive": true} 67 | }, 68 | 69 | // Column select 70 | { 71 | "button": "button2", "modifiers": ["shift"], 72 | "press_command": "drag_select", 73 | "press_args": {"by": "columns"} 74 | }, 75 | { 76 | "button": "button2", "modifiers": ["shift", "ctrl"], 77 | "press_command": "drag_select", 78 | "press_args": {"by": "columns", "additive": true} 79 | }, 80 | { 81 | "button": "button2", "modifiers": ["shift", "alt"], 82 | "press_command": "drag_select", 83 | "press_args": {"by": "columns", "subtractive": true} 84 | }, 85 | 86 | // Middle click paste 87 | { "button": "button3", "command": "paste_selection_clipboard" }, 88 | 89 | // Switch files with buttons 4 and 5, as well as 8 and 9 90 | { "button": "button4", "modifiers": [], "command": "prev_view" }, 91 | { "button": "button5", "modifiers": [], "command": "next_view" }, 92 | { "button": "button8", "modifiers": [], "command": "prev_view" }, 93 | { "button": "button9", "modifiers": [], "command": "next_view" }, 94 | 95 | // Change font size with ctrl+scroll wheel 96 | { "button": "scroll_down", "modifiers": ["ctrl"], "command": "decrease_font_size" }, 97 | { "button": "scroll_up", "modifiers": ["ctrl"], "command": "increase_font_size" }, 98 | 99 | { "button": "button2", "modifiers": [], "press_command": "context_menu" } 100 | ] 101 | -------------------------------------------------------------------------------- /src/view/context/group.rs: -------------------------------------------------------------------------------- 1 | use core::Core; 2 | use view::modal::{Modal, ModalPosition}; 3 | use core::bindings::Key; 4 | use core::menu::{Menu}; 5 | 6 | use toolkit::*; 7 | 8 | use unicode_width::UnicodeWidthStr; 9 | 10 | use view::modal::ModalView; 11 | 12 | use view::theme::*; 13 | 14 | use super::menu::{ContextMenu, ContextMenuView}; 15 | 16 | #[derive(Debug)] 17 | pub struct Group { 18 | caption: String, 19 | modal: Modal, 20 | is_opened: bool 21 | } 22 | 23 | #[derive(Debug)] 24 | pub struct GroupView<'a> { 25 | pub caption: &'a str, 26 | pub is_focused: bool, 27 | pub modal: Option>>, 28 | } 29 | 30 | impl Group { 31 | pub fn new(caption: String, menu: Menu) -> Group { 32 | let modal = Modal::new(ContextMenu::new(menu), ModalPosition::RightTop); 33 | Group { caption: caption, modal: modal, is_opened: false } 34 | } 35 | } 36 | 37 | impl<'a> Widget<'a> for Group { 38 | type Context = (&'a Core, bool); 39 | type View = GroupView<'a>; 40 | 41 | fn view(&'a self, (core, focused): Self::Context) -> GroupView<'a> { 42 | GroupView { 43 | caption: &self.caption[..], 44 | is_focused: focused, 45 | modal: if self.is_opened { 46 | Some(self.modal.view(core)) 47 | } else { 48 | None 49 | } 50 | } 51 | } 52 | 53 | fn on_keypress(&'a mut self, (core, focused): Self::Context, canvas: Canvas, key: Key) -> bool { 54 | if self.is_opened { 55 | if self.modal.on_keypress(core, canvas, key) { 56 | return true 57 | } 58 | if key == Key::Left { 59 | self.unfocus((core, focused)); 60 | return true 61 | } 62 | } else { 63 | if key == Key::Right { 64 | self.focus((core, focused)); 65 | return true 66 | } 67 | } 68 | false 69 | } 70 | 71 | fn focus(&mut self, (core, _): Self::Context) { 72 | self.is_opened = true; 73 | self.modal.focus(core); 74 | } 75 | 76 | fn unfocus(&mut self, (core, _): Self::Context) { 77 | self.is_opened = false; 78 | self.modal.unfocus(core); 79 | } 80 | } 81 | 82 | impl<'a> GroupView<'a> { 83 | fn caption_style(&self) -> Style { 84 | if self.is_focused { 85 | MODAL_SELECTED_STYLE 86 | } else { 87 | MODAL_STYLE 88 | } 89 | } 90 | 91 | fn arrow_style(&self) -> Style { 92 | if self.is_focused { 93 | MODAL_SELECTED_LOW_STYLE 94 | } else { 95 | MODAL_LOW_STYLE 96 | } 97 | } 98 | } 99 | 100 | impl<'a> View for GroupView<'a> { 101 | fn width(&self) -> usize { 102 | self.caption.width() + 5 103 | } 104 | 105 | fn height(&self) -> usize { 106 | 1 107 | } 108 | 109 | fn render(&self, mut canvas: Canvas) { 110 | if let Some(ref view) = self.modal { 111 | view.render(canvas.clone()); 112 | } 113 | canvas.style(self.caption_style()); 114 | let left_canvas = canvas.cut_left(self.caption.width() + 1); 115 | let right_canvas = canvas.cut_right(2); 116 | left_canvas.char(' ', 0, 0); 117 | left_canvas.text(self.caption, 0, 1); 118 | canvas.fill(); 119 | right_canvas.style(self.arrow_style()); 120 | right_canvas.char('▸', 0, 0); 121 | right_canvas.char(' ', 0, 1); 122 | } 123 | } 124 | 125 | -------------------------------------------------------------------------------- /src/view/context/item.rs: -------------------------------------------------------------------------------- 1 | use core::Core; 2 | use core::bindings::Key; 3 | use core::menu::MenuItem; 4 | 5 | use toolkit::*; 6 | 7 | use unicode_width::UnicodeWidthStr; 8 | 9 | use view::theme::*; 10 | 11 | use super::button::{Button, ButtonView}; 12 | use super::group::{Group, GroupView}; 13 | 14 | #[derive(Debug)] 15 | pub enum ContextMenuItem { 16 | Divider, 17 | Button(Button), 18 | Group(Group), 19 | } 20 | 21 | #[derive(Debug)] 22 | pub enum ContextMenuItemView<'a> { 23 | Divider, 24 | Button(ButtonView<'a>), 25 | Group(GroupView<'a>), 26 | } 27 | 28 | impl<'a> Widget<'a> for ContextMenuItem { 29 | type Context = (&'a Core, bool); 30 | type View = ContextMenuItemView<'a>; 31 | 32 | fn enabled(&self, context: Self::Context) -> bool { 33 | match *self { 34 | ContextMenuItem::Button(ref button) => button.enabled(context), 35 | ContextMenuItem::Group(ref group) => group.enabled(context), 36 | ContextMenuItem::Divider => false 37 | } 38 | } 39 | 40 | fn view(&'a self, context: Self::Context) -> Self::View { 41 | match *self { 42 | ContextMenuItem::Button(ref button) => 43 | ContextMenuItemView::Button(button.view(context)), 44 | ContextMenuItem::Group(ref group) => 45 | ContextMenuItemView::Group(group.view(context)), 46 | ContextMenuItem::Divider => ContextMenuItemView::Divider 47 | } 48 | } 49 | 50 | fn on_keypress(&'a mut self, context: (&'a Core, bool), canvas: Canvas, key: Key) -> bool { 51 | match *self { 52 | ContextMenuItem::Button(ref mut button) => button.on_keypress(context, canvas, key), 53 | ContextMenuItem::Group(ref mut group) => group.on_keypress(context, canvas, key), 54 | ContextMenuItem::Divider => false 55 | } 56 | } 57 | 58 | fn unfocus(&mut self, context: Self::Context) { 59 | match *self { 60 | ContextMenuItem::Button(ref mut button) => button.unfocus(context), 61 | ContextMenuItem::Group(ref mut group) => group.unfocus(context), 62 | ContextMenuItem::Divider => {}, 63 | } 64 | } 65 | } 66 | 67 | impl From for ContextMenuItem { 68 | fn from(item: MenuItem) -> ContextMenuItem { 69 | match item { 70 | MenuItem::Button(caption, command, is_checkbox) => 71 | ContextMenuItem::Button(Button::new(caption, command, is_checkbox)), 72 | MenuItem::Group(caption, menu) => 73 | ContextMenuItem::Group(Group::new(caption, menu)), 74 | MenuItem::Divider => ContextMenuItem::Divider 75 | } 76 | } 77 | } 78 | 79 | impl<'a> View for ContextMenuItemView<'a> { 80 | fn width(&self) -> usize { 81 | match *self { 82 | ContextMenuItemView::Divider => 2, 83 | ContextMenuItemView::Group(ref view) => view.width(), 84 | ContextMenuItemView::Button(ref view) => view.width() 85 | } 86 | } 87 | 88 | fn height(&self) -> usize { 89 | match *self { 90 | ContextMenuItemView::Divider => 1, 91 | ContextMenuItemView::Group(ref view) => view.height(), 92 | ContextMenuItemView::Button(ref view) => view.height() 93 | } 94 | } 95 | 96 | fn render(&self, canvas: Canvas) { 97 | match *self { 98 | ContextMenuItemView::Divider => { 99 | canvas.style(MODAL_DISABLED_STYLE); 100 | canvas.fill_char('─'); 101 | }, 102 | ContextMenuItemView::Group(ref view) => view.render(canvas), 103 | ContextMenuItemView::Button(ref view) => view.render(canvas) 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/view/context/menu.rs: -------------------------------------------------------------------------------- 1 | use core::Core; 2 | use core::bindings::Key; 3 | use core::menu::Menu; 4 | 5 | use toolkit::*; 6 | 7 | use unicode_width::UnicodeWidthStr; 8 | 9 | use super::item::{ContextMenuItem, ContextMenuItemView}; 10 | 11 | #[derive(Debug)] 12 | pub struct ContextMenu { 13 | focused: Option, 14 | items: Vec 15 | } 16 | 17 | #[derive(Debug)] 18 | pub struct ContextMenuView<'a> { 19 | pub views: Vec> 20 | } 21 | 22 | impl ContextMenu { 23 | pub fn new(items: Menu) -> Self { 24 | ContextMenu {focused: None, items: items.into_iter().map(From::from).collect()} 25 | } 26 | 27 | fn focused(&mut self) -> Option<&mut ContextMenuItem> { 28 | match self.focused { 29 | Some(index) => Some(&mut self.items[index]), 30 | None => None 31 | } 32 | } 33 | 34 | fn focus_prev(&mut self, core: &Core) { 35 | let skip = self.focused.map_or(0, |i| self.items.len() - i); 36 | self.unfocus(core); 37 | self.focused = self.items 38 | .iter() 39 | .enumerate() 40 | .rev() 41 | .cycle() 42 | .skip(skip) 43 | .filter(|&(_, ref item)| item.enabled((core, false))) 44 | .next() 45 | .map(|(i, _)| i); 46 | } 47 | 48 | fn focus_next(&mut self, core: &Core) { 49 | let skip = self.focused.map_or(0, |i| i + 1); 50 | self.unfocus(core); 51 | self.focused = self.items 52 | .iter() 53 | .enumerate() 54 | .cycle() 55 | .skip(skip) 56 | .filter(|&(_, ref item)| item.enabled((core, false))) 57 | .next() 58 | .map(|(i, _)| i); 59 | } 60 | } 61 | 62 | impl<'a> Widget<'a> for ContextMenu { 63 | type Context = &'a Core; 64 | type View = ContextMenuView<'a>; 65 | 66 | fn view(&'a self, core: &'a Core) -> ContextMenuView<'a> { 67 | ContextMenuView { views: self.items 68 | .iter() 69 | .enumerate() 70 | .map(|(i, item)| item.view((core, Some(i) == self.focused))) 71 | .collect() 72 | } 73 | } 74 | 75 | fn on_keypress(&mut self, core: &Core, canvas: Canvas, key: Key) -> bool { 76 | let mut processed = false; 77 | if let Some(ref mut item) = self.focused() { 78 | if item.on_keypress((core, true), canvas, key) { 79 | processed = true 80 | } 81 | } 82 | if processed { 83 | self.view(core).render(canvas); 84 | return true 85 | } 86 | if key == Key::Up { 87 | self.focus_prev(core); 88 | self.view(core).render(canvas); 89 | return true 90 | } 91 | if key == Key::Down { 92 | self.focus_next(core); 93 | self.view(core).render(canvas); 94 | return true 95 | } 96 | false 97 | } 98 | 99 | fn focus(&mut self, core: &Core) { 100 | if self.focused == None { 101 | self.focus_next(core); 102 | } 103 | } 104 | 105 | fn unfocus(&mut self, core: &Core) { 106 | if let Some(item) = self.focused() { 107 | item.unfocus((core, true)); 108 | } 109 | self.focused = None; 110 | } 111 | } 112 | 113 | impl<'a> View for ContextMenuView<'a> { 114 | fn width(&self) -> usize { 115 | self.views.iter().map(|view| view.width()).max().unwrap_or(0) 116 | } 117 | 118 | fn height(&self) -> usize { 119 | self.views.len() 120 | } 121 | 122 | fn render(&self, mut canvas: Canvas) { 123 | for view in &self.views { 124 | let h = view.height(); 125 | if h > canvas.height() { 126 | break; 127 | } 128 | view.render(canvas.cut_top(h)); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /packages/default/Default (Windows).sublime-mousemap: -------------------------------------------------------------------------------- 1 | [ 2 | // Basic drag select 3 | { 4 | "button": "button1", "count": 1, 5 | "press_command": "drag_select" 6 | }, 7 | { 8 | "button": "button1", "count": 1, "modifiers": ["ctrl"], 9 | "press_command": "drag_select", 10 | "press_args": {"additive": true} 11 | }, 12 | { 13 | "button": "button1", "count": 1, "modifiers": ["alt"], 14 | "press_command": "drag_select", 15 | "press_args": {"subtractive": true} 16 | }, 17 | 18 | // Select between selection and click location 19 | { 20 | "button": "button1", "modifiers": ["shift"], 21 | "press_command": "drag_select", 22 | "press_args": {"extend": true} 23 | }, 24 | { 25 | "button": "button1", "modifiers": ["shift", "ctrl"], 26 | "press_command": "drag_select", 27 | "press_args": {"additive": true, "extend": true} 28 | }, 29 | { 30 | "button": "button1", "modifiers": ["shift", "alt"], 31 | "press_command": "drag_select", 32 | "press_args": {"subtractive": true, "extend": true} 33 | }, 34 | 35 | // Drag select by words 36 | { 37 | "button": "button1", "count": 2, 38 | "press_command": "drag_select", 39 | "press_args": {"by": "words"} 40 | }, 41 | { 42 | "button": "button1", "count": 2, "modifiers": ["ctrl"], 43 | "press_command": "drag_select", 44 | "press_args": {"by": "words", "additive": true} 45 | }, 46 | { 47 | "button": "button1", "count": 2, "modifiers": ["alt"], 48 | "press_command": "drag_select", 49 | "press_args": {"by": "words", "subtractive": true} 50 | }, 51 | 52 | // Drag select by lines 53 | { 54 | "button": "button1", "count": 3, 55 | "press_command": "drag_select", 56 | "press_args": {"by": "lines"} 57 | }, 58 | { 59 | "button": "button1", "count": 3, "modifiers": ["ctrl"], 60 | "press_command": "drag_select", 61 | "press_args": {"by": "lines", "additive": true} 62 | }, 63 | { 64 | "button": "button1", "count": 3, "modifiers": ["alt"], 65 | "press_command": "drag_select", 66 | "press_args": {"by": "lines", "subtractive": true} 67 | }, 68 | 69 | // Shift + Mouse 2 Column select 70 | { 71 | "button": "button2", "modifiers": ["shift"], 72 | "press_command": "drag_select", 73 | "press_args": {"by": "columns"} 74 | }, 75 | { 76 | "button": "button2", "modifiers": ["shift", "ctrl"], 77 | "press_command": "drag_select", 78 | "press_args": {"by": "columns", "additive": true} 79 | }, 80 | { 81 | "button": "button2", "modifiers": ["shift", "alt"], 82 | "press_command": "drag_select", 83 | "press_args": {"by": "columns", "subtractive": true} 84 | }, 85 | 86 | // Mouse 3 column select 87 | { 88 | "button": "button3", 89 | "press_command": "drag_select", 90 | "press_args": {"by": "columns"} 91 | }, 92 | { 93 | "button": "button3", "modifiers": ["ctrl"], 94 | "press_command": "drag_select", 95 | "press_args": {"by": "columns", "additive": true} 96 | }, 97 | { 98 | "button": "button3", "modifiers": ["alt"], 99 | "press_command": "drag_select", 100 | "press_args": {"by": "columns", "subtractive": true} 101 | }, 102 | 103 | // Simple chording: hold down mouse 2, and click mouse 1 104 | { 105 | "button": "button1", "count": 1, "modifiers": ["button2"], 106 | "command": "expand_selection", "args": {"to": "line"}, 107 | "press_command": "drag_select" 108 | }, 109 | { 110 | "button": "button1", "count": 2, "modifiers": ["button2"], 111 | "command": "expand_selection_to_paragraph" 112 | }, 113 | { 114 | "button": "button1", "count": 3, "modifiers": ["button2"], 115 | "command": "select_all" 116 | }, 117 | 118 | // Switch files with buttons 4 and 5 119 | { "button": "button4", "modifiers": [], "command": "prev_view" }, 120 | { "button": "button5", "modifiers": [], "command": "next_view" }, 121 | 122 | // Switch files by holding down button 2, and using the scroll wheel 123 | { "button": "scroll_down", "modifiers": ["button2"], "command": "next_view" }, 124 | { "button": "scroll_up", "modifiers": ["button2"], "command": "prev_view" }, 125 | 126 | // Change font size with ctrl+scroll wheel 127 | { "button": "scroll_down", "modifiers": ["ctrl"], "command": "decrease_font_size" }, 128 | { "button": "scroll_up", "modifiers": ["ctrl"], "command": "increase_font_size" }, 129 | 130 | { "button": "button2", "modifiers": [], "command": "context_menu" } 131 | ] 132 | -------------------------------------------------------------------------------- /src/core/syntax/highlighter.rs: -------------------------------------------------------------------------------- 1 | use std::iter::Iterator; 2 | 3 | use super::scope::{Scope, ScopePath, ScopeCommand, ScopeTree}; 4 | use super::theme::{Theme, ThemeSettings}; 5 | use super::style::{Style, StyleModifier, Color, FontStyle, BLACK, WHITE}; 6 | 7 | #[derive(Debug)] 8 | pub struct Highlighter { 9 | settings: ThemeSettings, 10 | foreground_tree: ScopeTree, 11 | background_tree: ScopeTree, 12 | font_style_tree: ScopeTree, 13 | } 14 | 15 | pub struct HighlightIterator<'a> { 16 | index: usize, 17 | pos: usize, 18 | path: ScopePath, 19 | changes: &'a [(usize, ScopeCommand)], 20 | text: &'a str, 21 | highlighter: &'a Highlighter, 22 | styles: Vec