Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sublime-like changes #109

Open
wants to merge 37 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d202ae3
Update Cargo.lock
msirringhaus Jun 24, 2019
83692fd
First step towards configurable keybindings: TUI can use bindings pro…
msirringhaus Jun 24, 2019
4f9d809
Copy keybindings to editor and all views. Ugly, but I cannot find a n…
msirringhaus Jun 24, 2019
2d2763e
Reshaped Command to have relative and absolute move as variant instea…
msirringhaus Jun 24, 2019
a4e9f93
Editor now holds the keymap alone and translates it for the view. All…
msirringhaus Jun 25, 2019
386f22b
Rename functions to avoid key-name <-> functionality confusion
msirringhaus Jun 25, 2019
dfd0781
Merge branch 'keybindings'
msirringhaus Jun 25, 2019
0699d1c
Prepare to switch to rust edition 2018
msirringhaus Jun 25, 2019
0bc9269
Rust 2018: run cargo fix --edition-idioms
msirringhaus Jun 25, 2019
117299f
Remove duplicate functions in editor, view and client. Now each objec…
msirringhaus Jun 26, 2019
702b140
Make Insert a command as well and let the client handle it
msirringhaus Jun 26, 2019
39995fd
Add command to hide prompt
msirringhaus Jun 26, 2019
1c11099
Add undo/redo (unfortunately, there is a bug in termion that does not…
msirringhaus Jun 26, 2019
db5488b
Add multi-cursor support! Find current selection and add cursors on e…
msirringhaus Jun 26, 2019
1424a88
Implement copy&paste
msirringhaus Jun 27, 2019
2b38760
Implement (almost) all move commands
msirringhaus Jun 27, 2019
decb28f
Embedd sublime keybindings for now. This is stupid but easier for now…
msirringhaus Jun 27, 2019
c276d9b
Restructure Command a bit so that there is a single parsing function …
msirringhaus Jun 27, 2019
b6472f1
Make open-command work with tilde and env-variables.
msirringhaus Jun 27, 2019
fbd71c2
Implement close current view (last view always remains).
msirringhaus Jun 27, 2019
574c9a9
Implement select_all command
msirringhaus Jun 28, 2019
15ce710
Unify and simplify prompt parsing a bit
msirringhaus Jun 28, 2019
495083c
Add (terminal-specific) keys for next/prev buffer.
msirringhaus Jun 28, 2019
e31365d
First somewhat functioning implementation of Find()
msirringhaus Jun 28, 2019
62a601f
Implement configurable search
msirringhaus Jun 28, 2019
6c32c20
Add explanation of how search works above the search prompt.
msirringhaus Jun 28, 2019
0e77c3d
Activate move_to linenumber as prompt command
msirringhaus Jun 28, 2019
ab35a26
Implement function to set multiple cursor with clicking middle mouse …
msirringhaus Jun 28, 2019
f19eb17
First, hacky and incomplete draft of commandprompt with suggestions
msirringhaus Jun 28, 2019
8bfcfa1
Generate nicer names for prompt suggestions. This is really ugly and …
msirringhaus Jun 28, 2019
694b3b1
Move clipboard to Editor. Had a clipboard for each view, which meant …
msirringhaus Jun 29, 2019
88c8b35
Major rework of command structure. Known bug: Keybindings of subcomma…
msirringhaus Jun 30, 2019
3790457
Remove old, left-over comment
msirringhaus Jul 2, 2019
b1f795c
Adjust logging levels to be more sensible.
msirringhaus Jul 2, 2019
e94e3e9
Make enum-names rusty and use serdes rename_all to use it for parsing.
msirringhaus Jul 3, 2019
7e1aea1
Prompt: Fix prompt parsing and close prompt after finalizing.
msirringhaus Jul 3, 2019
75dff11
Fix prompt parsing of select_lines
msirringhaus Jul 3, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*.err
*.orig
*.log
*.log.rpc
*.rej
*.swo
*.swp
Expand Down
183 changes: 165 additions & 18 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
authors = ["Corentin Henry <[email protected]>"]
name = "xi-term"
version = "0.1.0"
edition = "2018"
msirringhaus marked this conversation as resolved.
Show resolved Hide resolved

[dependencies]
clap = "2.33.0"
Expand All @@ -14,3 +15,6 @@ tokio = "0.1.21"
xdg = "2.2.0"
indexmap = "1.0.2"
xrl = "0.0.7"
serde = { version = "1.0.93", features = ["derive"] }
serde_json = "1.0.39"
json5 = "0.2.4"
740 changes: 740 additions & 0 deletions configs/Default (Linux).sublime-keymap

Large diffs are not rendered by default.

159 changes: 134 additions & 25 deletions src/core/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,62 @@
use xrl::ViewId;

use std::str::FromStr;
use serde::{Deserialize, Serialize};

#[derive(Debug)]
#[allow(non_camel_case_types)]
msirringhaus marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
pub enum RelativeMoveDistance {
/// Move only one character
characters,
/// Move a line
lines,
/// Move to new word
words,
/// Move to end of word
word_ends,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these all actually implemented in xi? I'd rather have only what's currently implemented, with maybe a comment explaining how this may be extended in the future.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

words yes, word_ends no. I have it in there so that serde can parse the given keymap without choking

/// Move to new subword
subwords,
/// Move to end of subword
subword_ends,
/// Move a page
pages,
}

#[allow(non_camel_case_types)]
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
pub struct RelativeMove {
pub by: RelativeMoveDistance,
pub forward: bool,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how I feel about forward. On one hand, I think enums are clearer: Direction::{Left,Up,Right,Down} is clearer than have to think: "horizontal move + not forward => left", "vertical move + forward => down", etc. On the other, having an enum with all 4 directions here would be partially redundant with the by attribute, because when moving by lines, we already know the direction is either up or down for instance.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This again comes from sublimes keymap. Thats how they specify it and I just went along. I agree that an enum would be nicer here (but I would actually just use Direction::{Forward, Backward}, so not to make by redundant). But I didn't have the time to hack that into the serde parsing.

#[serde(default)]
pub extend: bool
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does extend do?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, not a perfect name (taken from sublime). If true, it states that this movement should extend the current selection (e.g. move one word right with extend selects that word).
Works for all kinds of movements (page up down, go to beginning / end of file / line, and so on).

}

#[allow(non_camel_case_types)]
msirringhaus marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
pub enum AbsoluteMovePoint {
/// Beginning of file
bof,
/// End of file
eof,
/// Beginning of line
bol,
/// End of line
eol,
/// Enclosing brackets
brackets,
/// Line number
line(u64)
}

#[allow(non_camel_case_types)]
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
pub struct AbsoluteMove {
pub to: AbsoluteMovePoint,
#[serde(default)]
pub extend: bool
}

#[derive(Debug, PartialEq, Clone)]
pub enum Command {
/// Close the CommandPrompt.
Cancel,
Expand All @@ -23,22 +77,16 @@ pub enum Command {
NextBuffer,
/// Cycle to the previous buffer.
PrevBuffer,
/// Move cursor left.
MoveLeft,
/// Move cursor right.
MoveRight,
/// Move cursor up.
MoveUp,
/// Move cursor down.
MoveDown,
/// Page down
PageDown,
/// Page up
PageUp,
/// Change the syntax theme.
// Relative move like line up/down, page up/down, left, right, word left, ..
RelativeMove(RelativeMove),
// Relative move like line ending/beginning, file ending/beginning, line-number, ...
AbsoluteMove(AbsoluteMove),

SetTheme(String),
/// Toggle displaying line numbers.
ToggleLineNumbers,
/// Open prompt for user-input
OpenPrompt,
}

#[derive(Debug)]
Expand Down Expand Up @@ -67,18 +115,79 @@ impl FromStr for Command {
fn from_str(s: &str) -> Result<Command, Self::Err> {
match &s[..] {
"s" | "save" => Ok(Command::Save(None)),
"q" | "quit" => Ok(Command::Quit),
"b" | "back" => Ok(Command::Back),
"d" | "delete" => Ok(Command::Delete),
"bn" | "next-buffer" => Ok(Command::NextBuffer),
"bp" | "prev-buffer" => Ok(Command::PrevBuffer),
"pd" | "page-down" => Ok(Command::PageDown),
"pu" | "page-up" => Ok(Command::PageUp),
"ml" | "move-left" => Ok(Command::MoveLeft),
"mr" | "move-right" => Ok(Command::MoveRight),
"mu" | "move-up" => Ok(Command::MoveUp),
"md" | "move-down" => Ok(Command::MoveDown),
"q" | "quit" | "exit" => Ok(Command::Quit),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should refrain from adding more command aliases. We could discuss modifying the existing ones, but I think it will confusing to have too many commands that do the same thing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I never planned to do a full PR.
I switched to command names used by sublime texts keymap-file, but didn't want to remove the old names just yet.

"b" | "back" | "left_delete" => Ok(Command::Back),
"d" | "delete" | "right_delete" => Ok(Command::Delete),
"bn" | "next-buffer" | "next_view" => Ok(Command::NextBuffer),
"bp" | "prev-buffer" | "prev_view" => Ok(Command::PrevBuffer),
"md" | "move-down" => Ok(Command::RelativeMove(
RelativeMove{
by: RelativeMoveDistance::lines,
forward: true,
extend: false
}
)),
"mu" | "move-up" => Ok(Command::RelativeMove(
RelativeMove{
by: RelativeMoveDistance::lines,
forward: false,
extend: false
}
)),
"mr" | "move-right" => Ok(Command::RelativeMove(
RelativeMove{
by: RelativeMoveDistance::characters,
forward: true,
extend: false
}
)),
"ml" | "move-left" => Ok(Command::RelativeMove(
RelativeMove{
by: RelativeMoveDistance::characters,
forward: false,
extend: false
}
)),
"pd" | "page-down" => Ok(Command::RelativeMove(
RelativeMove{
by: RelativeMoveDistance::pages,
forward: true,
extend: false
}
)),
"pu" | "page-up" => Ok(Command::RelativeMove(
RelativeMove{
by: RelativeMoveDistance::pages,
forward: false,
extend: false
}
)),
"bof" | "beginning-of-file" => Ok(Command::AbsoluteMove(
AbsoluteMove{
to: AbsoluteMovePoint::bof,
extend: false
}
)),
"eof" | "end-of-file" => Ok(Command::AbsoluteMove(
AbsoluteMove{
to: AbsoluteMovePoint::eof,
extend: false
}
)),
"bol" | "beginning-of-line" => Ok(Command::AbsoluteMove(
AbsoluteMove{
to: AbsoluteMovePoint::bol,
extend: false
}
)),
"eol" | "end-of-line" => Ok(Command::AbsoluteMove(
AbsoluteMove{
to: AbsoluteMovePoint::eol,
extend: false
}
)),
"ln" | "line-numbers" => Ok(Command::ToggleLineNumbers),
"op" | "open-prompt" | "show_overlay" => Ok(Command::OpenPrompt),
command => {
let mut parts: Vec<&str> = command.split(' ').collect();

Expand Down
136 changes: 136 additions & 0 deletions src/core/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use crate::core::{Command, RelativeMove, AbsoluteMove};
use termion::event::{Event, Key};

use serde::{Deserialize, Serialize};
use serde_json::Value;

use std::path::{Path, PathBuf};
use std::fs;
use std::collections::HashMap;

use std::str::FromStr;

pub type Keymap = HashMap<Event, Command>;

#[derive(Debug, Serialize, Deserialize)]
struct Keybinding {
keys: Vec<String>,
command: String,
// For now, unstructured value
args: Option<Value>,
context: Option<Value>,
}

#[derive(Clone)]
pub struct KeybindingConfig {
pub keymap: Keymap,
pub config_path: PathBuf
}

impl KeybindingConfig {
pub fn parse(config_path: &Path) -> Result<KeybindingConfig, Box<dyn std::error::Error + Sync + Send + 'static>> {
let entries = fs::read_to_string(config_path)?;
// Read the JSON contents of the file as an instance of `User`.
let bindings: Vec<Keybinding> = json5::from_str(&entries)?;
error!("Bindings parsed!");

let mut keymap = Keymap::new();
let mut found_cmds = Vec::new();
for binding in bindings {
let cmd = match binding.command.as_ref() {
"move" => {
let args = binding.args.ok_or("move binding incomplete! Missing \"args\"")?;
let cmd : RelativeMove = serde_json::from_value(args)?;
Command::RelativeMove(cmd)
},
"move_to" => {
let args = binding.args.ok_or("move_to binding incomplete! Missing \"args\"")?;
let cmd : AbsoluteMove = serde_json::from_value(args)?;
Command::AbsoluteMove(cmd)
},
x => match Command::from_str(x) {
Ok(cmd) => cmd,
// unimplemented command for now
Err(_) => continue,
}
};
if found_cmds.contains(&cmd) {
continue;
}
if let Some(binding) = KeybindingConfig::parse_keys(&binding.keys) {
error!("{:?} = {:?}", cmd, binding);
keymap.insert(binding, cmd.clone());
found_cmds.push(cmd);
} else {
warn!("Skipping failed binding");
continue;
}
}

Ok(KeybindingConfig{keymap: keymap, config_path: config_path.to_owned()})
}

fn parse_keys(keys: &Vec<String>) -> Option<Event> {
if keys.len() != 1 {
return None;
}

let key = &keys[0];
match key.as_ref() {
"enter" => Some(Event::Key(Key::Char('\n'))),
"tab" => Some(Event::Key(Key::Char('\t'))),
"backspace" => Some(Event::Key(Key::Backspace)),
"left" => Some(Event::Key(Key::Left)),
"right" => Some(Event::Key(Key::Right)),
"up" => Some(Event::Key(Key::Up)),
"down" => Some(Event::Key(Key::Down)),
"home" => Some(Event::Key(Key::Home)),
"end" => Some(Event::Key(Key::End)),
"pageup" => Some(Event::Key(Key::PageUp)),
"pagedown" => Some(Event::Key(Key::PageDown)),
"delete" => Some(Event::Key(Key::Delete)),
"insert" => Some(Event::Key(Key::Insert)),
"escape" => Some(Event::Key(Key::Esc)),

x if x.starts_with("f") => {
match x[1..].parse::<u8>() {
Ok(val) => Some(Event::Key(Key::F(val))),
Err(_) => {
warn!("Cannot parse {}", x);
None
}
}
}

x if x.starts_with("ctrl+") || x.starts_with("alt+") => {
let is_alt = x.starts_with("alt+");
let start_length = if is_alt {4} else {5};

let character;
// start_length + "shift+x".len() || start_length + "x".len()
if x.len() != start_length + 7 && x.len() != start_length + 1 {
warn!("Cannot parse {}. Length is = {}, which is neither {} nor {} ", x, x.len(), start_length + 1, start_length + 7);
return None
} else {
if x.len() == start_length + 7 {
// With "+shift+", so we use an upper case letter
character = x.chars().last().unwrap().to_ascii_uppercase();
} else {
character = x.chars().last().unwrap().to_ascii_lowercase();
}
}

if is_alt {
Some(Event::Key(Key::Alt(character)))
} else {
Some(Event::Key(Key::Ctrl(character)))
}
}

x => {
warn!("Completely unknown argument {}", x);
None
},
}
}
}
5 changes: 4 additions & 1 deletion src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ mod tui;
pub use self::tui::{CoreEvent, Tui, TuiService, TuiServiceBuilder};

mod cmd;
pub use self::cmd::{Command, ParseCommandError};
pub use self::cmd::{Command, ParseCommandError, RelativeMove, AbsoluteMove, RelativeMoveDistance, AbsoluteMovePoint};

mod config;
pub use self::config::{KeybindingConfig, Keymap};
Loading