├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ └── ci.yaml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Cargo.toml
├── LICENSE
├── README.md
├── examples
├── auto_pair.rs
├── cursor_style.rs
├── custom_prompt.rs
├── drop_down_auto_complete.rs
├── hex_color_highlighter.rs
├── input_filter.rs
├── key_bindings.rs
├── keyword_highlighter.rs
├── keyword_hinter.rs
├── matching_brackets_highlighters.rs
├── surround_selection.rs
├── text_prompt.rs
└── visual_selection.rs
├── media
├── auto_complete_demo.gif
└── line_editor_demo.gif
└── src
├── autopair
└── mod.rs
├── completion
└── mod.rs
├── core
├── editor.rs
├── event.rs
├── input_filter.rs
├── keybindings.rs
├── mod.rs
├── style.rs
└── styled_buffer.rs
├── engine.rs
├── highlighter
└── mod.rs
├── hinter
└── mod.rs
├── lib.rs
├── prompt
└── mod.rs
└── view
├── base.rs
├── drop_down_list_view.rs
├── list_view.rs
├── mod.rs
└── styled_editor_view.rs
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: amrdeveloper
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 |
7 | ---
8 |
9 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 |
12 | **To Reproduce**
13 | Steps to reproduce the behavior:
14 | 1. Go to '...'
15 | 2. Click on '....'
16 | 3. Scroll down to '....'
17 | 4. See error
18 |
19 | **Expected behavior**
20 | A clear and concise description of what you expected to happen.
21 |
22 | **Screenshots**
23 | If applicable, add screenshots to help explain your problem.
24 |
25 | **LineEditor (please complete the following information):**
26 | - Version [e.g. 1.0.0]
27 |
28 | **Additional context**
29 | Add any other context about the problem here.
30 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 |
7 | ---
8 |
9 | **Is your feature request related to a problem? Please describe.**
10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11 |
12 | **Describe the solution you'd like**
13 | A clear and concise description of what you want to happen.
14 |
15 | **Describe alternatives you've considered**
16 | A clear and concise description of any alternative solutions or features you've considered.
17 |
18 | **Additional context**
19 | Add any other context or screenshots about the feature request here.
20 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - master
10 |
11 | env:
12 | CARGO_TERM_COLOR: always
13 |
14 | jobs:
15 | fmt:
16 | name: Rustfmt
17 | runs-on: ubuntu-latest
18 | steps:
19 | - uses: actions/checkout@v3
20 | - uses: dtolnay/rust-toolchain@stable
21 | - run: cargo fmt --all -- --check
22 |
23 | clippy:
24 | name: Clippy
25 | runs-on: ubuntu-latest
26 | steps:
27 | - uses: actions/checkout@v3
28 | - uses: dtolnay/rust-toolchain@stable
29 | - run: cargo clippy -- -D warnings
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | Cargo.lock
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Change Log
2 | ==========
3 |
4 | Version 0.4.0 *(2024-10-26)*
5 | -----------------------------
6 |
7 | - Fix Paste Edit command
8 |
9 | Version 0.3.0 *(2024-10-26)*
10 | -----------------------------
11 |
12 | * Update Crossterm version.
13 | * Fix clearing the buffer after success command.
14 |
15 | Version 0.2.0 *(2023-12-29)*
16 | -----------------------------
17 |
18 | * Implement Cut, Copy, Paste.
19 | * Supports InputFilter with Mixed and Custom rules.
20 | * Implement ListView, DropDownListView.
21 |
22 | Version 0.1.0 *(2023-12-21)*
23 | -----------------------------
24 |
25 | * Syntax highlighter
26 | * Hinter
27 | * Auto Pair complete
28 | * Key bindings
29 | * Custom Prompt
30 | * Visual Selection
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | ## Code of Conduct
2 |
3 | ### Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | ### Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ### Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ### Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ### Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at amr96@programmer.net. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ### Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [http://contributor-covenant.org/version/1/4][version]
72 |
73 | [homepage]: http://contributor-covenant.org
74 | [version]: http://contributor-covenant.org/version/1/4/
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Please provide issue report in the format that we request, EACH DETAIL IS A HUGE HELP.
4 |
5 | Issues that are not following the guidelines,
6 | will be processed as last priority or never or simply closed as invalid.
7 |
8 | ## Contributing Guide
9 |
10 | Please note that PRs are looked only for approved issues.
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "lineeditor"
3 | version = "0.4.0"
4 | edition = "2021"
5 | repository = "https://github.com/amrdeveloper/lineeditor"
6 | license = "MIT"
7 | authors = ["AmrDeveloper"]
8 | description = "A cross platform rich line editor"
9 | readme = "README.md"
10 | keywords = ["line-editor", "gitql", "cli", "rich-editor"]
11 | categories = ["command-line-utilities"]
12 |
13 | [lib]
14 | doctest = true
15 |
16 | [dependencies]
17 | crossterm = "0.28.1"
18 | clipboard = "0.5.0"
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Amr Hesham
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
LineEditor
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | A new cross platform Line editor implementation inspired by reedline, rustyline and other cool line editors, designed with the goal to provides the core components that allows you to build your custom line editor with as mush as customization options.
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | > [!IMPORTANT]
24 | > LineEditor API is still experimental and may be changed from version to version until finishing the design and implementations of the basics features
25 |
26 | ### Basic example
27 |
28 | ```rs
29 | let prompt = StringPrompt::new("prompt> ".to_string());
30 | let line_editor = LineEditor::new(Box::new(prompt));
31 | match line_editor.read_line() {
32 | Ok(LineEditorResult::Success(buffer)) => {
33 |
34 | }
35 | _ => {}
36 | }
37 | ```
38 |
39 | ### Customization examples
40 | - [Text Prompt](/examples/text_prompt.rs)
41 | - [Custom Prompt](/examples/custom_prompt.rs)
42 | - [Cursor style](/examples/cursor_style.rs)
43 | - [Input Filter](/examples/input_filter.rs)
44 | - [Key bindings](/examples/key_bindings.rs)
45 | - [DropDown AutoComplete](/examples/drop_down_auto_complete.rs)
46 | - [Keywords Highlighter](/examples/keyword_highlighter.rs)
47 | - [Matching Brackets Highlighter](/examples/matching_brackets_highlighter.rs)
48 | - [Hex Color Highlighter](/examples/hex_color_highlighter.rs)
49 | - [Keywords Hinter](/examples/keyword_hinter.rs)
50 | - [Auto Pair complete](/examples/auto_pair.rs)
51 | - [Visual Selection](/examples/visual_selection.rs)
52 | - [Auto Surround Selection](/examples/surround_selection.rs)
53 |
54 | ### License
55 | ```
56 | MIT License
57 |
58 | Copyright (c) 2023 Amr Hesham
59 |
60 | Permission is hereby granted, free of charge, to any person obtaining a copy
61 | of this software and associated documentation files (the "Software"), to deal
62 | in the Software without restriction, including without limitation the rights
63 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
64 | copies of the Software, and to permit persons to whom the Software is
65 | furnished to do so, subject to the following conditions:
66 |
67 | The above copyright notice and this permission notice shall be included in all
68 | copies or substantial portions of the Software.
69 |
70 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
71 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
72 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
73 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
74 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
75 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
76 | SOFTWARE.
77 | ```
--------------------------------------------------------------------------------
/examples/auto_pair.rs:
--------------------------------------------------------------------------------
1 | use lineeditor::DefaultAutoPair;
2 | use lineeditor::LineEditor;
3 | use lineeditor::LineEditorResult;
4 | use lineeditor::StringPrompt;
5 |
6 | fn main() {
7 | let prompt = StringPrompt::new("prompt> ".to_string());
8 | let mut line_editor = LineEditor::new(Box::new(prompt));
9 | line_editor.set_auto_pair(Some(Box::::default()));
10 |
11 | let bindings = line_editor.keybinding();
12 | bindings.register_common_control_bindings();
13 |
14 | match line_editor.read_line() {
15 | Ok(LineEditorResult::Success(line)) => {
16 | println!("Line {}", line);
17 | }
18 | _ => {}
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/cursor_style.rs:
--------------------------------------------------------------------------------
1 | use lineeditor::LineEditor;
2 | use lineeditor::LineEditorResult;
3 | use lineeditor::SetCursorStyle;
4 | use lineeditor::StringPrompt;
5 |
6 | fn main() {
7 | let prompt = StringPrompt::new("prompt> ".to_string());
8 | let mut line_editor = LineEditor::new(Box::new(prompt));
9 | line_editor.set_cursor_style(Some(SetCursorStyle::BlinkingBlock));
10 |
11 | let bindings = line_editor.keybinding();
12 | bindings.register_common_control_bindings();
13 |
14 | match line_editor.read_line() {
15 | Ok(LineEditorResult::Success(line)) => {
16 | println!("Line {}", line);
17 | }
18 | _ => {}
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/custom_prompt.rs:
--------------------------------------------------------------------------------
1 | use lineeditor::styled_buffer::StyledBuffer;
2 | use lineeditor::LineEditor;
3 | use lineeditor::LineEditorResult;
4 | use lineeditor::Prompt;
5 |
6 | pub struct CurrentPathPrompt {}
7 |
8 | impl Prompt for CurrentPathPrompt {
9 | fn prompt(&self) -> StyledBuffer {
10 | let path = if let Ok(current_dir) = std::env::current_dir() {
11 | current_dir.to_string_lossy().to_string()
12 | } else {
13 | "".to_owned()
14 | };
15 | let mut styled_buffer = StyledBuffer::default();
16 | styled_buffer.insert_string(&format!("📁 {}> ", path));
17 | styled_buffer
18 | }
19 | }
20 |
21 | fn main() {
22 | let prompt = CurrentPathPrompt {};
23 | let mut line_editor = LineEditor::new(Box::new(prompt));
24 |
25 | let bindings = line_editor.keybinding();
26 | bindings.register_common_control_bindings();
27 |
28 | match line_editor.read_line() {
29 | Ok(LineEditorResult::Success(line)) => {
30 | println!("Line {}", line);
31 | }
32 | _ => {}
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/examples/drop_down_auto_complete.rs:
--------------------------------------------------------------------------------
1 | use lineeditor::event::LineEditorEvent;
2 | use lineeditor::keybindings::KeyCombination;
3 | use lineeditor::styled_buffer::StyledBuffer;
4 | use lineeditor::Completer;
5 | use lineeditor::KeyModifiers;
6 | use lineeditor::LineEditor;
7 | use lineeditor::LineEditorResult;
8 | use lineeditor::Span;
9 | use lineeditor::StringPrompt;
10 | use lineeditor::Suggestion;
11 |
12 | const GITQL_RESERVED_KEYWORDS: [&str; 31] = [
13 | "set", "select", "distinct", "from", "group", "where", "having", "offset", "limit", "order",
14 | "by", "case", "when", "then", "else", "end", "between", "in", "is", "not", "like", "glob",
15 | "or", "and", "xor", "true", "false", "null", "as", "asc", "desc",
16 | ];
17 |
18 | pub struct FixedCompleter {}
19 |
20 | impl Completer for FixedCompleter {
21 | fn complete(&self, input: &StyledBuffer) -> Vec {
22 | let mut suggestions: Vec = vec![];
23 | if input.position() != input.len() {
24 | return suggestions;
25 | }
26 |
27 | if let Some(keyword) = input.last_alphabetic_keyword() {
28 | for reserved_keyword in GITQL_RESERVED_KEYWORDS {
29 | if reserved_keyword.starts_with(&keyword) {
30 | let suggestion = Suggestion {
31 | content: StyledBuffer::from(reserved_keyword),
32 | span: Span {
33 | start: input.len() - keyword.len(),
34 | end: input.len(),
35 | },
36 | };
37 | suggestions.push(suggestion);
38 | }
39 | }
40 | }
41 | suggestions
42 | }
43 | }
44 |
45 | fn main() {
46 | let prompt = StringPrompt::new("prompt> ".to_string());
47 | let mut line_editor = LineEditor::new(Box::new(prompt));
48 |
49 | line_editor.set_completer(Box::new(FixedCompleter {}));
50 |
51 | let bindings = line_editor.keybinding();
52 |
53 | bindings.register_binding(
54 | KeyCombination {
55 | key_kind: lineeditor::KeyEventKind::Press,
56 | modifier: KeyModifiers::NONE,
57 | key_code: lineeditor::KeyCode::Tab,
58 | },
59 | LineEditorEvent::ToggleAutoComplete,
60 | );
61 | bindings.register_common_control_bindings();
62 | bindings.register_common_navigation_bindings();
63 | bindings.register_common_edit_bindings();
64 | bindings.register_common_selection_bindings();
65 |
66 | match line_editor.read_line() {
67 | Ok(LineEditorResult::Success(line)) => {
68 | println!("Line {}", line);
69 | }
70 | _ => {}
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/examples/hex_color_highlighter.rs:
--------------------------------------------------------------------------------
1 | use lineeditor::style::Style;
2 | use lineeditor::styled_buffer::StyledBuffer;
3 | use lineeditor::Color;
4 | use lineeditor::Highlighter;
5 | use lineeditor::LineEditor;
6 | use lineeditor::LineEditorResult;
7 | use lineeditor::StringPrompt;
8 |
9 | #[derive(Default)]
10 | pub struct HexColorHighlighter {}
11 |
12 | impl Highlighter for HexColorHighlighter {
13 | fn highlight(&self, buffer: &mut StyledBuffer) {
14 | let lines = buffer.buffer().clone();
15 | let mut i: usize = 0;
16 |
17 | loop {
18 | if i >= lines.len() {
19 | break;
20 | }
21 |
22 | // Highlight String literal
23 | if lines[i] == '"' {
24 | i += 1;
25 |
26 | while i < lines.len() && lines[i] != '"' {
27 | i += 1;
28 | }
29 |
30 | if i < lines.len() && lines[i] == '"' {
31 | i += 1;
32 | }
33 |
34 | continue;
35 | }
36 |
37 | // Highlight hex value background with it value
38 | if lines[i] == '#' && i + 6 < lines.len() {
39 | let start = i;
40 | i += 1;
41 | let hex_value = &lines[i..i + 6];
42 | for ch in hex_value {
43 | if !ch.is_ascii_hexdigit() {
44 | return;
45 | }
46 | }
47 | let hex_string = hex_value.iter().cloned().collect::();
48 | if let Ok(hex_value) = usize::from_str_radix(&hex_string, 16) {
49 | let red = (hex_value >> 16) & 0xFF;
50 | let green = (hex_value >> 8) & 0xFF;
51 | let blue = hex_value & 0xFF;
52 | let mut style = Style::default();
53 | style.set_background_color(Color::Rgb {
54 | r: red as u8,
55 | g: green as u8,
56 | b: blue as u8,
57 | });
58 | buffer.style_range(start, start + 7, style);
59 | }
60 | }
61 |
62 | i += 1;
63 | }
64 | }
65 | }
66 |
67 | fn main() {
68 | let prompt = StringPrompt::new("prompt> ".to_string());
69 | let mut line_editor = LineEditor::new(Box::new(prompt));
70 |
71 | line_editor.add_highlighter(Box::::default());
72 |
73 | let bindings = line_editor.keybinding();
74 | bindings.register_common_control_bindings();
75 | bindings.register_common_navigation_bindings();
76 | bindings.register_common_edit_bindings();
77 | bindings.register_common_selection_bindings();
78 |
79 | match line_editor.read_line() {
80 | Ok(LineEditorResult::Success(line)) => {
81 | println!("Line {}", line);
82 | }
83 | _ => {}
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/examples/input_filter.rs:
--------------------------------------------------------------------------------
1 | use lineeditor::input_filter::InputFilter;
2 | use lineeditor::LineEditor;
3 | use lineeditor::LineEditorResult;
4 | use lineeditor::StringPrompt;
5 |
6 | fn main() {
7 | let prompt = StringPrompt::new("prompt> ".to_string());
8 | let mut line_editor = LineEditor::new(Box::new(prompt));
9 |
10 | line_editor.set_input_filter(InputFilter::Options(vec![
11 | Box::new(InputFilter::Digit),
12 | Box::new(InputFilter::Whitespace),
13 | ]));
14 |
15 | let bindings = line_editor.keybinding();
16 | bindings.register_common_control_bindings();
17 | bindings.register_common_navigation_bindings();
18 | bindings.register_common_edit_bindings();
19 | bindings.register_common_selection_bindings();
20 |
21 | match line_editor.read_line() {
22 | Ok(LineEditorResult::Success(line)) => {
23 | println!("Line {}", line);
24 | }
25 | _ => {}
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/key_bindings.rs:
--------------------------------------------------------------------------------
1 | use lineeditor::LineEditor;
2 | use lineeditor::LineEditorResult;
3 | use lineeditor::StringPrompt;
4 |
5 | fn main() {
6 | let prompt = StringPrompt::new("prompt> ".to_string());
7 | let mut line_editor = LineEditor::new(Box::new(prompt));
8 |
9 | let bindings = line_editor.keybinding();
10 | bindings.register_common_control_bindings();
11 | bindings.register_common_navigation_bindings();
12 | bindings.register_common_edit_bindings();
13 | bindings.register_common_selection_bindings();
14 |
15 | match line_editor.read_line() {
16 | Ok(LineEditorResult::Success(line)) => {
17 | println!("Line {}", line);
18 | }
19 | _ => {}
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/keyword_highlighter.rs:
--------------------------------------------------------------------------------
1 | use lineeditor::style::Style;
2 | use lineeditor::styled_buffer::StyledBuffer;
3 | use lineeditor::Color;
4 | use lineeditor::Highlighter;
5 | use lineeditor::LineEditor;
6 | use lineeditor::LineEditorResult;
7 | use lineeditor::StringPrompt;
8 |
9 | const GITQL_RESERVED_KEYWORDS: [&str; 31] = [
10 | "set", "select", "distinct", "from", "group", "where", "having", "offset", "limit", "order",
11 | "by", "case", "when", "then", "else", "end", "between", "in", "is", "not", "like", "glob",
12 | "or", "and", "xor", "true", "false", "null", "as", "asc", "desc",
13 | ];
14 |
15 | #[derive(Default)]
16 | pub struct GitQLHighlighter {}
17 |
18 | impl Highlighter for GitQLHighlighter {
19 | fn highlight(&self, buffer: &mut StyledBuffer) {
20 | let lines = buffer.buffer().clone();
21 | let mut i: usize = 0;
22 |
23 | let mut keyword_style = Style::default();
24 | keyword_style.set_foreground_color(Color::Magenta);
25 |
26 | let mut string_style = Style::default();
27 | string_style.set_foreground_color(Color::Yellow);
28 |
29 | loop {
30 | if i >= lines.len() {
31 | break;
32 | }
33 |
34 | // Highlight String literal
35 | if lines[i] == '"' {
36 | buffer.style_char(i, string_style.clone());
37 | i += 1;
38 |
39 | while i < lines.len() && lines[i] != '"' {
40 | buffer.style_char(i, string_style.clone());
41 | i += 1;
42 | }
43 |
44 | if i < lines.len() && lines[i] == '"' {
45 | buffer.style_char(i, string_style.clone());
46 | i += 1;
47 | }
48 |
49 | continue;
50 | }
51 |
52 | // Highlight reserved keyword
53 | if lines[i].is_alphabetic() {
54 | let start = i;
55 | let mut keyword = String::new();
56 | while i < lines.len() && (lines[i].is_alphanumeric() || lines[i] == '_') {
57 | keyword.push(lines[i]);
58 | i += 1;
59 | }
60 |
61 | keyword = keyword.to_lowercase();
62 | if GITQL_RESERVED_KEYWORDS.contains(&keyword.as_str()) {
63 | buffer.style_range(start, i, keyword_style.clone())
64 | }
65 | continue;
66 | }
67 |
68 | i += 1;
69 | }
70 | }
71 | }
72 |
73 | fn main() {
74 | let prompt = StringPrompt::new("prompt> ".to_string());
75 | let mut line_editor = LineEditor::new(Box::new(prompt));
76 |
77 | let bindings = line_editor.keybinding();
78 | bindings.register_common_control_bindings();
79 |
80 | line_editor.add_highlighter(Box::::default());
81 |
82 | match line_editor.read_line() {
83 | Ok(LineEditorResult::Success(line)) => {
84 | println!("Line {}", line);
85 | }
86 | _ => {}
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/examples/keyword_hinter.rs:
--------------------------------------------------------------------------------
1 | use lineeditor::style::Style;
2 | use lineeditor::styled_buffer::StyledBuffer;
3 | use lineeditor::Color;
4 | use lineeditor::Hinter;
5 | use lineeditor::LineEditor;
6 | use lineeditor::LineEditorResult;
7 | use lineeditor::StringPrompt;
8 |
9 | const GITQL_RESERVED_KEYWORDS: [&str; 31] = [
10 | "set", "select", "distinct", "from", "group", "where", "having", "offset", "limit", "order",
11 | "by", "case", "when", "then", "else", "end", "between", "in", "is", "not", "like", "glob",
12 | "or", "and", "xor", "true", "false", "null", "as", "asc", "desc",
13 | ];
14 |
15 | #[derive(Default)]
16 | pub struct GitQLHinter {}
17 |
18 | impl Hinter for GitQLHinter {
19 | fn hint(&self, buffer: &mut StyledBuffer) -> Option {
20 | if let Some(keyword) = buffer.last_alphabetic_keyword() {
21 | let keyword_lower = keyword.to_lowercase();
22 | for word in GITQL_RESERVED_KEYWORDS {
23 | if word.starts_with(&keyword_lower) {
24 | let hint = &word[keyword.len()..];
25 | let mut styled_buffer = StyledBuffer::default();
26 | let mut style = Style::default();
27 | style.set_foreground_color(Color::DarkGrey);
28 | styled_buffer.insert_styled_string(hint, style);
29 | return Some(styled_buffer);
30 | }
31 | }
32 | }
33 | None
34 | }
35 | }
36 |
37 | fn main() {
38 | let prompt = StringPrompt::new("prompt> ".to_string());
39 | let mut line_editor = LineEditor::new(Box::new(prompt));
40 | line_editor.add_hinter(Box::::default());
41 |
42 | let bindings = line_editor.keybinding();
43 | bindings.register_common_control_bindings();
44 |
45 | match line_editor.read_line() {
46 | Ok(LineEditorResult::Success(line)) => {
47 | println!("Line {}", line);
48 | }
49 | _ => {}
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/examples/matching_brackets_highlighters.rs:
--------------------------------------------------------------------------------
1 | use lineeditor::style::Style;
2 | use lineeditor::styled_buffer::StyledBuffer;
3 | use lineeditor::Color;
4 | use lineeditor::Highlighter;
5 | use lineeditor::LineEditor;
6 | use lineeditor::LineEditorResult;
7 | use lineeditor::StringPrompt;
8 |
9 | #[derive(Default)]
10 | pub struct MatchingBracketsHighlighter {}
11 |
12 | impl Highlighter for MatchingBracketsHighlighter {
13 | fn highlight(&self, buffer: &mut StyledBuffer) {
14 | let colors = vec![Color::Red, Color::Blue, Color::Yellow, Color::Green];
15 | let mut brackets_stack: Vec = vec![];
16 | let mut current_color_index = 0;
17 |
18 | let lines = buffer.buffer().clone();
19 | let mut i: usize = 0;
20 | loop {
21 | if i >= lines.len() {
22 | break;
23 | }
24 |
25 | if lines[i] == '"' {
26 | i += 1;
27 | while i < lines.len() && lines[i] != '"' {
28 | i += 1;
29 | }
30 |
31 | if i < lines.len() {
32 | i += 1;
33 | }
34 | continue;
35 | }
36 |
37 | if lines[i] == '(' || lines[i] == '<' || lines[i] == '[' || lines[i] == '{' {
38 | if current_color_index >= colors.len() {
39 | current_color_index = 0;
40 | }
41 |
42 | let color = colors[current_color_index];
43 | current_color_index += 1;
44 |
45 | brackets_stack.push(color);
46 |
47 | let mut style = Style::default();
48 | style.set_foreground_color(color);
49 | buffer.style_char(i, style);
50 | i += 1;
51 | continue;
52 | }
53 |
54 | if lines[i] == ')' || lines[i] == '>' || lines[i] == ']' || lines[i] == '}' {
55 | let color = if brackets_stack.is_empty() {
56 | colors[0]
57 | } else {
58 | brackets_stack.pop().unwrap()
59 | };
60 |
61 | let mut style = Style::default();
62 | style.set_foreground_color(color);
63 | buffer.style_char(i, style);
64 |
65 | i += 1;
66 | continue;
67 | }
68 | i += 1;
69 | }
70 | }
71 | }
72 |
73 | fn main() {
74 | let prompt = StringPrompt::new("gql> ".to_string());
75 | let mut line_editor = LineEditor::new(Box::new(prompt));
76 | line_editor.add_highlighter(Box::::default());
77 |
78 | let bindings = line_editor.keybinding();
79 | bindings.register_common_control_bindings();
80 |
81 | match line_editor.read_line() {
82 | Ok(LineEditorResult::Success(line)) => {
83 | println!("Line {}", line);
84 | }
85 | _ => {}
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/examples/surround_selection.rs:
--------------------------------------------------------------------------------
1 | use lineeditor::style::Style;
2 | use lineeditor::LineEditor;
3 | use lineeditor::LineEditorResult;
4 | use lineeditor::StringPrompt;
5 |
6 | fn main() {
7 | let prompt = StringPrompt::new("prompt> ".to_string());
8 | let mut line_editor = LineEditor::new(Box::new(prompt));
9 |
10 | let mut style = Style::default();
11 | style.set_background_color(lineeditor::Color::Cyan);
12 | line_editor.set_visual_selection_style(Some(style));
13 | line_editor.enable_surround_selection(true);
14 |
15 | let bindings = line_editor.keybinding();
16 | bindings.register_common_control_bindings();
17 | bindings.register_common_navigation_bindings();
18 | bindings.register_common_edit_bindings();
19 | bindings.register_common_selection_bindings();
20 |
21 | match line_editor.read_line() {
22 | Ok(LineEditorResult::Success(line)) => {
23 | println!("Line {}", line);
24 | }
25 | _ => {}
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/text_prompt.rs:
--------------------------------------------------------------------------------
1 | use lineeditor::LineEditor;
2 | use lineeditor::LineEditorResult;
3 | use lineeditor::StringPrompt;
4 |
5 | fn main() {
6 | let prompt = StringPrompt::new("prompt> ".to_string());
7 | let mut line_editor = LineEditor::new(Box::new(prompt));
8 |
9 | let bindings = line_editor.keybinding();
10 | bindings.register_common_control_bindings();
11 |
12 | match line_editor.read_line() {
13 | Ok(LineEditorResult::Success(line)) => {
14 | println!("Line {}", line);
15 | }
16 | _ => {}
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/examples/visual_selection.rs:
--------------------------------------------------------------------------------
1 | use lineeditor::style::Style;
2 | use lineeditor::LineEditor;
3 | use lineeditor::LineEditorResult;
4 | use lineeditor::StringPrompt;
5 |
6 | fn main() {
7 | let prompt = StringPrompt::new("prompt> ".to_string());
8 | let mut line_editor = LineEditor::new(Box::new(prompt));
9 |
10 | let mut style = Style::default();
11 | style.set_background_color(lineeditor::Color::Cyan);
12 | line_editor.set_visual_selection_style(Some(style));
13 |
14 | let bindings = line_editor.keybinding();
15 | bindings.register_common_control_bindings();
16 | bindings.register_common_navigation_bindings();
17 | bindings.register_common_edit_bindings();
18 | bindings.register_common_selection_bindings();
19 |
20 | match line_editor.read_line() {
21 | Ok(LineEditorResult::Success(line)) => {
22 | println!("Line {}", line);
23 | }
24 | _ => {}
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/media/auto_complete_demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmrDeveloper/Lineeditor/bb7228fc78e2c54c7d2d29e60b31d66cf11b7ee8/media/auto_complete_demo.gif
--------------------------------------------------------------------------------
/media/line_editor_demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmrDeveloper/Lineeditor/bb7228fc78e2c54c7d2d29e60b31d66cf11b7ee8/media/line_editor_demo.gif
--------------------------------------------------------------------------------
/src/autopair/mod.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 |
3 | use crate::styled_buffer::StyledBuffer;
4 |
5 | /// List of common pairs
6 | pub const DEFAULT_PAIRS: &[(char, char)] = &[
7 | ('(', ')'),
8 | ('{', '}'),
9 | ('[', ']'),
10 | ('\'', '\''),
11 | ('"', '"'),
12 | ('`', '`'),
13 | ];
14 |
15 | /// The Auto pair trait. Implementers of this trait will take the current styled buffer and then
16 | /// modify it, which represents the contents of the original line
17 | pub trait AutoPair {
18 | /// The action that will handle the current styled buffer as a line
19 | fn complete_pair(&self, buffer: &mut StyledBuffer);
20 | }
21 |
22 | /// Auto pair complete that depend on a map of pairs
23 | pub struct DefaultAutoPair {
24 | pairs: HashMap,
25 | }
26 |
27 | /// Create instance of DefaultAutoPair with default pairs [`DEFAULT_PAIRS`]
28 | impl Default for DefaultAutoPair {
29 | fn default() -> Self {
30 | let mut pairs = HashMap::with_capacity(DEFAULT_PAIRS.len());
31 | for pair in DEFAULT_PAIRS {
32 | pairs.insert(pair.0, pair.1);
33 | }
34 | Self { pairs }
35 | }
36 | }
37 |
38 | impl DefaultAutoPair {
39 | /// Create instance of DefaultAutoPair with custom pairs
40 | pub fn with_pairs(pairs: HashMap) -> Self {
41 | Self { pairs }
42 | }
43 | }
44 |
45 | impl AutoPair for DefaultAutoPair {
46 | /// Complete the pair if it exists on the pairs map and cursor is at the end
47 | fn complete_pair(&self, buffer: &mut StyledBuffer) {
48 | if buffer.position() == buffer.len() {
49 | if let Some(last_char) = buffer.buffer().last() {
50 | if let Some(pair) = self.pairs.get(last_char) {
51 | buffer.insert_char(*pair);
52 | buffer.move_char_left();
53 | }
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/completion/mod.rs:
--------------------------------------------------------------------------------
1 | use crate::styled_buffer::StyledBuffer;
2 |
3 | /// A span of source code, with positions
4 | pub struct Span {
5 | pub start: usize,
6 | pub end: usize,
7 | }
8 |
9 | impl Span {
10 | pub fn new(start: usize, end: usize) -> Self {
11 | Span { start, end }
12 | }
13 | }
14 |
15 | /// Suggestion returned by the Completer
16 | pub struct Suggestion {
17 | /// Suggestion content and styles
18 | pub content: StyledBuffer,
19 | /// Replacement span
20 | pub span: Span,
21 | }
22 |
23 | /// The Completer trait, Implementers of this trait will return a list of suggestions as styled buffers
24 | pub trait Completer {
25 | /// The action that will return a list of suggestions
26 | fn complete(&self, input: &StyledBuffer) -> Vec;
27 | }
28 |
--------------------------------------------------------------------------------
/src/core/editor.rs:
--------------------------------------------------------------------------------
1 | use crate::event::MovementCommand;
2 |
3 | use super::event::EditCommand;
4 | use super::styled_buffer::StyledBuffer;
5 |
6 | /// Wrapper for the Buffer to make it easy to run edit commands
7 | pub struct Editor {
8 | buffer: StyledBuffer,
9 | }
10 |
11 | /// Create a new instance of [`Editor`]
12 | impl Default for Editor {
13 | fn default() -> Self {
14 | Self {
15 | buffer: Default::default(),
16 | }
17 | }
18 | }
19 |
20 | impl Editor {
21 | /// Get the current [`StyledBuffer`]
22 | pub fn styled_buffer(&mut self) -> &mut StyledBuffer {
23 | &mut self.buffer
24 | }
25 |
26 | /// Apply [`EditCommand`] to the current buffer
27 | pub fn run_edit_commands(&mut self, command: &EditCommand) {
28 | match command {
29 | EditCommand::InsertChar(c) => self.buffer.insert_char(*c),
30 | EditCommand::InsertString(s) => self.buffer.insert_string(s),
31 | EditCommand::DeleteLeftChar => self.buffer.delete_left_char(),
32 | EditCommand::DeleteRightChar => self.buffer.delete_right_char(),
33 | EditCommand::DeleteSpan(from, to) => self.buffer.delete_range(*from, *to),
34 | EditCommand::Clear => self.buffer.clear(),
35 | }
36 | }
37 |
38 | /// Apply [`MovementCommand`] to the current buffer
39 | pub fn run_movement_commands(&mut self, command: &MovementCommand) {
40 | match command {
41 | MovementCommand::MoveToStart => self.buffer.move_to_start(),
42 | MovementCommand::MoveToEnd => self.buffer.move_to_end(),
43 | MovementCommand::MoveLeftChar => self.buffer.move_char_left(),
44 | MovementCommand::MoveRightChar => self.buffer.move_char_right(),
45 | MovementCommand::MoveLeftWord => self.buffer.move_word_left(),
46 | MovementCommand::MoveRightWord => self.buffer.move_word_right(),
47 | MovementCommand::MoveToPosition(position) => self.buffer.set_position(*position),
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/core/event.rs:
--------------------------------------------------------------------------------
1 | /// Editing actions which can be mapped to key bindings.
2 | ///
3 | /// Executed by `Editor::run_edit_commands()`
4 | #[derive(Clone)]
5 | pub enum EditCommand {
6 | /// Insert a character at the current insertion point
7 | InsertChar(char),
8 |
9 | /// Insert a string at the current insertion point
10 | InsertString(String),
11 |
12 | /// Backspace delete from the current insertion point
13 | DeleteLeftChar,
14 |
15 | /// Delete in-place from the current insertion point
16 | DeleteRightChar,
17 |
18 | /// Delete in-place range
19 | DeleteSpan(usize, usize),
20 |
21 | /// Clear the current buffer
22 | Clear,
23 | }
24 |
25 | /// Movements actions which can be mapped to key bindings.
26 | #[derive(Clone)]
27 | pub enum MovementCommand {
28 | /// Move to the start of the buffer
29 | MoveToStart,
30 |
31 | /// Move to the end of the buffer
32 | MoveToEnd,
33 |
34 | /// Move one character to the left
35 | MoveLeftChar,
36 |
37 | /// Move one character to the right
38 | MoveRightChar,
39 |
40 | /// Move one word to the left
41 | MoveLeftWord,
42 |
43 | /// Move one word to the right
44 | MoveRightWord,
45 |
46 | /// Move to position
47 | MoveToPosition(usize),
48 | }
49 |
50 | /// LineEditor supported actions.
51 | #[derive(Clone)]
52 | pub enum LineEditorEvent {
53 | /// No op event
54 | None,
55 |
56 | /// Handle enter event
57 | Enter,
58 |
59 | /// Esc event
60 | Esc,
61 |
62 | /// Handle unconditional submit event
63 | Submit,
64 |
65 | /// Run these commands in the editor
66 | Edit(Vec),
67 |
68 | /// Run movements commands in the editor
69 | Movement(Vec),
70 |
71 | /// Move up to the previous line, if multiline, or up into the historic buffers
72 | Up,
73 |
74 | /// Move down to the next line, if multiline, or down through the historic buffers
75 | Down,
76 |
77 | /// Move right to the next column, completion entry, or complete hint
78 | Right,
79 |
80 | /// Move left to the next column, or completion entry
81 | Left,
82 |
83 | /// Select one character to the right
84 | SelectRight,
85 |
86 | /// Select one character to the left
87 | SelectLeft,
88 |
89 | /// Select all buffer
90 | SelectAll,
91 |
92 | /// Cut the selected text into clipboard
93 | CutSelected,
94 |
95 | /// Copy the selected text into clipboard
96 | CopySelected,
97 |
98 | /// Paste text from clipboard into selection or at insertion point
99 | Paste,
100 |
101 | /// Delete char from the left or delete selected range
102 | Backspace,
103 |
104 | /// Delete char from the right or delete selected range
105 | Delete,
106 |
107 | /// Show or Hide Auto Complete view depend on the state
108 | ToggleAutoComplete,
109 | }
110 |
--------------------------------------------------------------------------------
/src/core/input_filter.rs:
--------------------------------------------------------------------------------
1 | /// Input filter used to ignore any input character that not matching the rules
2 | /// You can Mix one or more rules to make your own custom rules
3 | ///
4 | /// ## Examples of mixed rules
5 | ///
6 | /// ```
7 | /// // To Make rules that accept any alphabetic character or whitespace
8 | /// InputFilter::Options(vec![Box::new(InputFilter::Alphabetic), Box::new(InputFilter::Whitespace)])
9 | ///
10 | /// // To Make rules that accept any Text but not accept Punctuation
11 | /// InputFilter::Options(vec![Box::new(InputFilter::Alphabetic), Box::new(Not(Box::new(InputFilter::Punctuation)))])
12 | /// ```
13 | ///
14 | pub enum InputFilter {
15 | /// A-Z and a-z
16 | Alphabetic,
17 | /// A-Z, a-z and 0-9
18 | AlphaNumeric,
19 | /// 0-9
20 | Digit,
21 | /// Any character
22 | Text,
23 | /// a-f, A-F and 0-9
24 | HexDigit,
25 | /// Whitespace
26 | Whitespace,
27 | /// Punctuation
28 | Punctuation,
29 | /// Allow everything except One InputFilter
30 | Not(Box),
31 | /// Valid if one of the char is matching at least one of the InputFilters
32 | Options(Vec>),
33 | /// User defined input filter function
34 | Custom(fn(char) -> bool),
35 | }
36 |
37 | /// Input Filter function that returns true if character is matching the rules of the given InputFilter
38 | pub fn filter_input(ch: char, input_filter: &InputFilter) -> bool {
39 | match input_filter {
40 | InputFilter::Alphabetic => ch.is_alphabetic(),
41 | InputFilter::AlphaNumeric => ch.is_alphanumeric(),
42 | InputFilter::Text => true,
43 | InputFilter::Digit => ch.is_numeric(),
44 | InputFilter::HexDigit => ch.is_ascii_hexdigit(),
45 | InputFilter::Whitespace => ch.is_whitespace(),
46 | InputFilter::Punctuation => ch.is_ascii_punctuation(),
47 | InputFilter::Not(filter) => !filter_input(ch, filter),
48 | InputFilter::Options(input_filters) => {
49 | for filter in input_filters {
50 | if filter_input(ch, filter) {
51 | return true;
52 | }
53 | }
54 | false
55 | }
56 | InputFilter::Custom(function) => function(ch),
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/core/keybindings.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 | use std::hash::Hash;
3 |
4 | use crossterm::event::KeyCode;
5 | use crossterm::event::KeyEvent;
6 | use crossterm::event::KeyEventKind;
7 | use crossterm::event::KeyModifiers;
8 |
9 | use crate::event::MovementCommand;
10 |
11 | use super::event::LineEditorEvent;
12 |
13 | /// Represent the key combination
14 | #[derive(Clone, PartialEq, Eq, Hash, Debug)]
15 | pub struct KeyCombination {
16 | pub key_kind: KeyEventKind,
17 | pub modifier: KeyModifiers,
18 | pub key_code: KeyCode,
19 | }
20 |
21 | /// Create KeyCombination from crossterm KeyEvent
22 | impl From for KeyCombination {
23 | fn from(key_event: KeyEvent) -> Self {
24 | KeyCombination {
25 | key_kind: key_event.kind,
26 | modifier: key_event.modifiers,
27 | key_code: key_event.code,
28 | }
29 | }
30 | }
31 |
32 | /// Map of keybindings and [`LineEditorEvent`]
33 | pub struct Keybindings {
34 | /// Defines a keybinding for a reedline event
35 | pub bindings: HashMap,
36 | }
37 |
38 | /// Create a new instance of [`Keybindings`]
39 | impl Default for Keybindings {
40 | fn default() -> Self {
41 | Keybindings {
42 | bindings: HashMap::new(),
43 | }
44 | }
45 | }
46 |
47 | impl Keybindings {
48 | /// Register an [`LineEditorEvent`] for specific key combination
49 | pub fn register_binding(&mut self, key_combination: KeyCombination, event: LineEditorEvent) {
50 | self.bindings.insert(key_combination, event);
51 | }
52 |
53 | /// Find a keybinding based on the modifier and keycode
54 | pub fn find_binding(&self, key_combination: KeyCombination) -> Option {
55 | self.bindings.get(&key_combination).cloned()
56 | }
57 |
58 | /// Get assigned keybindings
59 | pub fn keybindings(&self) -> &HashMap {
60 | &self.bindings
61 | }
62 |
63 | /// Register basic functionality to Control
64 | ///
65 | /// `Enter`, `Esc`
66 | pub fn register_common_control_bindings(&mut self) {
67 | self.register_binding(
68 | KeyCombination {
69 | key_kind: KeyEventKind::Press,
70 | modifier: KeyModifiers::NONE,
71 | key_code: KeyCode::Enter,
72 | },
73 | LineEditorEvent::Enter,
74 | );
75 |
76 | self.register_binding(
77 | KeyCombination {
78 | key_kind: KeyEventKind::Press,
79 | modifier: KeyModifiers::NONE,
80 | key_code: KeyCode::Esc,
81 | },
82 | LineEditorEvent::Esc,
83 | );
84 | }
85 |
86 | /// Register basic functionality to Navigation
87 | ///
88 | /// `Up`, `Down`, `Right`, `Left` Arrow keys
89 | /// `CTRL + Right`, `CTRL + Left`
90 | /// `Home`, `End`
91 | pub fn register_common_navigation_bindings(&mut self) {
92 | self.register_binding(
93 | KeyCombination {
94 | key_kind: KeyEventKind::Press,
95 | modifier: KeyModifiers::NONE,
96 | key_code: KeyCode::Up,
97 | },
98 | LineEditorEvent::Up,
99 | );
100 |
101 | self.register_binding(
102 | KeyCombination {
103 | key_kind: KeyEventKind::Press,
104 | modifier: KeyModifiers::NONE,
105 | key_code: KeyCode::Down,
106 | },
107 | LineEditorEvent::Down,
108 | );
109 |
110 | self.register_binding(
111 | KeyCombination {
112 | key_kind: KeyEventKind::Press,
113 | modifier: KeyModifiers::NONE,
114 | key_code: KeyCode::Left,
115 | },
116 | LineEditorEvent::Left,
117 | );
118 |
119 | self.register_binding(
120 | KeyCombination {
121 | key_kind: KeyEventKind::Press,
122 | modifier: KeyModifiers::NONE,
123 | key_code: KeyCode::Right,
124 | },
125 | LineEditorEvent::Right,
126 | );
127 |
128 | self.register_binding(
129 | KeyCombination {
130 | key_kind: KeyEventKind::Press,
131 | modifier: KeyModifiers::NONE,
132 | key_code: KeyCode::Home,
133 | },
134 | LineEditorEvent::Movement(vec![MovementCommand::MoveToStart]),
135 | );
136 |
137 | self.register_binding(
138 | KeyCombination {
139 | key_kind: KeyEventKind::Press,
140 | modifier: KeyModifiers::NONE,
141 | key_code: KeyCode::End,
142 | },
143 | LineEditorEvent::Movement(vec![MovementCommand::MoveToEnd]),
144 | );
145 |
146 | self.register_binding(
147 | KeyCombination {
148 | key_kind: KeyEventKind::Press,
149 | modifier: KeyModifiers::CONTROL,
150 | key_code: KeyCode::Left,
151 | },
152 | LineEditorEvent::Movement(vec![MovementCommand::MoveLeftWord]),
153 | );
154 |
155 | self.register_binding(
156 | KeyCombination {
157 | key_kind: KeyEventKind::Press,
158 | modifier: KeyModifiers::CONTROL,
159 | key_code: KeyCode::Right,
160 | },
161 | LineEditorEvent::Movement(vec![MovementCommand::MoveRightWord]),
162 | );
163 | }
164 |
165 | /// Register basic functionality to edit
166 | ///
167 | /// `Delete`, `Backspace` and the basic variants do delete words
168 | pub fn register_common_edit_bindings(&mut self) {
169 | self.register_binding(
170 | KeyCombination {
171 | key_kind: KeyEventKind::Press,
172 | modifier: KeyModifiers::NONE,
173 | key_code: KeyCode::Backspace,
174 | },
175 | LineEditorEvent::Backspace,
176 | );
177 |
178 | self.register_binding(
179 | KeyCombination {
180 | key_kind: KeyEventKind::Press,
181 | modifier: KeyModifiers::NONE,
182 | key_code: KeyCode::Delete,
183 | },
184 | LineEditorEvent::Delete,
185 | );
186 | }
187 |
188 | /// Register basic functionality to selection
189 | ///
190 | /// Select right and left and select all
191 | pub fn register_common_selection_bindings(&mut self) {
192 | self.register_binding(
193 | KeyCombination {
194 | key_kind: KeyEventKind::Press,
195 | modifier: KeyModifiers::SHIFT,
196 | key_code: KeyCode::Left,
197 | },
198 | LineEditorEvent::SelectLeft,
199 | );
200 |
201 | self.register_binding(
202 | KeyCombination {
203 | key_kind: KeyEventKind::Press,
204 | modifier: KeyModifiers::SHIFT,
205 | key_code: KeyCode::Right,
206 | },
207 | LineEditorEvent::SelectRight,
208 | );
209 |
210 | self.register_binding(
211 | KeyCombination {
212 | key_kind: KeyEventKind::Press,
213 | modifier: KeyModifiers::CONTROL,
214 | key_code: KeyCode::Char('a'),
215 | },
216 | LineEditorEvent::SelectAll,
217 | );
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/src/core/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod editor;
2 | pub mod event;
3 | pub mod input_filter;
4 | pub mod keybindings;
5 | pub mod style;
6 | pub mod styled_buffer;
7 |
--------------------------------------------------------------------------------
/src/core/style.rs:
--------------------------------------------------------------------------------
1 | use crossterm::style::Attribute;
2 | use crossterm::style::Color;
3 |
4 | /// Represent the foreground, background colors and attributes
5 | #[derive(Clone)]
6 | pub struct Style {
7 | /// Optional foreground color
8 | foreground: Option,
9 | /// Optional background color
10 | background: Option,
11 | /// Set of attributes like Bold, Italic, Undercurled...etc.
12 | attributes: Vec,
13 | }
14 |
15 | /// Create default instance of Style
16 | impl Default for Style {
17 | fn default() -> Self {
18 | Style {
19 | foreground: None,
20 | background: None,
21 | attributes: vec![],
22 | }
23 | }
24 | }
25 |
26 | impl Style {
27 | /// Get the style foreground color
28 | pub fn set_foreground_color(&mut self, color: Color) {
29 | self.foreground = Some(color);
30 | }
31 |
32 | /// Get the style foreground color
33 | pub fn foreground_color(&self) -> &Option {
34 | &self.foreground
35 | }
36 |
37 | /// Get the style background color
38 | pub fn set_background_color(&mut self, color: Color) {
39 | self.background = Some(color);
40 | }
41 |
42 | /// Get the style background color
43 | pub fn background_color(&self) -> &Option {
44 | &self.background
45 | }
46 |
47 | /// Get the style attributes
48 | pub fn attributes(&self) -> &Vec {
49 | &self.attributes
50 | }
51 |
52 | /// Add attribute to this style
53 | pub fn add_attribute(&mut self, attribute: Attribute) {
54 | self.attributes.push(attribute);
55 | }
56 |
57 | /// Remove all attributes for this style
58 | pub fn clear_attributes(&mut self) {
59 | self.attributes.clear();
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/core/styled_buffer.rs:
--------------------------------------------------------------------------------
1 | use super::style::Style;
2 |
3 | /// Memory representation of the lines and styles
4 | pub struct StyledBuffer {
5 | /// The buffer as list of characters
6 | buffer: Vec,
7 | /// The styles for each character on the buffer
8 | styles: Vec