terminal keyboard

This commit is contained in:
Dongdong Zhou 2021-10-14 13:05:47 +01:00
parent 1ef920b19f
commit 1bdc28ca5f
11 changed files with 284 additions and 73 deletions

View File

@ -21,7 +21,7 @@
data::EditorKind,
editor::{EditorLocation, EditorLocationNew, HighlightTextLayout},
palette::{NewPaletteItem, PaletteType},
proxy::TerminalContent,
proxy::{CursorShape, TerminalContent},
split::SplitMoveDirection,
state::LapceWorkspace,
};
@ -376,7 +376,12 @@ pub enum LapceUICommand {
UpdateLineChanges(BufferId),
PublishDiagnostics(PublishDiagnosticsParams),
UpdateDiffFiles(Vec<PathBuf>),
TerminalUpdateContent(TermId, TerminalContent),
TerminalUpdateContent(
TermId,
TerminalContent,
alacritty_terminal::index::Point,
CursorShape,
),
ReloadBuffer(BufferId, u64, String),
EnsureVisible((Rect, (f64, f64), Option<EnsureVisiblePosition>)),
EnsureRectVisible(Rect),

View File

@ -4,7 +4,7 @@
use directories::ProjectDirs;
use druid::{
piet::{PietText, Text, TextLayout, TextLayoutBuilder},
theme, Color, Env, FontDescriptor, FontFamily, Key,
theme, Color, Env, FontDescriptor, FontFamily, Key, Size,
};
use serde::{Deserialize, Deserializer, Serialize};
@ -37,6 +37,10 @@ impl LapceTheme {
pub const EDITOR_SELECTION: &'static str = "editor.selection";
pub const EDITOR_CURRENT_LINE: &'static str = "editor.current_line";
pub const TERMINAL_CURSOR: &'static str = "terminal.cursor";
pub const TERMINAL_BACKGROUND: &'static str = "terminal.background";
pub const TERMINAL_FOREGROUND: &'static str = "terminal.foreground";
pub const PALETTE_BACKGROUND: &'static str = "palette.background";
pub const PALETTE_CURRENT: &'static str = "palette.current";
@ -183,6 +187,15 @@ pub fn editor_text_width(&self, text: &mut PietText, c: &str) -> f64 {
text_layout.size().width
}
pub fn editor_text_size(&self, text: &mut PietText, c: &str) -> Size {
let text_layout = text
.new_text_layout(c.to_string())
.font(self.editor.font_family(), self.editor.font_size as f64)
.build()
.unwrap();
text_layout.size()
}
pub fn reload_env(&self, env: &mut Env) {
env.set(theme::SCROLLBAR_RADIUS, 0.0);
env.set(theme::SCROLLBAR_EDGE_WIDTH, 0.0);

View File

@ -53,6 +53,9 @@ pub struct KeyMap {
pub trait KeyPressFocus {
fn get_mode(&self) -> Mode;
fn check_condition(&self, condition: &str) -> bool;
fn is_terminal(&self) -> bool {
false
}
fn run_command(
&mut self,
ctx: &mut EventCtx,
@ -236,7 +239,8 @@ fn match_keymap<T: KeyPressFocus>(
.iter()
.filter(|keymap| {
if keymap.modes.len() > 0
&& !keymap.modes.contains(&check.get_mode())
&& (!keymap.modes.contains(&check.get_mode())
|| check.is_terminal())
{
return false;
}

View File

@ -354,6 +354,24 @@ pub fn stop(&self) {
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CursorShape {
/// Cursor is a block like `▒`.
Block,
/// Cursor is an underscore like `_`.
Underline,
/// Cursor is a vertical bar `⎸`.
Beam,
/// Cursor is a box like `☐`.
HollowBlock,
/// Invisible cursor.
Hidden,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "method", content = "params")]
@ -383,6 +401,8 @@ pub enum Notification {
TerminalUpdateContent {
id: TermId,
content: TerminalContent,
cursor_shape: CursorShape,
cursor_point: alacritty_terminal::index::Point,
},
DiffFiles {
files: Vec<PathBuf>,
@ -426,7 +446,7 @@ fn handle_notification(
Notification::ListDir { mut items } => {}
Notification::DiffFiles { files } => {}
Notification::PublishDiagnostics { diagnostics } => {}
Notification::TerminalUpdateContent { id, content } => {}
Notification::TerminalUpdateContent { id, content, .. } => {}
}
}
@ -561,10 +581,20 @@ fn handle_notification(
Target::Widget(self.tab_id),
);
}
Notification::TerminalUpdateContent { id, content } => {
Notification::TerminalUpdateContent {
id,
content,
cursor_shape,
cursor_point,
} => {
self.event_sink.submit_command(
LAPCE_UI_COMMAND,
LapceUICommand::TerminalUpdateContent(id, content),
LapceUICommand::TerminalUpdateContent(
id,
content,
cursor_point,
cursor_shape,
),
Target::Widget(self.tab_id),
);
}

View File

@ -226,12 +226,20 @@ fn event(
Arc::make_mut(buffer).load_content(content);
ctx.set_handled();
}
LapceUICommand::TerminalUpdateContent(id, content) => {
LapceUICommand::TerminalUpdateContent(
id,
content,
cursor_point,
cursor_shape,
) => {
let terminal = Arc::make_mut(&mut data.terminal)
.terminals
.get_mut(id)
.unwrap();
Arc::make_mut(terminal).content = content.to_owned();
let terminal = Arc::make_mut(terminal);
terminal.content = content.to_owned();
terminal.cursor_point = cursor_point.to_owned();
terminal.cursor_shape = cursor_shape.to_owned();
ctx.set_handled();
}
LapceUICommand::UpdateDiffFiles(files) => {

View File

@ -1,22 +1,30 @@
use std::sync::Arc;
use alacritty_terminal::ansi;
use druid::{
piet::{Text, TextLayoutBuilder},
BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx,
PaintCtx, Point, RenderContext, Size, UpdateCtx, Widget, WidgetExt, WidgetId,
WidgetPod,
BoxConstraints, Color, Data, Env, Event, EventCtx, KbKey, LayoutCtx, LifeCycle,
LifeCycleCtx, Modifiers, PaintCtx, Point, RenderContext, Size, UpdateCtx,
Widget, WidgetExt, WidgetId, WidgetPod,
};
use lapce_proxy::terminal::TermId;
use crate::{
command::LapceCommand,
config::LapceTheme,
data::LapceTabData,
keypress::KeyPressFocus,
proxy::{LapceProxy, TerminalContent},
proxy::{CursorShape, LapceProxy, TerminalContent},
scroll::LapcePadding,
split::LapceSplitNew,
state::Mode,
};
const CTRL_CHARS: &'static [char] = &[
'@', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '[', '\\', ']', '^', '_',
];
#[derive(Clone)]
pub struct TerminalSplitData {
pub widget_id: WidgetId,
@ -46,18 +54,25 @@ pub struct LapceTerminalViewData {
}
impl KeyPressFocus for LapceTerminalViewData {
fn is_terminal(&self) -> bool {
true
}
fn get_mode(&self) -> Mode {
Mode::Insert
}
fn check_condition(&self, condition: &str) -> bool {
false
match condition {
"terminal_focus" => true,
_ => false,
}
}
fn run_command(
&mut self,
ctx: &mut EventCtx,
command: &crate::command::LapceCommand,
command: &LapceCommand,
count: Option<usize>,
env: &Env,
) {
@ -72,6 +87,8 @@ fn insert(&mut self, ctx: &mut EventCtx, c: &str) {
pub struct LapceTerminalData {
id: TermId,
pub content: TerminalContent,
pub cursor_point: alacritty_terminal::index::Point,
pub cursor_shape: CursorShape,
}
impl LapceTerminalData {
@ -83,6 +100,8 @@ pub fn new(proxy: Arc<LapceProxy>) -> Self {
Self {
id,
content: TerminalContent::new(),
cursor_point: alacritty_terminal::index::Point::default(),
cursor_shape: CursorShape::Block,
}
}
}
@ -95,7 +114,7 @@ pub struct TerminalPanel {
impl TerminalPanel {
pub fn new(data: &LapceTabData) -> Self {
let (term_id, _) = data.terminal.terminals.iter().next().unwrap();
let terminal = LapceTerminal::new(*term_id);
let terminal = LapcePadding::new(10.0, LapceTerminal::new(*term_id));
let split = LapceSplitNew::new(data.terminal.split_id).with_flex_child(
terminal.boxed(),
None,
@ -211,12 +230,42 @@ fn event(
}
Event::KeyDown(key_event) => {
let mut keypress = data.keypress.clone();
Arc::make_mut(&mut keypress).key_down(
if !Arc::make_mut(&mut keypress).key_down(
ctx,
key_event,
&mut term_data,
env,
);
) {
let s = match &key_event.key {
KbKey::Character(c) => {
let mut s = "".to_string();
let mut mods = key_event.mods.clone();
if mods.ctrl() {
mods.set(Modifiers::CONTROL, false);
if mods.is_empty() && c.chars().count() == 1 {
let c = c.chars().next().unwrap();
if let Some(i) =
CTRL_CHARS.iter().position(|e| &c == e)
{
s = char::from_u32(i as u32)
.unwrap()
.to_string()
}
}
}
s
}
KbKey::Backspace => "\x08".to_string(),
KbKey::Tab => "\x09".to_string(),
KbKey::Enter => "\x0a".to_string(),
KbKey::Escape => "\x1b".to_string(),
_ => "".to_string(),
};
if s != "" {
data.proxy.terminal_insert(self.term_id, &s);
}
}
data.keypress = keypress.clone();
}
_ => (),
@ -235,6 +284,12 @@ fn lifecycle(
data: &LapceTabData,
env: &Env,
) {
match event {
LifeCycle::FocusChanged(_) => {
ctx.request_paint();
}
_ => (),
}
}
fn update(
@ -259,21 +314,64 @@ fn layout(
self.height = size.height;
let width = data.config.editor_text_width(ctx.text(), "W");
let line_height = data.config.editor.line_height as f64;
let width = (self.width / width).ceil() as usize;
let height = (self.height / line_height).ceil() as usize;
let width = (self.width / width).floor() as usize;
let height = (self.height / line_height).floor() as usize;
data.proxy.terminal_resize(self.term_id, width, height);
}
size
}
fn paint(&mut self, ctx: &mut PaintCtx, data: &LapceTabData, env: &Env) {
let width = data.config.editor_text_width(ctx.text(), "W");
let char_size = data.config.editor_text_size(ctx.text(), "W");
let char_width = char_size.width;
let line_height = data.config.editor.line_height as f64;
let y_shift = (line_height - char_size.height) / 2.0;
let terminal = data.terminal.terminals.get(&self.term_id).unwrap();
let rect =
Size::new(char_width, line_height)
.to_rect()
.with_origin(Point::new(
terminal.cursor_point.column.0 as f64 * char_width,
terminal.cursor_point.line.0 as f64 * line_height,
));
if ctx.is_focused() {
ctx.fill(
rect,
data.config.get_color_unchecked(LapceTheme::TERMINAL_CURSOR),
);
} else {
ctx.stroke(
rect,
data.config.get_color_unchecked(LapceTheme::TERMINAL_CURSOR),
1.0,
);
}
for (p, cell) in terminal.content.iter() {
let x = p.column.0 as f64 * width;
let y = p.line.0 as f64 * line_height;
let x = p.column.0 as f64 * char_width;
let y = p.line.0 as f64 * line_height + y_shift;
let fg = match cell.fg {
ansi::Color::Named(color) => {
let color = match color {
ansi::NamedColor::Cursor => LapceTheme::TERMINAL_CURSOR,
ansi::NamedColor::Foreground => {
LapceTheme::TERMINAL_FOREGROUND
}
ansi::NamedColor::Background => {
LapceTheme::TERMINAL_BACKGROUND
}
_ => LapceTheme::TERMINAL_FOREGROUND,
};
data.config.get_color_unchecked(color).clone()
}
ansi::Color::Spec(rgb) => Color::rgb8(rgb.r, rgb.g, rgb.b),
ansi::Color::Indexed(index) => data
.config
.get_color_unchecked(LapceTheme::TERMINAL_FOREGROUND)
.clone(),
};
let text_layout = ctx
.text()
.new_text_layout(cell.c.to_string())
@ -281,6 +379,7 @@ fn paint(&mut self, ctx: &mut PaintCtx, data: &LapceTabData, env: &Env) {
data.config.editor.font_family(),
data.config.editor.font_size as f64,
)
.text_color(fg)
.build()
.unwrap();
ctx.draw_text(&text_layout, Point::new(x, y));

View File

@ -17,6 +17,10 @@ cyan = "#56b6c2"
"lapce.dropdown_shadow" = "#000000"
"lapce.border" = "#000000"
"terminal.cursor" = "$white"
"terminal.foreground" = "$white"
"terminal.background" = "$black"
"editor.background" = "$black"
"editor.foreground" = "$white"
"editor.dim" = "#A0A1A7"

View File

@ -487,6 +487,21 @@ key = "meta+;"
command = "split_vertical"
mode = "n"
[[keymaps]]
key = "meta+;"
command = "split_vertical"
when = "terminal_focus"
[[keymaps]]
key = "meta+l"
command = "split_right"
when = "terminal_focus"
[[keymaps]]
key = "meta+h"
command = "split_left"
when = "terminal_focus"
[[keymaps]]
key = "meta+g"
command = "split_horizontal"

View File

@ -16,6 +16,10 @@ cyan = "#0184bc"
"lapce.dropdown_shadow" = "#b4b4b4"
"lapce.border" = "#b4b4b4"
"terminal.cursor" = "$black"
"terminal.foreground" = "$black"
"terminal.background" = "$white"
"editor.background" = "$white"
"editor.foreground" = "$black"
"editor.dim" = "#A0A1A7"

View File

@ -2,7 +2,8 @@
use crate::core_proxy::CoreProxy;
use crate::lsp::LspCatalog;
use crate::plugin::PluginCatalog;
use crate::terminal::{TermId, Terminal, TerminalEvent};
use crate::terminal::{TermId, Terminal, TerminalHostEvent};
use alacritty_terminal::ansi::CursorShape;
use anyhow::{anyhow, Result};
use crossbeam_channel::{unbounded, Receiver, Sender};
use git2::{DiffOptions, Oid, Repository};
@ -444,11 +445,20 @@ fn handle_notification(&self, rpc: Notification) {
loop {
let event = receiver.recv()?;
match event {
TerminalEvent::UpdateContent(content) => {
TerminalHostEvent::UpdateContent { cursor, content } => {
let shape = match cursor.shape {
CursorShape::Block => "Block",
CursorShape::Underline => "Underline",
CursorShape::Beam => "Beam",
CursorShape::HollowBlock => "HollowBlock",
CursorShape::Hidden => "Hidden",
};
local_proxy.send_notification(
"terminal_update_content",
json!({
"id": term_id,
"cursor_shape": shape,
"cursor_point": cursor.point,
"content": content,
}),
);

View File

@ -8,13 +8,14 @@
};
use alacritty_terminal::{
ansi,
config::Config,
event::{Event, EventListener, Notify},
event_loop::{EventLoop, Notifier},
event::{Event, EventListener, Notify, OnResize},
event_loop::{EventLoop, Msg, Notifier},
grid::GridCell,
index::Point,
sync::FairMutex,
term::{cell::Cell, SizeInfo},
term::{cell::Cell, RenderableCursor, SizeInfo},
tty, Term,
};
use anyhow::Result;
@ -44,34 +45,42 @@ pub fn next() -> Self {
}
}
pub enum TerminalHostEvent {
UpdateContent {
cursor: RenderableCursor,
content: Vec<(Point, Cell)>,
},
}
pub enum TerminalEvent {
UpdateContent(Vec<(Point, Cell)>),
resize(SizeInfo),
event(Event),
}
#[derive(Clone)]
pub struct Terminal {
pub term: Arc<FairMutex<Term<EventProxy>>>,
sender: Sender<Event>,
host_sender: Sender<TerminalEvent>,
sender: Sender<TerminalEvent>,
host_sender: Sender<TerminalHostEvent>,
}
pub type TermConfig = Config<HashMap<String, String>>;
#[derive(Clone)]
pub struct EventProxy {
sender: Sender<Event>,
sender: Sender<TerminalEvent>,
}
impl EventProxy {}
impl EventListener for EventProxy {
fn send_event(&self, event: alacritty_terminal::event::Event) {
self.sender.send(event);
self.sender.send(TerminalEvent::event(event));
}
}
impl Terminal {
pub fn new(width: usize, height: usize) -> (Self, Receiver<TerminalEvent>) {
pub fn new(width: usize, height: usize) -> (Self, Receiver<TerminalHostEvent>) {
let config = TermConfig::default();
let (sender, receiver) = crossbeam_channel::unbounded();
let event_proxy = EventProxy {
@ -99,24 +108,23 @@ pub fn new(width: usize, height: usize) -> (Self, Receiver<TerminalEvent>) {
}
pub fn resize(&self, width: usize, height: usize) {
self.term.lock().resize(SizeInfo::new(
width as f32,
height as f32,
1.0,
1.0,
0.0,
0.0,
true,
));
let size =
SizeInfo::new(width as f32, height as f32, 1.0, 1.0, 0.0, 0.0, true);
self.sender.send(TerminalEvent::resize(size));
self.term.lock().resize(size.clone());
}
fn run(&self, receiver: Receiver<Event>, notifier: Notifier) {
fn run(&self, receiver: Receiver<TerminalEvent>, mut notifier: Notifier) {
let term = self.term.clone();
let host_sender = self.host_sender.clone();
std::thread::spawn(move || -> Result<()> {
loop {
let event = receiver.recv()?;
match event {
TerminalEvent::resize(size) => {
notifier.on_resize(&size);
}
TerminalEvent::event(event) => match event {
Event::MouseCursorDirty => {}
Event::Title(_) => {}
Event::ResetTitle => {}
@ -124,34 +132,45 @@ fn run(&self, receiver: Receiver<Event>, notifier: Notifier) {
Event::ClipboardLoad(_, _) => {}
Event::ColorRequest(_, _) => {}
Event::PtyWrite(s) => {
eprintln!("pty write {}", s);
notifier.notify(s.into_bytes());
}
Event::CursorBlinkingChange(_) => {}
Event::Wakeup => {
let cursor =
term.lock().renderable_content().cursor.clone();
let content = term
.lock()
.renderable_content()
.display_iter
.filter_map(|c| {
if c.is_empty() {
if (c.c == ' ' || c.c == '\t')
&& c.bg
== ansi::Color::Named(
ansi::NamedColor::Background,
)
{
None
} else {
Some((c.point, c.cell.clone()))
}
})
.collect::<Vec<(Point, Cell)>>();
let event = TerminalEvent::UpdateContent(content);
let event = TerminalHostEvent::UpdateContent {
content: content,
cursor: cursor,
};
host_sender.send(event);
}
Event::Bell => {}
Event::Exit => {}
},
}
}
});
}
pub fn insert<B: Into<String>>(&self, data: B) {
self.sender.send(Event::PtyWrite(data.into()));
self.sender
.send(TerminalEvent::event(Event::PtyWrite(data.into())));
}
}