gitui/src/components/externaleditor.rs
Antonio Yang a95ffd7bcc
Customize key binds (#234)
* customizable key config
* provide example vim key config
* automatically show correct key binding in bottom cmd-bar
2020-08-26 18:23:53 +02:00

160 lines
3.9 KiB
Rust

use crate::{
components::{
visibility_blocking, CommandBlocking, CommandInfo, Component,
DrawableComponent,
},
keys::SharedKeyConfig,
strings,
ui::{self, style::SharedTheme},
};
use anyhow::{anyhow, Result};
use asyncgit::{sync::utils::repo_work_dir, CWD};
use crossterm::{
event::Event,
terminal::{EnterAlternateScreen, LeaveAlternateScreen},
ExecutableCommand,
};
use scopeguard::defer;
use std::ffi::OsStr;
use std::{env, io, path::Path, process::Command};
use tui::{
backend::Backend,
layout::Rect,
widgets::{Block, BorderType, Borders, Clear, Paragraph, Text},
Frame,
};
///
pub struct ExternalEditorComponent {
visible: bool,
theme: SharedTheme,
key_config: SharedKeyConfig,
}
impl ExternalEditorComponent {
///
pub fn new(
theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self {
Self {
visible: false,
theme,
key_config,
}
}
/// opens file at given `path` in an available editor
pub fn open_file_in_editor(path: &Path) -> Result<()> {
let work_dir = repo_work_dir(CWD)?;
let path = if path.is_relative() {
Path::new(&work_dir).join(path)
} else {
path.into()
};
if !path.exists() {
return Err(anyhow!("file not found: {:?}", path));
}
io::stdout().execute(LeaveAlternateScreen)?;
defer! {
io::stdout().execute(EnterAlternateScreen).expect("reset terminal");
}
let editor = env::var("GIT_EDITOR")
.ok()
.or_else(|| env::var("VISUAL").ok())
.or_else(|| env::var("EDITOR").ok())
.unwrap_or_else(|| String::from("vi"));
// TODO: proper handling arguments containing whitespaces
// This does not do the right thing if the input is `editor --something "with spaces"`
let mut editor = editor.split_whitespace();
let command = editor.next().ok_or_else(|| {
anyhow!("unable to read editor command")
})?;
let mut editor: Vec<&OsStr> =
editor.map(|s| OsStr::new(s)).collect();
editor.push(path.as_os_str());
Command::new(command)
.current_dir(work_dir)
.args(editor)
.status()
.map_err(|e| anyhow!("\"{}\": {}", command, e))?;
Ok(())
}
}
impl DrawableComponent for ExternalEditorComponent {
fn draw<B: Backend>(
&self,
f: &mut Frame<B>,
_rect: Rect,
) -> Result<()> {
if self.visible {
let txt = vec![Text::Raw(
strings::msg_opening_editor(&self.key_config).into(),
)];
let area = ui::centered_rect_absolute(25, 3, f.size());
f.render_widget(Clear, area);
f.render_widget(
Paragraph::new(txt.iter())
.block(
Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Thick)
.title_style(self.theme.title(true))
.border_style(self.theme.block(true)),
)
.style(self.theme.text_danger()),
area,
);
}
Ok(())
}
}
impl Component for ExternalEditorComponent {
fn commands(
&self,
out: &mut Vec<CommandInfo>,
_force_all: bool,
) -> CommandBlocking {
if self.visible {
out.clear();
}
visibility_blocking(self)
}
fn event(&mut self, _ev: Event) -> Result<bool> {
if self.visible {
return Ok(true);
}
Ok(false)
}
fn is_visible(&self) -> bool {
self.visible
}
fn hide(&mut self) {
self.visible = false
}
fn show(&mut self) -> Result<()> {
self.visible = true;
Ok(())
}
}