terminal: Implement text selection and copy/paste functionality in terminal mode (#1163)
This commit is contained in:
@@ -27,6 +27,18 @@ command = "open_settings"
|
||||
key = "ctrl+k ctrl+s"
|
||||
command = "open_keyboard_shortcuts"
|
||||
|
||||
# --------------------------------- Terminal copy/paste ---------------------------------
|
||||
|
||||
[[keymaps]]
|
||||
key = "ctrl+shift+c"
|
||||
command = "clipboard_copy"
|
||||
mode = "t"
|
||||
|
||||
[[keymaps]]
|
||||
key = "ctrl+shift+v"
|
||||
command = "clipboard_paste"
|
||||
mode = "t"
|
||||
|
||||
# --------------------------------- Basic editing ---------------------------------------
|
||||
|
||||
[[keymaps]]
|
||||
|
||||
@@ -34,7 +34,7 @@ pub enum Mode {
|
||||
|
||||
bitflags! {
|
||||
pub struct Modes: u32 {
|
||||
const NORMAL = 0x01;
|
||||
const NORMAL = 0x1;
|
||||
const INSERT = 0x2;
|
||||
const VISUAL = 0x4;
|
||||
const TERMINAL = 0x8;
|
||||
|
||||
@@ -101,7 +101,11 @@ impl KeyMapLoader {
|
||||
|
||||
let modes = get_modes(toml_keymap);
|
||||
// If not using modal editing, remove keymaps that only make sense in modal.
|
||||
if !modal && !modes.is_empty() && !modes.contains(Modes::INSERT) {
|
||||
if !modal
|
||||
&& !modes.is_empty()
|
||||
&& !modes.contains(Modes::INSERT)
|
||||
&& !modes.contains(Modes::TERMINAL)
|
||||
{
|
||||
log::debug!("Keymap ignored: {}", key);
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
@@ -11,14 +11,15 @@ use alacritty_terminal::{
|
||||
Term,
|
||||
};
|
||||
use druid::{
|
||||
keyboard_types::Key, Application, Color, Command, Env, EventCtx, ExtEventSink,
|
||||
KeyEvent, Modifiers, Target, WidgetId,
|
||||
keyboard_types::Key, Color, Command, Env, EventCtx, ExtEventSink, KeyEvent,
|
||||
Modifiers, Target, WidgetId,
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use lapce_core::{
|
||||
command::{EditCommand, FocusCommand},
|
||||
mode::{Mode, VisualMode},
|
||||
movement::{LinePosition, Movement},
|
||||
register::Clipboard,
|
||||
};
|
||||
use lapce_rpc::terminal::TermId;
|
||||
use parking_lot::Mutex;
|
||||
@@ -29,6 +30,7 @@ use crate::{
|
||||
},
|
||||
config::{Config, LapceTheme},
|
||||
data::LapceWorkspace,
|
||||
document::SystemClipboard,
|
||||
find::Find,
|
||||
keypress::KeyPressFocus,
|
||||
proxy::LapceProxy,
|
||||
@@ -263,6 +265,7 @@ impl KeyPressFocus for LapceTerminalViewData {
|
||||
_mods: Modifiers,
|
||||
_env: &Env,
|
||||
) -> CommandExecuted {
|
||||
let mut clipboard = SystemClipboard {};
|
||||
ctx.request_paint();
|
||||
match &command.kind {
|
||||
CommandKind::Move(cmd) => {
|
||||
@@ -357,12 +360,19 @@ impl KeyPressFocus for LapceTerminalViewData {
|
||||
let mut raw = self.terminal.raw.lock();
|
||||
let term = &mut raw.term;
|
||||
if let Some(content) = term.selection_to_string() {
|
||||
Application::global().clipboard().put_string(content);
|
||||
clipboard.put_string(content);
|
||||
}
|
||||
if self.terminal.mode != Mode::Terminal {
|
||||
self.terminal.clear_selection(term);
|
||||
}
|
||||
self.terminal.clear_selection(term);
|
||||
}
|
||||
EditCommand::ClipboardPaste => {
|
||||
if let Some(s) = Application::global().clipboard().get_string() {
|
||||
if self.terminal.mode == Mode::Terminal {
|
||||
let mut raw = self.terminal.raw.lock();
|
||||
let term = &mut raw.term;
|
||||
self.terminal.clear_selection(term);
|
||||
}
|
||||
if let Some(s) = clipboard.get_string() {
|
||||
self.receive_char(ctx, &s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use alacritty_terminal::{
|
||||
grid::Dimensions,
|
||||
index::{Direction, Side},
|
||||
term::{cell::Flags, search::RegexSearch},
|
||||
grid::{Dimensions, Scroll},
|
||||
index::{Column, Direction, Line, Side},
|
||||
selection::{Selection, SelectionType},
|
||||
term::{cell::Flags, search::RegexSearch, Term},
|
||||
};
|
||||
use druid::{
|
||||
piet::{Text, TextAttribute, TextLayoutBuilder},
|
||||
BoxConstraints, Command, Data, Env, Event, EventCtx, FontWeight, LayoutCtx,
|
||||
LifeCycle, LifeCycleCtx, MouseEvent, PaintCtx, Point, Rect, RenderContext, Size,
|
||||
Target, UpdateCtx, Widget, WidgetExt, WidgetId, WidgetPod,
|
||||
BoxConstraints, Command, Cursor, Data, Env, Event, EventCtx, FontWeight,
|
||||
LayoutCtx, LifeCycle, LifeCycleCtx, MouseEvent, PaintCtx, Point, Rect,
|
||||
RenderContext, Size, Target, UpdateCtx, Widget, WidgetExt, WidgetId, WidgetPod,
|
||||
};
|
||||
use lapce_core::mode::Mode;
|
||||
use lapce_core::{mode::Mode, register::Clipboard};
|
||||
use lapce_data::{
|
||||
command::{LapceUICommand, LAPCE_UI_COMMAND},
|
||||
config::LapceTheme,
|
||||
data::{FocusArea, LapceTabData},
|
||||
document::SystemClipboard,
|
||||
panel::PanelKind,
|
||||
terminal::{LapceTerminalData, LapceTerminalViewData},
|
||||
terminal::{EventProxy, LapceTerminalData, LapceTerminalViewData},
|
||||
};
|
||||
use lapce_rpc::terminal::TermId;
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
@@ -498,6 +500,32 @@ impl LapceTerminal {
|
||||
panel.active = position;
|
||||
}
|
||||
}
|
||||
|
||||
fn select(
|
||||
&self,
|
||||
term: &mut Term<EventProxy>,
|
||||
mouse_event: &MouseEvent,
|
||||
ty: SelectionType,
|
||||
) {
|
||||
let row_size = self.height / term.screen_lines() as f64;
|
||||
let col_size = self.width / term.columns() as f64;
|
||||
let offset = term.grid().display_offset();
|
||||
let column = Column((mouse_event.pos.x / col_size) as usize);
|
||||
let line = Line((mouse_event.pos.y / row_size) as i32 - offset as i32);
|
||||
match &mut term.selection {
|
||||
Some(selection) => selection.update(
|
||||
alacritty_terminal::index::Point { line, column },
|
||||
Direction::Left,
|
||||
),
|
||||
None => {
|
||||
term.selection = Some(Selection::new(
|
||||
ty,
|
||||
alacritty_terminal::index::Point { line, column },
|
||||
Direction::Left,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget<LapceTabData> for LapceTerminal {
|
||||
@@ -519,9 +547,55 @@ impl Widget<LapceTabData> for LapceTerminal {
|
||||
config: data.config.clone(),
|
||||
find: data.find.clone(),
|
||||
};
|
||||
ctx.set_cursor(&Cursor::IBeam);
|
||||
match event {
|
||||
Event::MouseDown(_mouse_event) => {
|
||||
Event::MouseDown(mouse_event) => {
|
||||
self.request_focus(ctx, data);
|
||||
let terminal = data.terminal.terminals.get(&self.term_id).unwrap();
|
||||
let term = &mut terminal.raw.lock().term;
|
||||
if mouse_event.button.is_right() {
|
||||
let mut clipboard = SystemClipboard {};
|
||||
match term.selection_to_string() {
|
||||
Some(selection) => {
|
||||
clipboard.put_string(selection);
|
||||
term.selection = None;
|
||||
}
|
||||
None => match clipboard.get_string() {
|
||||
Some(string) => {
|
||||
terminal.proxy.proxy_rpc.terminal_write(
|
||||
terminal.term_id,
|
||||
string.as_str(),
|
||||
);
|
||||
term.scroll_display(Scroll::Bottom);
|
||||
}
|
||||
None => {}
|
||||
},
|
||||
}
|
||||
} else if mouse_event.button.is_left() {
|
||||
match mouse_event.count {
|
||||
2 => self.select(term, mouse_event, SelectionType::Semantic),
|
||||
_ => {
|
||||
term.selection = None;
|
||||
if mouse_event.count == 3 {
|
||||
self.select(term, mouse_event, SelectionType::Lines);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::MouseMove(mouse_event) => {
|
||||
if mouse_event.buttons.has_left() {
|
||||
let term = &mut data
|
||||
.terminal
|
||||
.terminals
|
||||
.get(&self.term_id)
|
||||
.unwrap()
|
||||
.raw
|
||||
.lock()
|
||||
.term;
|
||||
self.select(term, mouse_event, SelectionType::Simple);
|
||||
ctx.request_paint();
|
||||
}
|
||||
}
|
||||
Event::Wheel(wheel_event) => {
|
||||
data.terminal
|
||||
|
||||
Reference in New Issue
Block a user