mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-10 10:35:16 +03:00
Add bracketed paste (#3233)
This commit is contained in:
parent
51b62230da
commit
f38ede8631
@ -29,7 +29,10 @@ use std::{
|
||||
use anyhow::{Context, Error};
|
||||
|
||||
use crossterm::{
|
||||
event::{DisableMouseCapture, EnableMouseCapture, Event as CrosstermEvent},
|
||||
event::{
|
||||
DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture,
|
||||
Event as CrosstermEvent,
|
||||
},
|
||||
execute, terminal,
|
||||
tty::IsTty,
|
||||
};
|
||||
@ -425,14 +428,13 @@ impl Application {
|
||||
scroll: None,
|
||||
};
|
||||
// Handle key events
|
||||
let should_redraw = match event {
|
||||
Ok(CrosstermEvent::Resize(width, height)) => {
|
||||
let should_redraw = match event.unwrap() {
|
||||
CrosstermEvent::Resize(width, height) => {
|
||||
self.compositor.resize(width, height);
|
||||
self.compositor
|
||||
.handle_event(Event::Resize(width, height), &mut cx)
|
||||
.handle_event(&Event::Resize(width, height), &mut cx)
|
||||
}
|
||||
Ok(event) => self.compositor.handle_event(event.into(), &mut cx),
|
||||
Err(x) => panic!("{}", x),
|
||||
event => self.compositor.handle_event(&event.into(), &mut cx),
|
||||
};
|
||||
|
||||
if should_redraw && !self.editor.should_close() {
|
||||
@ -788,7 +790,7 @@ impl Application {
|
||||
async fn claim_term(&mut self) -> Result<(), Error> {
|
||||
terminal::enable_raw_mode()?;
|
||||
let mut stdout = stdout();
|
||||
execute!(stdout, terminal::EnterAlternateScreen)?;
|
||||
execute!(stdout, terminal::EnterAlternateScreen, EnableBracketedPaste)?;
|
||||
execute!(stdout, terminal::Clear(terminal::ClearType::All))?;
|
||||
if self.config.load().editor.mouse {
|
||||
execute!(stdout, EnableMouseCapture)?;
|
||||
@ -821,7 +823,11 @@ impl Application {
|
||||
// probably not a good idea to `unwrap()` inside a panic handler.
|
||||
// So we just ignore the `Result`s.
|
||||
let _ = execute!(std::io::stdout(), DisableMouseCapture);
|
||||
let _ = execute!(std::io::stdout(), terminal::LeaveAlternateScreen);
|
||||
let _ = execute!(
|
||||
std::io::stdout(),
|
||||
terminal::LeaveAlternateScreen,
|
||||
DisableBracketedPaste
|
||||
);
|
||||
let _ = terminal::disable_raw_mode();
|
||||
hook(info);
|
||||
}));
|
||||
|
@ -3369,13 +3369,7 @@ enum Paste {
|
||||
Cursor,
|
||||
}
|
||||
|
||||
fn paste_impl(
|
||||
values: &[String],
|
||||
doc: &mut Document,
|
||||
view: &View,
|
||||
action: Paste,
|
||||
count: usize,
|
||||
) -> Option<Transaction> {
|
||||
fn paste_impl(values: &[String], doc: &mut Document, view: &View, action: Paste, count: usize) {
|
||||
let repeat = std::iter::repeat(
|
||||
values
|
||||
.last()
|
||||
@ -3418,8 +3412,17 @@ fn paste_impl(
|
||||
};
|
||||
(pos, pos, values.next())
|
||||
});
|
||||
doc.apply(&transaction, view.id);
|
||||
}
|
||||
|
||||
Some(transaction)
|
||||
pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) {
|
||||
let count = cx.count();
|
||||
let (view, doc) = current!(cx.editor);
|
||||
let paste = match doc.mode {
|
||||
Mode::Insert | Mode::Select => Paste::Cursor,
|
||||
Mode::Normal => Paste::Before,
|
||||
};
|
||||
paste_impl(&[contents], doc, view, paste, count);
|
||||
}
|
||||
|
||||
fn paste_clipboard_impl(
|
||||
@ -3429,18 +3432,11 @@ fn paste_clipboard_impl(
|
||||
count: usize,
|
||||
) -> anyhow::Result<()> {
|
||||
let (view, doc) = current!(editor);
|
||||
|
||||
match editor
|
||||
.clipboard_provider
|
||||
.get_contents(clipboard_type)
|
||||
.map(|contents| paste_impl(&[contents], doc, view, action, count))
|
||||
{
|
||||
Ok(Some(transaction)) => {
|
||||
doc.apply(&transaction, view.id);
|
||||
doc.append_changes_to_history(view.id);
|
||||
match editor.clipboard_provider.get_contents(clipboard_type) {
|
||||
Ok(contents) => {
|
||||
paste_impl(&[contents], doc, view, action, count);
|
||||
Ok(())
|
||||
}
|
||||
Ok(None) => Ok(()),
|
||||
Err(e) => Err(e.context("Couldn't get system clipboard contents")),
|
||||
}
|
||||
}
|
||||
@ -3553,11 +3549,8 @@ fn paste(cx: &mut Context, pos: Paste) {
|
||||
let (view, doc) = current!(cx.editor);
|
||||
let registers = &mut cx.editor.registers;
|
||||
|
||||
if let Some(transaction) = registers
|
||||
.read(reg_name)
|
||||
.and_then(|values| paste_impl(values, doc, view, pos, count))
|
||||
{
|
||||
doc.apply(&transaction, view.id);
|
||||
if let Some(values) = registers.read(reg_name) {
|
||||
paste_impl(values, doc, view, pos, count);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4849,7 +4842,7 @@ fn replay_macro(cx: &mut Context) {
|
||||
cx.callback = Some(Box::new(move |compositor, cx| {
|
||||
for _ in 0..count {
|
||||
for &key in keys.iter() {
|
||||
compositor.handle_event(compositor::Event::Key(key), cx);
|
||||
compositor.handle_event(&compositor::Event::Key(key), cx);
|
||||
}
|
||||
}
|
||||
// The macro under replay is cleared at the end of the callback, not in the
|
||||
|
@ -29,7 +29,7 @@ pub struct Context<'a> {
|
||||
|
||||
pub trait Component: Any + AnyComponent {
|
||||
/// Process input events, return true if handled.
|
||||
fn handle_event(&mut self, _event: Event, _ctx: &mut Context) -> EventResult {
|
||||
fn handle_event(&mut self, _event: &Event, _ctx: &mut Context) -> EventResult {
|
||||
EventResult::Ignored(None)
|
||||
}
|
||||
// , args: ()
|
||||
@ -157,10 +157,10 @@ impl Compositor {
|
||||
Some(self.layers.remove(idx))
|
||||
}
|
||||
|
||||
pub fn handle_event(&mut self, event: Event, cx: &mut Context) -> bool {
|
||||
pub fn handle_event(&mut self, event: &Event, cx: &mut Context) -> bool {
|
||||
// If it is a key event and a macro is being recorded, push the key event to the recording.
|
||||
if let (Event::Key(key), Some((_, keys))) = (event, &mut cx.editor.macro_recording) {
|
||||
keys.push(key);
|
||||
keys.push(*key);
|
||||
}
|
||||
|
||||
let mut callbacks = Vec::new();
|
||||
|
@ -298,7 +298,7 @@ impl Completion {
|
||||
}
|
||||
|
||||
impl Component for Completion {
|
||||
fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
|
||||
fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
|
||||
// let the Editor handle Esc instead
|
||||
if let Event::Key(KeyEvent {
|
||||
code: KeyCode::Esc, ..
|
||||
|
@ -936,7 +936,7 @@ impl EditorView {
|
||||
impl EditorView {
|
||||
fn handle_mouse_event(
|
||||
&mut self,
|
||||
event: MouseEvent,
|
||||
event: &MouseEvent,
|
||||
cxt: &mut commands::Context,
|
||||
) -> EventResult {
|
||||
let config = cxt.editor.config();
|
||||
@ -946,7 +946,7 @@ impl EditorView {
|
||||
column,
|
||||
modifiers,
|
||||
..
|
||||
} = event;
|
||||
} = *event;
|
||||
|
||||
let pos_and_view = |editor: &Editor, row, column| {
|
||||
editor.tree.views().find_map(|(view, _focus)| {
|
||||
@ -1115,7 +1115,7 @@ impl EditorView {
|
||||
impl Component for EditorView {
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
context: &mut crate::compositor::Context,
|
||||
) -> EventResult {
|
||||
let mut cx = commands::Context {
|
||||
@ -1128,6 +1128,23 @@ impl Component for EditorView {
|
||||
};
|
||||
|
||||
match event {
|
||||
Event::Paste(contents) => {
|
||||
cx.count = cx.editor.count;
|
||||
commands::paste_bracketed_value(&mut cx, contents.clone());
|
||||
cx.editor.count = None;
|
||||
|
||||
let config = cx.editor.config();
|
||||
let (view, doc) = current!(cx.editor);
|
||||
view.ensure_cursor_in_view(doc, config.scrolloff);
|
||||
|
||||
// Store a history state if not in insert mode. Otherwise wait till we exit insert
|
||||
// to include any edits to the paste in the history state.
|
||||
if doc.mode() != Mode::Insert {
|
||||
doc.append_changes_to_history(view.id);
|
||||
}
|
||||
|
||||
EventResult::Consumed(None)
|
||||
}
|
||||
Event::Resize(_width, _height) => {
|
||||
// Ignore this event, we handle resizing just before rendering to screen.
|
||||
// Handling it here but not re-rendering will cause flashing
|
||||
|
@ -225,9 +225,9 @@ impl<T: Item> Menu<T> {
|
||||
use super::PromptEvent as MenuEvent;
|
||||
|
||||
impl<T: Item + 'static> Component for Menu<T> {
|
||||
fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
|
||||
fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
|
||||
let event = match event {
|
||||
Event::Key(event) => event,
|
||||
Event::Key(event) => *event,
|
||||
_ => return EventResult::Ignored(None),
|
||||
};
|
||||
|
||||
|
@ -61,7 +61,7 @@ impl<T: Component + 'static> Component for Overlay<T> {
|
||||
Some((width, height))
|
||||
}
|
||||
|
||||
fn handle_event(&mut self, event: Event, ctx: &mut Context) -> EventResult {
|
||||
fn handle_event(&mut self, event: &Event, ctx: &mut Context) -> EventResult {
|
||||
self.content.handle_event(event, ctx)
|
||||
}
|
||||
|
||||
|
@ -260,7 +260,7 @@ impl<T: Item + 'static> Component for FilePicker<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_event(&mut self, event: Event, ctx: &mut Context) -> EventResult {
|
||||
fn handle_event(&mut self, event: &Event, ctx: &mut Context) -> EventResult {
|
||||
// TODO: keybinds for scrolling preview
|
||||
self.picker.handle_event(event, ctx)
|
||||
}
|
||||
@ -476,6 +476,14 @@ impl<T: Item> Picker<T> {
|
||||
pub fn toggle_preview(&mut self) {
|
||||
self.show_preview = !self.show_preview;
|
||||
}
|
||||
|
||||
fn prompt_handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
|
||||
if let EventResult::Consumed(_) = self.prompt.handle_event(event, cx) {
|
||||
// TODO: recalculate only if pattern changed
|
||||
self.score();
|
||||
}
|
||||
EventResult::Consumed(None)
|
||||
}
|
||||
}
|
||||
|
||||
// process:
|
||||
@ -489,9 +497,10 @@ impl<T: Item + 'static> Component for Picker<T> {
|
||||
Some(viewport)
|
||||
}
|
||||
|
||||
fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
|
||||
fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
|
||||
let key_event = match event {
|
||||
Event::Key(event) => event,
|
||||
Event::Key(event) => *event,
|
||||
Event::Paste(..) => return self.prompt_handle_event(event, cx),
|
||||
Event::Resize(..) => return EventResult::Consumed(None),
|
||||
_ => return EventResult::Ignored(None),
|
||||
};
|
||||
@ -548,10 +557,7 @@ impl<T: Item + 'static> Component for Picker<T> {
|
||||
self.toggle_preview();
|
||||
}
|
||||
_ => {
|
||||
if let EventResult::Consumed(_) = self.prompt.handle_event(event, cx) {
|
||||
// TODO: recalculate only if pattern changed
|
||||
self.score();
|
||||
}
|
||||
self.prompt_handle_event(event, cx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,9 +138,9 @@ impl<T: Component> Popup<T> {
|
||||
}
|
||||
|
||||
impl<T: Component> Component for Popup<T> {
|
||||
fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
|
||||
fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
|
||||
let key = match event {
|
||||
Event::Key(event) => event,
|
||||
Event::Key(event) => *event,
|
||||
Event::Resize(_, _) => {
|
||||
// TODO: calculate inner area, call component's handle_event with that area
|
||||
return EventResult::Ignored(None);
|
||||
|
@ -466,9 +466,13 @@ impl Prompt {
|
||||
}
|
||||
|
||||
impl Component for Prompt {
|
||||
fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
|
||||
fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
|
||||
let event = match event {
|
||||
Event::Key(event) => event,
|
||||
Event::Paste(data) => {
|
||||
self.insert_str(data);
|
||||
return EventResult::Consumed(None);
|
||||
}
|
||||
Event::Key(event) => *event,
|
||||
Event::Resize(..) => return EventResult::Consumed(None),
|
||||
_ => return EventResult::Ignored(None),
|
||||
};
|
||||
|
@ -6,12 +6,13 @@ use std::fmt;
|
||||
|
||||
pub use crate::keyboard::{KeyCode, KeyModifiers};
|
||||
|
||||
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Hash)]
|
||||
pub enum Event {
|
||||
FocusGained,
|
||||
FocusLost,
|
||||
Key(KeyEvent),
|
||||
Mouse(MouseEvent),
|
||||
Paste(String),
|
||||
Resize(u16, u16),
|
||||
}
|
||||
|
||||
@ -276,9 +277,7 @@ impl From<crossterm::event::Event> for Event {
|
||||
crossterm::event::Event::Resize(w, h) => Self::Resize(w, h),
|
||||
crossterm::event::Event::FocusGained => Self::FocusGained,
|
||||
crossterm::event::Event::FocusLost => Self::FocusLost,
|
||||
crossterm::event::Event::Paste(_) => {
|
||||
unreachable!("crossterm shouldn't emit Paste events without them being enabled")
|
||||
}
|
||||
crossterm::event::Event::Paste(s) => Self::Paste(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user