Skip to content
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

Add bracketed paste #3233

Merged
merged 1 commit into from Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 14 additions & 8 deletions helix-term/src/application.rs
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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)?;
Expand Down Expand Up @@ -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);
}));
Expand Down
41 changes: 17 additions & 24 deletions helix-term/src/commands.rs
Expand Up @@ -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()
Expand Down Expand Up @@ -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) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't function as an actual command as I didn't see a good way to pass the contents through the Context. I think it's nicer to have it as a standalone function, but maybe I'm missing a way to make it work more like a regular command?

Because it's not a command, it doesn't set editor.last_insert and won't be repeated with .. Since you can spam your paste key as easily as ., I didn't think that was a big deal.

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(
Expand All @@ -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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't needed here as it's done in editor's event handling.

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")),
}
}
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions helix-term/src/compositor.rs
Expand Up @@ -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: ()
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion helix-term/src/ui/completion.rs
Expand Up @@ -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, ..
Expand Down
23 changes: 20 additions & 3 deletions helix-term/src/ui/editor.rs
Expand Up @@ -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();
Expand All @@ -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)| {
Expand Down Expand Up @@ -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 {
Expand All @@ -1128,6 +1128,23 @@ impl Component for EditorView {
};

match event {
Event::Paste(contents) => {
cx.count = cx.editor.count;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I felt like it made more sense to use a pending count here, though I could also see leaving it for an upcoming command.

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
Expand Down
4 changes: 2 additions & 2 deletions helix-term/src/ui/menu.rs
Expand Up @@ -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),
};

Expand Down
2 changes: 1 addition & 1 deletion helix-term/src/ui/overlay.rs
Expand Up @@ -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)
}

Expand Down
20 changes: 13 additions & 7 deletions helix-term/src/ui/picker.rs
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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:
Expand All @@ -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),
};
Expand Down Expand Up @@ -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);
}
}

Expand Down
4 changes: 2 additions & 2 deletions helix-term/src/ui/popup.rs
Expand Up @@ -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);
Expand Down
8 changes: 6 additions & 2 deletions helix-term/src/ui/prompt.rs
Expand Up @@ -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),
};
Expand Down
7 changes: 3 additions & 4 deletions helix-view/src/input.rs
Expand Up @@ -6,12 +6,13 @@ use std::fmt;

pub use crate::keyboard::{KeyCode, KeyModifiers};

#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the paste data is on the heap, we have to lose this Copy. That's why there's all this introduction of references through the rest of the PR.

#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Hash)]
pub enum Event {
FocusGained,
FocusLost,
Key(KeyEvent),
Mouse(MouseEvent),
Paste(String),
Resize(u16, u16),
}

Expand Down Expand Up @@ -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),
}
}
}
Expand Down