-
Notifications
You must be signed in to change notification settings - Fork 36
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
base: master
Are you sure you want to change the base?
Sublime-like changes #109
Changes from 2 commits
d202ae3
83692fd
4f9d809
2d2763e
a4e9f93
386f22b
dfd0781
0699d1c
0bc9269
117299f
702b140
39995fd
1c11099
db5488b
1424a88
2b38760
decb28f
c276d9b
b6472f1
fbd71c2
574c9a9
15ce710
495083c
e31365d
62a601f
6c32c20
0e77c3d
ab35a26
f19eb17
8bfcfa1
694b3b1
88c8b35
3790457
b1f795c
e94e3e9
7e1aea1
75dff11
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
use crate::core::Command; | ||
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; | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
struct Keybinding { | ||
keys: Vec<String>, | ||
command: String, | ||
// For now, unstructured value | ||
args: Option<Value>, | ||
context: Option<Value>, | ||
} | ||
|
||
pub struct KeybindingConfig { | ||
pub keymap: HashMap<Event, Command>, | ||
pub config_path: PathBuf | ||
} | ||
|
||
impl KeybindingConfig { | ||
pub fn parse(config_path: &Path) -> Result<KeybindingConfig, Box<std::error::Error + Sync + Send + 'static>> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably have an enum ConfigError {
Parsing(...), // contains the inner error
Invalid(....),
.... // potentially a bunch of other variants.
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, error handling was pretty low on my list, to be honest. I basically just reused existing error types. This definetly needs a major cleanup (I actually think of removing "failure"-crate in the process, as its not really maintained anymore AFAIK) |
||
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!"); | ||
msirringhaus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
let mut keymap = HashMap::new(); | ||
let mut found_cmds = Vec::new(); | ||
for binding in bindings { | ||
let cmd = match Command::from_str(&binding.command) { | ||
Ok(cmd) => cmd, | ||
// unimplemented command for now | ||
Err(_) => continue, | ||
}; | ||
error!("{:?} = {:?}", cmd, binding.keys); | ||
if found_cmds.contains(&cmd) { | ||
continue; | ||
} | ||
let binding = KeybindingConfig::parse_keys(&binding.keys).ok_or("Could not parse keybindings - config")?; | ||
keymap.insert(binding, cmd.clone()); | ||
found_cmds.push(cmd); | ||
} | ||
|
||
Ok(KeybindingConfig{keymap: keymap, config_path: config_path.to_owned()}) | ||
} | ||
|
||
fn parse_keys(keys: &Vec<String>) -> Option<Event> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In principle, yes. It means the key-text is unparsable. So there would only be one Err-condition. Option seemed easier/faster, just because of this simple "either we can parse it or we don't" distinction. |
||
if keys.len() != 1 { | ||
return None; | ||
} | ||
|
||
let key = &keys[0]; | ||
match key.as_ref() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, probably. I probably edited something back and forth and ended up with that, not noticing. |
||
"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 | ||
}, | ||
} | ||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,12 +4,12 @@ use futures::sync::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; | |
use futures::sync::oneshot::{self, Receiver, Sender}; | ||
use futures::{Async, Future, Poll, Sink, Stream}; | ||
|
||
use termion::event::{Event, Key}; | ||
use termion::event::{Event}; | ||
use xrl::{Client, Frontend, FrontendBuilder, MeasureWidth, XiNotification}; | ||
|
||
use failure::Error; | ||
|
||
use core::{Command, Terminal, TerminalEvent}; | ||
use core::{Command, Terminal, TerminalEvent, KeybindingConfig}; | ||
use widgets::{CommandPrompt, Editor}; | ||
|
||
pub struct Tui { | ||
|
@@ -32,18 +32,21 @@ pub struct Tui { | |
|
||
/// Stream of messages from Xi core. | ||
core_events: UnboundedReceiver<CoreEvent>, | ||
|
||
keybindings: KeybindingConfig, | ||
} | ||
|
||
impl Tui { | ||
/// Create a new Tui instance. | ||
pub fn new(client: Client, events: UnboundedReceiver<CoreEvent>) -> Result<Self, Error> { | ||
pub fn new(client: Client, events: UnboundedReceiver<CoreEvent>, keybindings: KeybindingConfig) -> Result<Self, Error> { | ||
Ok(Tui { | ||
terminal: Terminal::new()?, | ||
exit: false, | ||
term_size: (0, 0), | ||
editor: Editor::new(client), | ||
prompt: None, | ||
core_events: events, | ||
keybindings: keybindings, | ||
}) | ||
} | ||
|
||
|
@@ -54,9 +57,8 @@ impl Tui { | |
|
||
pub fn run_command(&mut self, cmd: Command) { | ||
match cmd { | ||
Command::Cancel => { | ||
self.prompt = None; | ||
} | ||
Command::OpenPrompt => self.open_prompt(), | ||
Command::Cancel => self.prompt = None, | ||
Command::Quit => self.exit = true, | ||
Command::Save(view) => self.editor.save(view), | ||
Command::Back => self.editor.back(), | ||
|
@@ -75,40 +77,43 @@ impl Tui { | |
} | ||
} | ||
|
||
fn open_prompt(&mut self) { | ||
if self.prompt.is_none() { | ||
self.prompt = Some(CommandPrompt::default()); | ||
} | ||
} | ||
|
||
/// Global keybindings can be parsed here | ||
fn handle_input(&mut self, event: Event) { | ||
debug!("handling input {:?}", event); | ||
match event { | ||
Event::Key(Key::Ctrl('c')) => self.exit = true, | ||
Event::Key(Key::Alt('x')) => { | ||
if let Some(ref mut prompt) = self.prompt { | ||
match prompt.handle_input(&event) { | ||
Ok(None) => {} | ||
Ok(Some(_)) => unreachable!(), | ||
Err(_) => unreachable!(), | ||
} | ||
} else { | ||
self.prompt = Some(CommandPrompt::default()); | ||
} | ||
|
||
if let Some(cmd) = self.keybindings.keymap.get(&event) { | ||
match cmd { | ||
Command::OpenPrompt => { | ||
if self.prompt.is_none() { | ||
self.prompt = Some(CommandPrompt::default()); | ||
} | ||
return; }, | ||
Command::Quit => { self.exit = true; return; }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Formatting is a bit off here, although it does not matter too much since we can just rustfmt later. |
||
_ => {/* Somebody else has to deal with these commands */}, | ||
} | ||
event => { | ||
// No command prompt is active, process the event normally. | ||
if self.prompt.is_none() { | ||
self.editor.handle_input(event); | ||
return; | ||
} | ||
} | ||
|
||
// A command prompt is active. | ||
let mut prompt = self.prompt.take().unwrap(); | ||
match prompt.handle_input(&event) { | ||
Ok(None) => { | ||
self.prompt = Some(prompt); | ||
} | ||
Ok(Some(cmd)) => self.run_command(cmd), | ||
Err(err) => { | ||
error!("Failed to parse command: {:?}", err); | ||
} | ||
} | ||
// No command prompt is active, process the event normally. | ||
if self.prompt.is_none() { | ||
self.editor.handle_input(event); | ||
return; | ||
} | ||
|
||
// A command prompt is active. | ||
let mut prompt = self.prompt.take().unwrap(); | ||
msirringhaus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
match prompt.handle_input(&event) { | ||
Ok(None) => { | ||
self.prompt = Some(prompt); | ||
} | ||
Ok(Some(cmd)) => self.run_command(cmd), | ||
Err(err) => { | ||
error!("Failed to parse command: {:?}", err); | ||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.