2020-09-19 16:16:00 +02:00
use helix_core ::{
2021-11-21 18:38:41 +01:00
comment , coords_at_pos , find_first_non_whitespace_char , find_root , graphemes ,
2021-11-14 16:16:47 +01:00
history ::UndoKind ,
2021-11-26 03:58:23 +01:00
increment ::date_time ::DateTimeIncrementor ,
2021-11-21 18:38:41 +01:00
increment ::{ number ::NumberIncrementor , Increment } ,
2021-11-14 16:16:47 +01:00
indent ,
2021-07-29 21:10:59 +02:00
indent ::IndentStyle ,
2021-07-09 01:45:19 +02:00
line_ending ::{ get_line_ending_of_str , line_end_char_index , str_is_line_ending } ,
2021-06-08 08:25:55 +02:00
match_brackets ,
2021-03-18 07:07:02 +01:00
movement ::{ self , Direction } ,
object , pos_at_coords ,
2021-09-20 06:45:07 +02:00
regex ::{ self , Regex , RegexBuilder } ,
2021-12-12 13:13:33 +01:00
search , selection , shellwords , surround , textobject ,
2021-11-18 16:19:40 +01:00
unicode ::width ::UnicodeWidthChar ,
LineEnding , Position , Range , Rope , RopeGraphemes , RopeSlice , Selection , SmallVec , Tendril ,
Transaction ,
2020-09-19 16:16:00 +02:00
} ;
2021-04-01 04:37:18 +02:00
use helix_view ::{
2021-10-24 15:47:10 +02:00
clipboard ::ClipboardType ,
2021-11-13 05:15:41 +01:00
document ::{ Mode , SCRATCH_BUFFER_NAME } ,
2021-10-24 15:47:10 +02:00
editor ::{ Action , Motion } ,
input ::KeyEvent ,
keyboard ::KeyCode ,
view ::View ,
Document , DocumentId , Editor , ViewId ,
2021-04-01 04:37:18 +02:00
} ;
2021-11-30 04:59:19 +01:00
use anyhow ::{ anyhow , bail , ensure , Context as _ } ;
2021-04-01 04:37:18 +02:00
use helix_lsp ::{
2021-11-08 16:17:54 +01:00
block_on , lsp ,
2021-06-12 14:45:21 +02:00
util ::{ lsp_pos_to_pos , lsp_range_to_range , pos_to_lsp_pos , range_to_lsp_range } ,
2021-07-01 19:41:20 +02:00
OffsetEncoding ,
2021-04-01 04:37:18 +02:00
} ;
2021-06-17 13:08:05 +02:00
use insert ::* ;
2021-06-11 14:57:07 +02:00
use movement ::Movement ;
2020-09-19 16:16:00 +02:00
2021-03-22 04:40:07 +01:00
use crate ::{
2021-07-01 19:41:20 +02:00
compositor ::{ self , Component , Compositor } ,
2021-08-12 09:00:42 +02:00
ui ::{ self , FilePicker , Picker , Popup , Prompt , PromptEvent } ,
2021-03-22 04:40:07 +01:00
} ;
2020-12-10 10:13:42 +01:00
2021-07-01 19:41:20 +02:00
use crate ::job ::{ self , Job , Jobs } ;
2021-09-21 18:03:12 +02:00
use futures_util ::{ FutureExt , StreamExt } ;
2021-11-30 02:22:21 +01:00
use std ::{ collections ::HashSet , num ::NonZeroUsize } ;
2021-07-01 19:41:20 +02:00
use std ::{ fmt , future ::Future } ;
2021-04-01 04:37:18 +02:00
2021-06-11 14:57:07 +02:00
use std ::{
borrow ::Cow ,
path ::{ Path , PathBuf } ,
} ;
2021-03-07 19:41:49 +01:00
2021-07-26 18:07:13 +02:00
use once_cell ::sync ::Lazy ;
2021-06-22 19:04:04 +02:00
use serde ::de ::{ self , Deserialize , Deserializer } ;
2021-03-26 08:02:13 +01:00
2021-09-24 03:27:16 +02:00
use grep_regex ::RegexMatcherBuilder ;
2021-09-21 18:03:12 +02:00
use grep_searcher ::{ sinks , BinaryDetection , SearcherBuilder } ;
use ignore ::{ DirEntry , WalkBuilder , WalkState } ;
use tokio_stream ::wrappers ::UnboundedReceiverStream ;
2021-01-21 08:55:46 +01:00
pub struct Context < ' a > {
2021-09-08 07:52:09 +02:00
pub register : Option < char > ,
2021-07-08 03:58:11 +02:00
pub count : Option < NonZeroUsize > ,
2021-01-21 08:55:46 +01:00
pub editor : & ' a mut Editor ,
2020-12-11 10:25:09 +01:00
pub callback : Option < crate ::compositor ::Callback > ,
2021-03-11 02:44:38 +01:00
pub on_next_key_callback : Option < Box < dyn FnOnce ( & mut Context , KeyEvent ) > > ,
2021-06-28 14:48:38 +02:00
pub jobs : & ' a mut Jobs ,
2020-10-30 06:09:59 +01:00
}
2021-01-21 08:55:46 +01:00
impl < ' a > Context < ' a > {
2021-02-21 11:04:31 +01:00
/// Push a new component onto the compositor.
2021-07-01 20:57:12 +02:00
pub fn push_layer ( & mut self , component : Box < dyn Component > ) {
2021-12-12 13:16:48 +01:00
self . callback = Some ( Box ::new ( | compositor : & mut Compositor , _ | {
2021-05-09 11:02:31 +02:00
compositor . push ( component )
} ) ) ;
2021-02-21 11:04:31 +01:00
}
2021-03-11 02:44:38 +01:00
#[ inline ]
pub fn on_next_key (
& mut self ,
on_next_key_callback : impl FnOnce ( & mut Context , KeyEvent ) + 'static ,
) {
self . on_next_key_callback = Some ( Box ::new ( on_next_key_callback ) ) ;
}
2021-03-26 08:02:13 +01:00
#[ inline ]
pub fn callback < T , F > (
& mut self ,
call : impl Future < Output = helix_lsp ::Result < serde_json ::Value > > + 'static + Send ,
callback : F ,
) where
T : for < ' de > serde ::Deserialize < ' de > + Send + 'static ,
F : FnOnce ( & mut Editor , & mut Compositor , T ) + Send + 'static ,
{
let callback = Box ::pin ( async move {
let json = call . await ? ;
let response = serde_json ::from_value ( json ) ? ;
2021-06-28 14:48:38 +02:00
let call : job ::Callback =
2021-03-26 08:02:13 +01:00
Box ::new ( move | editor : & mut Editor , compositor : & mut Compositor | {
callback ( editor , compositor , response )
} ) ;
Ok ( call )
} ) ;
2021-06-28 14:48:38 +02:00
self . jobs . callback ( callback ) ;
2021-03-26 08:02:13 +01:00
}
2021-06-08 05:24:27 +02:00
2021-06-17 08:19:02 +02:00
/// Returns 1 if no explicit count was provided
2021-06-08 05:24:27 +02:00
#[ inline ]
pub fn count ( & self ) -> usize {
2021-06-15 06:29:03 +02:00
self . count . map_or ( 1 , | v | v . get ( ) )
2021-06-08 05:24:27 +02:00
}
2021-01-21 08:55:46 +01:00
}
2021-05-08 08:36:27 +02:00
enum Align {
Top ,
Center ,
Bottom ,
}
fn align_view ( doc : & Document , view : & mut View , align : Align ) {
2021-07-26 17:40:30 +02:00
let pos = doc
. selection ( view . id )
. primary ( )
. cursor ( doc . text ( ) . slice ( .. ) ) ;
2021-05-08 08:36:27 +02:00
let line = doc . text ( ) . char_to_line ( pos ) ;
2021-08-19 06:19:15 +02:00
let height = view . inner_area ( ) . height as usize ;
2021-08-11 06:53:38 +02:00
2021-05-08 08:36:27 +02:00
let relative = match align {
2021-08-11 06:53:38 +02:00
Align ::Center = > height / 2 ,
2021-05-08 08:36:27 +02:00
Align ::Top = > 0 ,
2021-08-11 06:53:38 +02:00
Align ::Bottom = > height ,
2021-05-08 08:36:27 +02:00
} ;
2021-08-19 05:52:07 +02:00
view . offset . row = line . saturating_sub ( relative ) ;
2021-05-08 08:36:27 +02:00
}
2021-12-04 15:47:18 +01:00
/// A MappableCommand is either a static command like "jump_view_up" or a Typable command like
/// :format. It causes a side-effect on the state (usually by creating and applying a transaction).
/// Both of these types of commands can be mapped with keybindings in the config.toml.
#[ derive(Clone) ]
pub enum MappableCommand {
Typable {
name : String ,
args : Vec < String > ,
doc : String ,
} ,
Static {
name : & 'static str ,
fun : fn ( cx : & mut Context ) ,
doc : & 'static str ,
} ,
}
macro_rules ! static_commands {
2021-08-31 11:13:16 +02:00
( $( $name :ident , $doc :literal , ) * ) = > {
2021-06-17 13:08:05 +02:00
$(
#[ allow(non_upper_case_globals) ]
2021-12-04 15:47:18 +01:00
pub const $name : Self = Self ::Static {
2021-07-26 18:07:13 +02:00
name : stringify ! ( $name ) ,
fun : $name ,
doc : $doc
} ;
2021-06-17 13:08:05 +02:00
) *
2021-12-04 15:47:18 +01:00
pub const STATIC_COMMAND_LIST : & 'static [ Self ] = & [
2021-06-17 13:08:05 +02:00
$( Self ::$name , ) *
] ;
}
}
2020-06-07 17:15:39 +02:00
2021-12-04 15:47:18 +01:00
impl MappableCommand {
2021-06-17 13:08:05 +02:00
pub fn execute ( & self , cx : & mut Context ) {
2021-12-04 15:47:18 +01:00
match & self {
MappableCommand ::Typable { name , args , doc : _ } = > {
2021-12-12 13:13:33 +01:00
let args : Vec < Cow < str > > = args . iter ( ) . map ( Cow ::from ) . collect ( ) ;
2021-12-04 15:47:18 +01:00
if let Some ( command ) = cmd ::TYPABLE_COMMAND_MAP . get ( name . as_str ( ) ) {
let mut cx = compositor ::Context {
editor : cx . editor ,
jobs : cx . jobs ,
scroll : None ,
} ;
2021-12-12 13:13:33 +01:00
if let Err ( e ) = ( command . fun ) ( & mut cx , & args [ .. ] , PromptEvent ::Validate ) {
2021-12-04 15:47:18 +01:00
cx . editor . set_error ( format! ( " {} " , e ) ) ;
}
}
}
MappableCommand ::Static { fun , .. } = > ( fun ) ( cx ) ,
}
2021-06-17 13:08:05 +02:00
}
2021-12-04 15:47:18 +01:00
pub fn name ( & self ) -> & str {
match & self {
MappableCommand ::Typable { name , .. } = > name ,
MappableCommand ::Static { name , .. } = > name ,
}
2021-07-26 18:07:13 +02:00
}
2021-12-04 15:47:18 +01:00
pub fn doc ( & self ) -> & str {
match & self {
MappableCommand ::Typable { doc , .. } = > doc ,
MappableCommand ::Static { doc , .. } = > doc ,
}
2021-06-17 13:08:05 +02:00
}
2021-07-26 18:07:13 +02:00
#[ rustfmt::skip ]
2021-12-04 15:47:18 +01:00
static_commands! (
2021-09-13 10:48:12 +02:00
no_op , " Do nothing " ,
2021-07-26 18:07:13 +02:00
move_char_left , " Move left " ,
move_char_right , " Move right " ,
move_line_up , " Move up " ,
move_line_down , " Move down " ,
extend_char_left , " Extend left " ,
extend_char_right , " Extend right " ,
extend_line_up , " Extend up " ,
extend_line_down , " Extend down " ,
2021-07-17 14:09:46 +02:00
copy_selection_on_next_line , " Copy selection on next line " ,
copy_selection_on_prev_line , " Copy selection on previous line " ,
2021-07-26 18:07:13 +02:00
move_next_word_start , " Move to beginning of next word " ,
move_prev_word_start , " Move to beginning of previous word " ,
2021-11-15 16:31:20 +01:00
move_prev_word_end , " Move to end of previous word " ,
2021-07-26 18:07:13 +02:00
move_next_word_end , " Move to end of next word " ,
move_next_long_word_start , " Move to beginning of next long word " ,
move_prev_long_word_start , " Move to beginning of previous long word " ,
move_next_long_word_end , " Move to end of next long word " ,
extend_next_word_start , " Extend to beginning of next word " ,
extend_prev_word_start , " Extend to beginning of previous word " ,
2021-09-04 15:30:32 +02:00
extend_next_long_word_start , " Extend to beginning of next long word " ,
extend_prev_long_word_start , " Extend to beginning of previous long word " ,
extend_next_long_word_end , " Extend to end of next long word " ,
2021-07-26 18:07:13 +02:00
extend_next_word_end , " Extend to end of next word " ,
find_till_char , " Move till next occurance of char " ,
find_next_char , " Move to next occurance of char " ,
extend_till_char , " Extend till next occurance of char " ,
extend_next_char , " Extend to next occurance of char " ,
till_prev_char , " Move till previous occurance of char " ,
find_prev_char , " Move to previous occurance of char " ,
extend_till_prev_char , " Extend till previous occurance of char " ,
extend_prev_char , " Extend to previous occurance of char " ,
2021-10-24 15:47:10 +02:00
repeat_last_motion , " repeat last motion(extend_next_char, extend_till_char, find_next_char, find_till_char...) " ,
2021-07-26 18:07:13 +02:00
replace , " Replace with new char " ,
switch_case , " Switch (toggle) case " ,
switch_to_uppercase , " Switch to uppercase " ,
switch_to_lowercase , " Switch to lowercase " ,
page_up , " Move page up " ,
page_down , " Move page down " ,
half_page_up , " Move half page up " ,
half_page_down , " Move half page down " ,
select_all , " Select whole document " ,
select_regex , " Select all regex matches inside selections " ,
split_selection , " Split selection into subselections on regex matches " ,
split_selection_on_newline , " Split selection on newlines " ,
search , " Search for regex pattern " ,
2021-11-06 09:33:30 +01:00
rsearch , " Reverse search for regex pattern " ,
2021-07-26 18:07:13 +02:00
search_next , " Select next search match " ,
2021-11-06 09:33:30 +01:00
search_prev , " Select previous search match " ,
2021-07-26 18:07:13 +02:00
extend_search_next , " Add next search match to selection " ,
2021-11-06 09:33:30 +01:00
extend_search_prev , " Add previous search match to selection " ,
2021-07-26 18:07:13 +02:00
search_selection , " Use current selection as search pattern " ,
2021-09-21 18:03:12 +02:00
global_search , " Global Search in workspace folder " ,
2021-07-26 18:07:13 +02:00
extend_line , " Select current line, if already selected, extend to next line " ,
extend_to_line_bounds , " Extend selection to line bounds (line-wise selection) " ,
delete_selection , " Delete selection " ,
2021-11-24 08:46:40 +01:00
delete_selection_noyank , " Delete selection, without yanking " ,
2021-07-26 18:07:13 +02:00
change_selection , " Change selection (delete and enter insert mode) " ,
2021-11-24 08:46:40 +01:00
change_selection_noyank , " Change selection (delete and enter insert mode, without yanking) " ,
2021-07-26 18:07:13 +02:00
collapse_selection , " Collapse selection onto a single cursor " ,
flip_selections , " Flip selection cursor and anchor " ,
insert_mode , " Insert before selection " ,
append_mode , " Insert after selection (append) " ,
command_mode , " Enter command mode " ,
file_picker , " Open file picker " ,
code_action , " Perform code action " ,
buffer_picker , " Open buffer picker " ,
symbol_picker , " Open symbol picker " ,
2021-11-14 16:12:56 +01:00
workspace_symbol_picker , " Open workspace symbol picker " ,
2021-07-26 18:07:13 +02:00
last_picker , " Open last picker " ,
prepend_to_line , " Insert at start of line " ,
append_to_line , " Insert at end of line " ,
open_below , " Open new line below selection " ,
open_above , " Open new line above selection " ,
normal_mode , " Enter normal mode " ,
select_mode , " Enter selection extend mode " ,
exit_select_mode , " Exit selection mode " ,
goto_definition , " Goto definition " ,
2021-09-01 17:55:16 +02:00
add_newline_above , " Add newline above " ,
add_newline_below , " Add newline below " ,
2021-07-26 18:07:13 +02:00
goto_type_definition , " Goto type definition " ,
goto_implementation , " Goto implementation " ,
2021-12-18 15:58:17 +01:00
goto_file_start , " Goto line number <n> else file start " ,
2021-07-26 18:07:13 +02:00
goto_file_end , " Goto file end " ,
2021-12-13 06:36:16 +01:00
goto_file , " Goto files in selection " ,
goto_file_hsplit , " Goto files in selection (hsplit) " ,
goto_file_vsplit , " Goto files in selection (vsplit) " ,
2021-07-26 18:07:13 +02:00
goto_reference , " Goto references " ,
goto_window_top , " Goto window top " ,
2021-12-02 05:46:57 +01:00
goto_window_center , " Goto window center " ,
2021-07-26 18:07:13 +02:00
goto_window_bottom , " Goto window bottom " ,
goto_last_accessed_file , " Goto last accessed file " ,
2021-12-02 05:46:57 +01:00
goto_last_modified_file , " Goto last modified file " ,
2021-11-14 16:11:53 +01:00
goto_last_modification , " Goto last modification " ,
2021-07-28 09:55:34 +02:00
goto_line , " Goto line " ,
2021-08-03 15:49:40 +02:00
goto_last_line , " Goto last line " ,
2021-07-26 18:07:13 +02:00
goto_first_diag , " Goto first diagnostic " ,
goto_last_diag , " Goto last diagnostic " ,
goto_next_diag , " Goto next diagnostic " ,
goto_prev_diag , " Goto previous diagnostic " ,
goto_line_start , " Goto line start " ,
goto_line_end , " Goto line end " ,
2021-11-01 20:52:47 +01:00
goto_next_buffer , " Goto next buffer " ,
goto_previous_buffer , " Goto previous buffer " ,
2021-07-26 18:07:13 +02:00
// TODO: different description ?
goto_line_end_newline , " Goto line end " ,
goto_first_nonwhitespace , " Goto first non-blank in line " ,
2021-11-14 16:16:20 +01:00
trim_selections , " Trim whitespace from selections " ,
2021-09-07 16:22:39 +02:00
extend_to_line_start , " Extend to line start " ,
extend_to_line_end , " Extend to line end " ,
extend_to_line_end_newline , " Extend to line end " ,
2021-07-26 18:07:13 +02:00
signature_help , " Show signature help " ,
insert_tab , " Insert tab char " ,
insert_newline , " Insert newline char " ,
delete_char_backward , " Delete previous char " ,
delete_char_forward , " Delete next char " ,
delete_word_backward , " Delete previous word " ,
2021-11-15 16:31:20 +01:00
delete_word_forward , " Delete next word " ,
kill_to_line_start , " Delete content till the start of the line " ,
kill_to_line_end , " Delete content till the end of the line " ,
2021-07-26 18:07:13 +02:00
undo , " Undo change " ,
redo , " Redo change " ,
2021-11-14 16:16:47 +01:00
earlier , " Move backward in history " ,
later , " Move forward in history " ,
2021-07-26 18:07:13 +02:00
yank , " Yank selection " ,
yank_joined_to_clipboard , " Join and yank selections to clipboard " ,
yank_main_selection_to_clipboard , " Yank main selection to clipboard " ,
2021-08-12 04:53:48 +02:00
yank_joined_to_primary_clipboard , " Join and yank selections to primary clipboard " ,
yank_main_selection_to_primary_clipboard , " Yank main selection to primary clipboard " ,
2021-07-26 18:07:13 +02:00
replace_with_yanked , " Replace with yanked text " ,
replace_selections_with_clipboard , " Replace selections by clipboard content " ,
2021-08-12 04:53:48 +02:00
replace_selections_with_primary_clipboard , " Replace selections by primary clipboard content " ,
2021-07-26 18:07:13 +02:00
paste_after , " Paste after selection " ,
paste_before , " Paste before selection " ,
paste_clipboard_after , " Paste clipboard after selections " ,
paste_clipboard_before , " Paste clipboard before selections " ,
2021-08-12 04:53:48 +02:00
paste_primary_clipboard_after , " Paste primary clipboard after selections " ,
paste_primary_clipboard_before , " Paste primary clipboard before selections " ,
2021-07-26 18:07:13 +02:00
indent , " Indent selection " ,
unindent , " Unindent selection " ,
format_selections , " Format selection " ,
join_selections , " Join lines inside selection " ,
keep_selections , " Keep selections matching regex " ,
2021-11-12 01:34:08 +01:00
remove_selections , " Remove selections matching regex " ,
2021-11-23 15:08:05 +01:00
align_selections , " Align selections in column " ,
2021-07-26 18:07:13 +02:00
keep_primary_selection , " Keep primary selection " ,
2021-09-21 17:51:49 +02:00
remove_primary_selection , " Remove primary selection " ,
2021-07-26 18:07:13 +02:00
completion , " Invoke completion popup " ,
hover , " Show docs for item under cursor " ,
toggle_comments , " Comment/uncomment selections " ,
2021-08-06 04:22:01 +02:00
rotate_selections_forward , " Rotate selections forward " ,
rotate_selections_backward , " Rotate selections backward " ,
2021-08-08 06:26:13 +02:00
rotate_selection_contents_forward , " Rotate selection contents forward " ,
rotate_selection_contents_backward , " Rotate selections contents backward " ,
2021-07-26 18:07:13 +02:00
expand_selection , " Expand selection to parent syntax node " ,
jump_forward , " Jump forward on jumplist " ,
jump_backward , " Jump backward on jumplist " ,
2021-12-10 03:46:24 +01:00
save_selection , " Save the current selection to the jumplist " ,
2021-10-23 13:06:40 +02:00
jump_view_right , " Jump to the split to the right " ,
jump_view_left , " Jump to the split to the left " ,
jump_view_up , " Jump to the split above " ,
jump_view_down , " Jump to the split below " ,
2021-07-26 18:07:13 +02:00
rotate_view , " Goto next window " ,
hsplit , " Horizontal bottom split " ,
vsplit , " Vertical right split " ,
wclose , " Close window " ,
2021-11-11 03:32:23 +01:00
wonly , " Current window only " ,
2021-07-26 18:07:13 +02:00
select_register , " Select register " ,
2021-11-15 16:31:20 +01:00
insert_register , " Insert register " ,
2021-07-26 18:07:13 +02:00
align_view_middle , " Align view middle " ,
align_view_top , " Align view top " ,
align_view_center , " Align view center " ,
align_view_bottom , " Align view bottom " ,
scroll_up , " Scroll view up " ,
scroll_down , " Scroll view down " ,
match_brackets , " Goto matching bracket " ,
surround_add , " Surround add " ,
surround_replace , " Surround replace " ,
surround_delete , " Surround delete " ,
select_textobject_around , " Select around object " ,
2021-07-17 16:47:08 +02:00
select_textobject_inner , " Select inside object " ,
2021-08-31 11:13:16 +02:00
shell_pipe , " Pipe selections through shell command " ,
shell_pipe_to , " Pipe selections into shell command, ignoring command output " ,
shell_insert_output , " Insert output of shell command before each selection " ,
shell_append_output , " Append output of shell command after each selection " ,
shell_keep_pipe , " Filter selections with shell predicate " ,
suspend , " Suspend " ,
2021-11-08 16:17:54 +01:00
rename_symbol , " Rename symbol " ,
2021-11-15 16:32:58 +01:00
increment , " Increment " ,
decrement , " Decrement " ,
2021-12-12 13:16:48 +01:00
record_macro , " Record macro " ,
2021-12-12 14:51:57 +01:00
replay_macro , " Replay macro " ,
2021-06-17 13:08:05 +02:00
) ;
}
2021-12-04 15:47:18 +01:00
impl fmt ::Debug for MappableCommand {
2021-06-22 19:04:04 +02:00
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
2021-12-04 15:47:18 +01:00
f . debug_tuple ( " MappableCommand " )
. field ( & self . name ( ) )
. finish ( )
2021-06-22 19:04:04 +02:00
}
}
2021-12-04 15:47:18 +01:00
impl fmt ::Display for MappableCommand {
2021-06-22 19:04:04 +02:00
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
2021-12-04 15:47:18 +01:00
f . write_str ( self . name ( ) )
2021-06-22 19:04:04 +02:00
}
}
2021-12-04 15:47:18 +01:00
impl std ::str ::FromStr for MappableCommand {
2021-06-22 19:04:04 +02:00
type Err = anyhow ::Error ;
fn from_str ( s : & str ) -> Result < Self , Self ::Err > {
2021-12-04 15:47:18 +01:00
if let Some ( suffix ) = s . strip_prefix ( ':' ) {
let mut typable_command = suffix . split ( ' ' ) . into_iter ( ) . map ( | arg | arg . trim ( ) ) ;
let name = typable_command
. next ( )
. ok_or_else ( | | anyhow! ( " Expected typable command name " ) ) ? ;
let args = typable_command
. map ( | s | s . to_owned ( ) )
. collect ::< Vec < String > > ( ) ;
cmd ::TYPABLE_COMMAND_MAP
. get ( name )
. map ( | cmd | MappableCommand ::Typable {
name : cmd . name . to_owned ( ) ,
doc : format ! ( " :{} {:?} " , cmd . name , args ) ,
args ,
} )
. ok_or_else ( | | anyhow! ( " No TypableCommand named '{}' " , s ) )
} else {
MappableCommand ::STATIC_COMMAND_LIST
. iter ( )
. cloned ( )
. find ( | cmd | cmd . name ( ) = = s )
. ok_or_else ( | | anyhow! ( " No command named '{}' " , s ) )
}
2021-06-22 19:04:04 +02:00
}
}
2021-12-04 15:47:18 +01:00
impl < ' de > Deserialize < ' de > for MappableCommand {
2021-06-22 19:04:04 +02:00
fn deserialize < D > ( deserializer : D ) -> Result < Self , D ::Error >
where
D : Deserializer < ' de > ,
{
let s = String ::deserialize ( deserializer ) ? ;
s . parse ( ) . map_err ( de ::Error ::custom )
}
}
2021-12-04 15:47:18 +01:00
impl PartialEq for MappableCommand {
2021-06-22 19:04:04 +02:00
fn eq ( & self , other : & Self ) -> bool {
2021-12-04 15:47:18 +01:00
match ( self , other ) {
(
MappableCommand ::Typable {
name : first_name , ..
} ,
MappableCommand ::Typable {
name : second_name , ..
} ,
) = > first_name = = second_name ,
(
MappableCommand ::Static {
name : first_name , ..
} ,
MappableCommand ::Static {
name : second_name , ..
} ,
) = > first_name = = second_name ,
_ = > false ,
}
2021-06-22 19:04:04 +02:00
}
}
2021-09-13 10:48:12 +02:00
fn no_op ( _cx : & mut Context ) { }
2021-09-04 16:46:43 +02:00
fn move_impl < F > ( cx : & mut Context , move_fn : F , dir : Direction , behaviour : Movement )
where
F : Fn ( RopeSlice , Range , Direction , usize , Movement ) -> Range ,
{
2021-06-08 05:24:27 +02:00
let count = cx . count ( ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-18 07:07:02 +01:00
let text = doc . text ( ) . slice ( .. ) ;
2021-07-24 02:06:14 +02:00
2021-09-04 16:46:43 +02:00
let selection = doc
. selection ( view . id )
. clone ( )
. transform ( | range | move_fn ( text , range , dir , count , behaviour ) ) ;
2021-04-01 03:39:46 +02:00
doc . set_selection ( view . id , selection ) ;
2020-06-07 17:15:39 +02:00
}
2021-09-04 16:46:43 +02:00
use helix_core ::movement ::{ move_horizontally , move_vertically } ;
2021-07-24 02:06:14 +02:00
2021-09-04 16:46:43 +02:00
fn move_char_left ( cx : & mut Context ) {
move_impl ( cx , move_horizontally , Direction ::Backward , Movement ::Move )
2020-06-07 17:15:39 +02:00
}
2021-09-04 16:46:43 +02:00
fn move_char_right ( cx : & mut Context ) {
move_impl ( cx , move_horizontally , Direction ::Forward , Movement ::Move )
}
2021-07-24 02:06:14 +02:00
2021-09-04 16:46:43 +02:00
fn move_line_up ( cx : & mut Context ) {
move_impl ( cx , move_vertically , Direction ::Backward , Movement ::Move )
2020-06-07 17:15:39 +02:00
}
2021-06-17 13:08:05 +02:00
fn move_line_down ( cx : & mut Context ) {
2021-09-04 16:46:43 +02:00
move_impl ( cx , move_vertically , Direction ::Forward , Movement ::Move )
}
2021-07-24 02:06:14 +02:00
2021-09-04 16:46:43 +02:00
fn extend_char_left ( cx : & mut Context ) {
move_impl ( cx , move_horizontally , Direction ::Backward , Movement ::Extend )
}
fn extend_char_right ( cx : & mut Context ) {
move_impl ( cx , move_horizontally , Direction ::Forward , Movement ::Extend )
}
fn extend_line_up ( cx : & mut Context ) {
move_impl ( cx , move_vertically , Direction ::Backward , Movement ::Extend )
}
fn extend_line_down ( cx : & mut Context ) {
move_impl ( cx , move_vertically , Direction ::Forward , Movement ::Extend )
2020-06-07 17:15:39 +02:00
}
2020-09-05 15:01:05 +02:00
2021-09-11 11:31:40 +02:00
fn goto_line_end_impl ( view : & mut View , doc : & mut Document , movement : Movement ) {
2021-07-24 02:06:14 +02:00
let text = doc . text ( ) . slice ( .. ) ;
2020-09-25 16:04:58 +02:00
2021-07-24 02:06:14 +02:00
let selection = doc . selection ( view . id ) . clone ( ) . transform ( | range | {
2021-07-26 17:40:30 +02:00
let line = range . cursor_line ( text ) ;
2021-07-24 16:44:11 +02:00
let line_start = text . line_to_char ( line ) ;
2020-09-25 16:04:58 +02:00
2021-07-24 16:44:11 +02:00
let pos = graphemes ::prev_grapheme_boundary ( text , line_end_char_index ( & text , line ) )
. max ( line_start ) ;
2021-06-19 14:03:14 +02:00
2021-09-07 16:22:39 +02:00
range . put_cursor ( text , pos , movement = = Movement ::Extend )
2021-07-03 17:39:26 +02:00
} ) ;
doc . set_selection ( view . id , selection ) ;
}
2021-09-07 16:22:39 +02:00
fn goto_line_end ( cx : & mut Context ) {
2021-09-11 11:31:40 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
goto_line_end_impl (
view ,
doc ,
if doc . mode = = Mode ::Select {
Movement ::Extend
} else {
Movement ::Move
} ,
)
2021-09-07 16:22:39 +02:00
}
fn extend_to_line_end ( cx : & mut Context ) {
2021-09-11 11:31:40 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
goto_line_end_impl ( view , doc , Movement ::Extend )
2021-09-07 16:22:39 +02:00
}
2021-09-11 11:31:40 +02:00
fn goto_line_end_newline_impl ( view : & mut View , doc : & mut Document , movement : Movement ) {
2021-07-24 02:06:14 +02:00
let text = doc . text ( ) . slice ( .. ) ;
2021-07-03 17:39:26 +02:00
2021-07-24 02:06:14 +02:00
let selection = doc . selection ( view . id ) . clone ( ) . transform ( | range | {
2021-07-26 17:40:30 +02:00
let line = range . cursor_line ( text ) ;
2021-07-24 16:44:11 +02:00
let pos = line_end_char_index ( & text , line ) ;
2021-07-03 17:39:26 +02:00
2021-09-07 16:22:39 +02:00
range . put_cursor ( text , pos , movement = = Movement ::Extend )
2021-05-18 11:27:52 +02:00
} ) ;
2021-04-01 03:39:46 +02:00
doc . set_selection ( view . id , selection ) ;
2020-09-25 16:04:58 +02:00
}
2021-09-07 16:22:39 +02:00
fn goto_line_end_newline ( cx : & mut Context ) {
2021-09-11 11:31:40 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
goto_line_end_newline_impl (
view ,
doc ,
if doc . mode = = Mode ::Select {
Movement ::Extend
} else {
Movement ::Move
} ,
)
2021-09-07 16:22:39 +02:00
}
fn extend_to_line_end_newline ( cx : & mut Context ) {
2021-09-11 11:31:40 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
goto_line_end_newline_impl ( view , doc , Movement ::Extend )
2021-09-07 16:22:39 +02:00
}
2021-09-11 11:31:40 +02:00
fn goto_line_start_impl ( view : & mut View , doc : & mut Document , movement : Movement ) {
2021-07-24 02:06:14 +02:00
let text = doc . text ( ) . slice ( .. ) ;
2020-09-25 16:04:58 +02:00
2021-07-24 02:06:14 +02:00
let selection = doc . selection ( view . id ) . clone ( ) . transform ( | range | {
2021-07-26 17:40:30 +02:00
let line = range . cursor_line ( text ) ;
2020-09-25 16:04:58 +02:00
2021-05-18 11:27:52 +02:00
// adjust to start of the line
let pos = text . line_to_char ( line ) ;
2021-09-07 16:22:39 +02:00
range . put_cursor ( text , pos , movement = = Movement ::Extend )
2021-05-18 11:27:52 +02:00
} ) ;
2021-04-01 03:39:46 +02:00
doc . set_selection ( view . id , selection ) ;
2020-09-25 16:04:58 +02:00
}
2021-09-07 16:22:39 +02:00
fn goto_line_start ( cx : & mut Context ) {
2021-09-11 11:31:40 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
goto_line_start_impl (
view ,
doc ,
if doc . mode = = Mode ::Select {
Movement ::Extend
} else {
Movement ::Move
} ,
)
2021-09-07 16:22:39 +02:00
}
2021-11-01 20:52:47 +01:00
fn goto_next_buffer ( cx : & mut Context ) {
goto_buffer ( cx , Direction ::Forward ) ;
}
fn goto_previous_buffer ( cx : & mut Context ) {
goto_buffer ( cx , Direction ::Backward ) ;
}
fn goto_buffer ( cx : & mut Context , direction : Direction ) {
2021-11-04 05:55:45 +01:00
let current = view! ( cx . editor ) . doc ;
let id = match direction {
Direction ::Forward = > {
let iter = cx . editor . documents . keys ( ) ;
let mut iter = iter . skip_while ( | id | * id ! = & current ) ;
iter . next ( ) ; // skip current item
iter . next ( ) . or_else ( | | cx . editor . documents . keys ( ) . next ( ) )
}
Direction ::Backward = > {
let iter = cx . editor . documents . keys ( ) ;
let mut iter = iter . rev ( ) . skip_while ( | id | * id ! = & current ) ;
iter . next ( ) ; // skip current item
iter . next ( )
. or_else ( | | cx . editor . documents . keys ( ) . rev ( ) . next ( ) )
}
2021-11-01 20:52:47 +01:00
}
2021-11-04 05:55:45 +01:00
. unwrap ( ) ;
let id = * id ;
cx . editor . switch ( id , Action ::Replace ) ;
2021-11-01 20:52:47 +01:00
}
2021-09-07 16:22:39 +02:00
fn extend_to_line_start ( cx : & mut Context ) {
2021-09-11 11:31:40 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
goto_line_start_impl ( view , doc , Movement ::Extend )
2021-09-07 16:22:39 +02:00
}
2021-11-15 16:31:20 +01:00
fn kill_to_line_start ( cx : & mut Context ) {
let ( view , doc ) = current! ( cx . editor ) ;
let text = doc . text ( ) . slice ( .. ) ;
let selection = doc . selection ( view . id ) . clone ( ) . transform ( | range | {
let line = range . cursor_line ( text ) ;
range . put_cursor ( text , text . line_to_char ( line ) , true )
} ) ;
delete_selection_insert_mode ( doc , view , & selection ) ;
}
fn kill_to_line_end ( cx : & mut Context ) {
let ( view , doc ) = current! ( cx . editor ) ;
let text = doc . text ( ) . slice ( .. ) ;
let selection = doc . selection ( view . id ) . clone ( ) . transform ( | range | {
let line = range . cursor_line ( text ) ;
2021-12-06 17:44:04 +01:00
let line_end_pos = line_end_char_index ( & text , line ) ;
let pos = range . cursor ( text ) ;
let mut new_range = range . put_cursor ( text , line_end_pos , true ) ;
// don't want to remove the line separator itself if the cursor doesn't reach the end of line.
if pos ! = line_end_pos {
new_range . head = line_end_pos ;
}
new_range
2021-11-15 16:31:20 +01:00
} ) ;
delete_selection_insert_mode ( doc , view , & selection ) ;
}
2021-07-03 17:39:26 +02:00
fn goto_first_nonwhitespace ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-07-24 02:06:14 +02:00
let text = doc . text ( ) . slice ( .. ) ;
2021-06-08 08:25:55 +02:00
2021-07-24 02:06:14 +02:00
let selection = doc . selection ( view . id ) . clone ( ) . transform ( | range | {
2021-07-26 17:40:30 +02:00
let line = range . cursor_line ( text ) ;
2021-06-08 08:25:55 +02:00
2021-07-24 02:06:14 +02:00
if let Some ( pos ) = find_first_non_whitespace_char ( text . line ( line ) ) {
let pos = pos + text . line_to_char ( line ) ;
2021-07-26 17:40:30 +02:00
range . put_cursor ( text , pos , doc . mode = = Mode ::Select )
2021-06-08 08:25:55 +02:00
} else {
range
}
} ) ;
doc . set_selection ( view . id , selection ) ;
}
2021-11-14 16:16:20 +01:00
fn trim_selections ( cx : & mut Context ) {
let ( view , doc ) = current! ( cx . editor ) ;
let text = doc . text ( ) . slice ( .. ) ;
let ranges : SmallVec < [ Range ; 1 ] > = doc
. selection ( view . id )
. iter ( )
. filter_map ( | range | {
if range . is_empty ( ) | | range . fragment ( text ) . chars ( ) . all ( | ch | ch . is_whitespace ( ) ) {
return None ;
}
let mut start = range . from ( ) ;
let mut end = range . to ( ) ;
start = movement ::skip_while ( text , start , | x | x . is_whitespace ( ) ) . unwrap_or ( start ) ;
end = movement ::backwards_skip_while ( text , end , | x | x . is_whitespace ( ) ) . unwrap_or ( end ) ;
if range . anchor < range . head {
Some ( Range ::new ( start , end ) )
} else {
Some ( Range ::new ( end , start ) )
}
} )
. collect ( ) ;
if ! ranges . is_empty ( ) {
let primary = doc . selection ( view . id ) . primary ( ) ;
let idx = ranges
. iter ( )
. position ( | range | range . overlaps ( & primary ) )
. unwrap_or ( ranges . len ( ) - 1 ) ;
doc . set_selection ( view . id , Selection ::new ( ranges , idx ) ) ;
} else {
collapse_selection ( cx ) ;
keep_primary_selection ( cx ) ;
} ;
}
2021-11-23 15:08:05 +01:00
// align text in selection
fn align_selections ( cx : & mut Context ) {
let align_style = cx . count ( ) ;
if align_style > 3 {
cx . editor . set_error (
" align only accept 1,2,3 as count to set left/center/right align " . to_string ( ) ,
) ;
return ;
}
let ( view , doc ) = current! ( cx . editor ) ;
let text = doc . text ( ) . slice ( .. ) ;
let selection = doc . selection ( view . id ) ;
let mut column_widths = vec! [ ] ;
let mut last_line = text . len_lines ( ) ;
let mut column = 0 ;
// first of all, we need compute all column's width, let use max width of the selections in a column
for sel in selection {
let ( l1 , l2 ) = sel . line_range ( text ) ;
if l1 ! = l2 {
cx . editor
. set_error ( " align cannot work with multi line selections " . to_string ( ) ) ;
return ;
}
// if the selection is not in the same line with last selection, we set the column to 0
column = if l1 ! = last_line { 0 } else { column + 1 } ;
last_line = l1 ;
if column < column_widths . len ( ) {
if sel . to ( ) - sel . from ( ) > column_widths [ column ] {
column_widths [ column ] = sel . to ( ) - sel . from ( ) ;
}
} else {
// a new column, current selection width is the temp width of the column
column_widths . push ( sel . to ( ) - sel . from ( ) ) ;
}
}
last_line = text . len_lines ( ) ;
// once we get the with of each column, we transform each selection with to it's column width based on the align style
let transaction = Transaction ::change_by_selection ( doc . text ( ) , selection , | range | {
let l = range . cursor_line ( text ) ;
column = if l ! = last_line { 0 } else { column + 1 } ;
last_line = l ;
(
range . from ( ) ,
range . to ( ) ,
Some (
align_fragment_to_width ( & range . fragment ( text ) , column_widths [ column ] , align_style )
. into ( ) ,
) ,
)
} ) ;
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
}
fn align_fragment_to_width ( fragment : & str , width : usize , align_style : usize ) -> String {
let trimed = fragment . trim_matches ( | c | c = = ' ' ) ;
let mut s = " " . repeat ( width - trimed . chars ( ) . count ( ) ) ;
match align_style {
1 = > s . insert_str ( 0 , trimed ) , // left align
2 = > s . insert_str ( s . len ( ) / 2 , trimed ) , // center align
3 = > s . push_str ( trimed ) , // right align
n = > unimplemented! ( " {} " , n ) ,
}
s
}
2021-07-04 11:07:58 +02:00
fn goto_window ( cx : & mut Context , align : Align ) {
2021-11-29 02:58:21 +01:00
let count = cx . count ( ) - 1 ;
2021-07-03 17:39:26 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-08-19 06:19:15 +02:00
let height = view . inner_area ( ) . height as usize ;
2021-08-12 03:18:37 +02:00
2021-11-29 02:58:21 +01:00
// respect user given count if any
2021-08-11 06:53:38 +02:00
// - 1 so we have at least one gap in the middle.
// a height of 6 with padding of 3 on each side will keep shifting the view back and forth
// as we type
2021-08-12 03:18:37 +02:00
let scrolloff = cx . editor . config . scrolloff . min ( height . saturating_sub ( 1 ) / 2 ) ;
2021-07-03 17:39:26 +02:00
2021-07-04 11:07:58 +02:00
let last_line = view . last_line ( doc ) ;
2021-07-03 17:39:26 +02:00
2021-07-04 11:07:58 +02:00
let line = match align {
2021-11-29 02:58:21 +01:00
Align ::Top = > ( view . offset . row + scrolloff + count ) ,
Align ::Center = > ( view . offset . row + ( ( last_line - view . offset . row ) / 2 ) ) ,
Align ::Bottom = > last_line . saturating_sub ( scrolloff + count ) ,
2021-07-04 11:07:58 +02:00
}
2021-12-02 05:42:34 +01:00
. max ( view . offset . row + scrolloff )
. min ( last_line . saturating_sub ( scrolloff ) ) ;
2021-07-03 17:39:26 +02:00
let pos = doc . text ( ) . line_to_char ( line ) ;
doc . set_selection ( view . id , Selection ::point ( pos ) ) ;
}
2021-07-04 11:07:58 +02:00
fn goto_window_top ( cx : & mut Context ) {
goto_window ( cx , Align ::Top )
}
2021-07-03 17:39:26 +02:00
2021-12-02 05:46:57 +01:00
fn goto_window_center ( cx : & mut Context ) {
2021-07-04 11:07:58 +02:00
goto_window ( cx , Align ::Center )
}
2021-07-03 17:39:26 +02:00
2021-07-04 11:07:58 +02:00
fn goto_window_bottom ( cx : & mut Context ) {
goto_window ( cx , Align ::Bottom )
2021-07-03 17:39:26 +02:00
}
2021-09-04 16:59:11 +02:00
fn move_word_impl < F > ( cx : & mut Context , move_fn : F )
where
F : Fn ( RopeSlice , Range , usize ) -> Range ,
{
2021-06-08 05:24:27 +02:00
let count = cx . count ( ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-04 06:07:35 +01:00
let text = doc . text ( ) . slice ( .. ) ;
2020-09-24 12:16:35 +02:00
2021-06-11 14:57:07 +02:00
let selection = doc
. selection ( view . id )
2021-07-24 02:06:14 +02:00
. clone ( )
2021-09-04 16:59:11 +02:00
. transform ( | range | move_fn ( text , range , count ) ) ;
2021-04-01 03:39:46 +02:00
doc . set_selection ( view . id , selection ) ;
2020-09-24 12:16:35 +02:00
}
2021-09-04 16:59:11 +02:00
fn move_next_word_start ( cx : & mut Context ) {
move_word_impl ( cx , movement ::move_next_word_start )
}
2020-09-24 12:16:35 +02:00
2021-09-04 16:59:11 +02:00
fn move_prev_word_start ( cx : & mut Context ) {
move_word_impl ( cx , movement ::move_prev_word_start )
2020-09-24 12:16:35 +02:00
}
2021-11-15 16:31:20 +01:00
fn move_prev_word_end ( cx : & mut Context ) {
move_word_impl ( cx , movement ::move_prev_word_end )
}
2021-06-17 13:08:05 +02:00
fn move_next_word_end ( cx : & mut Context ) {
2021-09-04 16:59:11 +02:00
move_word_impl ( cx , movement ::move_next_word_end )
2020-09-24 12:16:35 +02:00
}
2021-06-29 14:54:16 +02:00
fn move_next_long_word_start ( cx : & mut Context ) {
2021-09-04 16:59:11 +02:00
move_word_impl ( cx , movement ::move_next_long_word_start )
2021-06-29 14:54:16 +02:00
}
fn move_prev_long_word_start ( cx : & mut Context ) {
2021-09-04 16:59:11 +02:00
move_word_impl ( cx , movement ::move_prev_long_word_start )
2021-06-29 14:54:16 +02:00
}
fn move_next_long_word_end ( cx : & mut Context ) {
2021-09-04 16:59:11 +02:00
move_word_impl ( cx , movement ::move_next_long_word_end )
2021-06-29 14:54:16 +02:00
}
2021-07-03 17:39:26 +02:00
fn goto_file_start ( cx : & mut Context ) {
2021-07-28 09:55:34 +02:00
if cx . count . is_some ( ) {
goto_line ( cx ) ;
} else {
push_jump ( cx . editor ) ;
let ( view , doc ) = current! ( cx . editor ) ;
2021-10-29 03:07:07 +02:00
let text = doc . text ( ) . slice ( .. ) ;
let selection = doc
. selection ( view . id )
. clone ( )
. transform ( | range | range . put_cursor ( text , 0 , doc . mode = = Mode ::Select ) ) ;
doc . set_selection ( view . id , selection ) ;
2021-07-28 09:55:34 +02:00
}
2020-10-04 23:47:37 +02:00
}
2021-07-03 17:39:26 +02:00
fn goto_file_end ( cx : & mut Context ) {
2021-05-06 10:15:49 +02:00
push_jump ( cx . editor ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-10-29 03:07:07 +02:00
let text = doc . text ( ) . slice ( .. ) ;
let pos = doc . text ( ) . len_chars ( ) ;
let selection = doc
. selection ( view . id )
. clone ( )
. transform ( | range | range . put_cursor ( text , pos , doc . mode = = Mode ::Select ) ) ;
doc . set_selection ( view . id , selection ) ;
2020-10-04 23:47:37 +02:00
}
2021-11-29 02:53:29 +01:00
fn goto_file ( cx : & mut Context ) {
goto_file_impl ( cx , Action ::Replace ) ;
}
fn goto_file_hsplit ( cx : & mut Context ) {
goto_file_impl ( cx , Action ::HorizontalSplit ) ;
}
fn goto_file_vsplit ( cx : & mut Context ) {
goto_file_impl ( cx , Action ::VerticalSplit ) ;
}
fn goto_file_impl ( cx : & mut Context , action : Action ) {
let ( view , doc ) = current_ref! ( cx . editor ) ;
let text = doc . text ( ) ;
let selections = doc . selection ( view . id ) ;
let mut paths : Vec < _ > = selections
. iter ( )
. map ( | r | text . slice ( r . from ( ) .. r . to ( ) ) . to_string ( ) )
. collect ( ) ;
let primary = selections . primary ( ) ;
if selections . len ( ) = = 1 & & primary . to ( ) - primary . from ( ) = = 1 {
let current_word = movement ::move_next_long_word_start (
text . slice ( .. ) ,
movement ::move_prev_long_word_start ( text . slice ( .. ) , primary , 1 ) ,
1 ,
) ;
paths . clear ( ) ;
paths . push (
text . slice ( current_word . from ( ) .. current_word . to ( ) )
. to_string ( ) ,
) ;
}
for sel in paths {
let p = sel . trim ( ) ;
if ! p . is_empty ( ) {
if let Err ( e ) = cx . editor . open ( PathBuf ::from ( p ) , action ) {
cx . editor . set_error ( format! ( " Open file failed: {:?} " , e ) ) ;
}
}
}
}
2021-09-04 16:59:11 +02:00
fn extend_word_impl < F > ( cx : & mut Context , extend_fn : F )
where
F : Fn ( RopeSlice , Range , usize ) -> Range ,
{
2021-06-08 05:24:27 +02:00
let count = cx . count ( ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-04 06:07:35 +01:00
let text = doc . text ( ) . slice ( .. ) ;
2021-07-29 00:57:00 +02:00
let selection = doc . selection ( view . id ) . clone ( ) . transform ( | range | {
2021-09-04 16:59:11 +02:00
let word = extend_fn ( text , range , count ) ;
2021-07-29 00:57:00 +02:00
let pos = word . cursor ( text ) ;
range . put_cursor ( text , pos , true )
2021-02-24 09:29:28 +01:00
} ) ;
2021-04-01 03:39:46 +02:00
doc . set_selection ( view . id , selection ) ;
2021-01-22 08:31:49 +01:00
}
2021-09-04 16:59:11 +02:00
fn extend_next_word_start ( cx : & mut Context ) {
extend_word_impl ( cx , movement ::move_next_word_start )
}
2021-03-04 06:07:35 +01:00
2021-09-04 16:59:11 +02:00
fn extend_prev_word_start ( cx : & mut Context ) {
extend_word_impl ( cx , movement ::move_prev_word_start )
2021-02-12 08:49:24 +01:00
}
2021-06-17 13:08:05 +02:00
fn extend_next_word_end ( cx : & mut Context ) {
2021-09-04 16:59:11 +02:00
extend_word_impl ( cx , movement ::move_next_word_end )
2021-01-22 08:31:49 +01:00
}
2021-09-04 15:30:32 +02:00
fn extend_next_long_word_start ( cx : & mut Context ) {
2021-09-04 16:59:11 +02:00
extend_word_impl ( cx , movement ::move_next_long_word_start )
2021-09-04 15:30:32 +02:00
}
fn extend_prev_long_word_start ( cx : & mut Context ) {
2021-09-04 16:59:11 +02:00
extend_word_impl ( cx , movement ::move_prev_long_word_start )
2021-09-04 15:30:32 +02:00
}
fn extend_next_long_word_end ( cx : & mut Context ) {
2021-09-04 16:59:11 +02:00
extend_word_impl ( cx , movement ::move_next_long_word_end )
2021-09-04 15:30:32 +02:00
}
2021-10-24 15:47:10 +02:00
fn will_find_char < F > ( cx : & mut Context , search_fn : F , inclusive : bool , extend : bool )
2021-03-11 08:14:52 +01:00
where
2021-06-05 16:25:45 +02:00
F : Fn ( RopeSlice , char , usize , usize , bool ) -> Option < usize > + 'static ,
2021-03-11 08:14:52 +01:00
{
2021-03-11 02:44:38 +01:00
// TODO: count is reset to 1 before next key so we move it into the closure here.
// Would be nice to carry over.
2021-06-08 05:24:27 +02:00
let count = cx . count ( ) ;
2021-03-11 02:44:38 +01:00
// need to wait for next key
2021-06-21 00:09:10 +02:00
// TODO: should this be done by grapheme rather than char? For example,
2021-06-21 20:59:03 +02:00
// we can't properly handle the line-ending CRLF case here in terms of char.
2021-03-11 02:44:38 +01:00
cx . on_next_key ( move | cx , event | {
2021-06-11 09:30:27 +02:00
let ch = match event {
KeyEvent {
code : KeyCode ::Enter ,
..
2021-06-21 20:59:03 +02:00
} = >
// TODO: this isn't quite correct when CRLF is involved.
// This hack will work in most cases, since documents don't
// usually mix line endings. But we should fix it eventually
// anyway.
{
2021-11-06 15:52:26 +01:00
doc! ( cx . editor ) . line_ending . as_str ( ) . chars ( ) . next ( ) . unwrap ( )
2021-06-21 20:59:03 +02:00
}
2021-06-11 09:30:27 +02:00
KeyEvent {
code : KeyCode ::Char ( ch ) ,
..
} = > ch ,
_ = > return ,
} ;
2021-10-24 15:47:10 +02:00
find_char_impl ( cx . editor , & search_fn , inclusive , extend , ch , count ) ;
cx . editor . last_motion = Some ( Motion ( Box ::new ( move | editor : & mut Editor | {
find_char_impl ( editor , & search_fn , inclusive , true , ch , 1 ) ;
} ) ) ) ;
} )
}
2021-06-11 09:30:27 +02:00
2021-10-24 15:47:10 +02:00
//
2021-06-11 09:30:27 +02:00
2021-10-24 15:47:10 +02:00
#[ inline ]
fn find_char_impl < F > (
editor : & mut Editor ,
search_fn : & F ,
inclusive : bool ,
extend : bool ,
ch : char ,
count : usize ,
) where
F : Fn ( RopeSlice , char , usize , usize , bool ) -> Option < usize > + 'static ,
{
let ( view , doc ) = current! ( editor ) ;
let text = doc . text ( ) . slice ( .. ) ;
let selection = doc . selection ( view . id ) . clone ( ) . transform ( | range | {
// TODO: use `Range::cursor()` here instead. However, that works in terms of
// graphemes, whereas this function doesn't yet. So we're doing the same logic
// here, but just in terms of chars instead.
let search_start_pos = if range . anchor < range . head {
range . head - 1
} else {
range . head
} ;
search_fn ( text , ch , search_start_pos , count , inclusive ) . map_or ( range , | pos | {
if extend {
range . put_cursor ( text , pos , true )
} else {
Range ::point ( range . cursor ( text ) ) . put_cursor ( text , pos , true )
}
} )
} ) ;
doc . set_selection ( view . id , selection ) ;
2021-03-11 02:44:38 +01:00
}
2021-07-24 16:44:11 +02:00
fn find_next_char_impl (
text : RopeSlice ,
ch : char ,
pos : usize ,
n : usize ,
inclusive : bool ,
) -> Option < usize > {
let pos = ( pos + 1 ) . min ( text . len_chars ( ) ) ;
if inclusive {
search ::find_nth_next ( text , ch , pos , n )
} else {
2021-10-24 15:47:10 +02:00
let n = match text . get_char ( pos ) {
Some ( next_ch ) if next_ch = = ch = > n + 1 ,
_ = > n ,
} ;
2021-07-24 16:44:11 +02:00
search ::find_nth_next ( text , ch , pos , n ) . map ( | n | n . saturating_sub ( 1 ) )
}
}
fn find_prev_char_impl (
text : RopeSlice ,
ch : char ,
pos : usize ,
n : usize ,
inclusive : bool ,
) -> Option < usize > {
if inclusive {
search ::find_nth_prev ( text , ch , pos , n )
} else {
2021-10-24 15:47:10 +02:00
let n = match text . get_char ( pos . saturating_sub ( 1 ) ) {
Some ( next_ch ) if next_ch = = ch = > n + 1 ,
_ = > n ,
} ;
2021-07-24 16:44:11 +02:00
search ::find_nth_prev ( text , ch , pos , n ) . map ( | n | ( n + 1 ) . min ( text . len_chars ( ) ) )
}
}
2021-06-17 13:08:05 +02:00
fn find_till_char ( cx : & mut Context ) {
2021-10-24 15:47:10 +02:00
will_find_char ( cx , find_next_char_impl , false , false )
2021-03-11 08:14:52 +01:00
}
2021-06-17 13:08:05 +02:00
fn find_next_char ( cx : & mut Context ) {
2021-10-24 15:47:10 +02:00
will_find_char ( cx , find_next_char_impl , true , false )
2021-03-11 08:14:52 +01:00
}
2021-06-17 13:08:05 +02:00
fn extend_till_char ( cx : & mut Context ) {
2021-10-24 15:47:10 +02:00
will_find_char ( cx , find_next_char_impl , false , true )
2021-03-11 08:14:52 +01:00
}
2021-06-17 13:08:05 +02:00
fn extend_next_char ( cx : & mut Context ) {
2021-10-24 15:47:10 +02:00
will_find_char ( cx , find_next_char_impl , true , true )
2021-03-11 08:14:52 +01:00
}
2021-06-17 13:08:05 +02:00
fn till_prev_char ( cx : & mut Context ) {
2021-10-24 15:47:10 +02:00
will_find_char ( cx , find_prev_char_impl , false , false )
2021-03-11 08:14:52 +01:00
}
2021-06-17 13:08:05 +02:00
fn find_prev_char ( cx : & mut Context ) {
2021-10-24 15:47:10 +02:00
will_find_char ( cx , find_prev_char_impl , true , false )
2021-03-11 08:14:52 +01:00
}
2021-06-17 13:08:05 +02:00
fn extend_till_prev_char ( cx : & mut Context ) {
2021-10-24 15:47:10 +02:00
will_find_char ( cx , find_prev_char_impl , false , true )
2021-03-11 08:14:52 +01:00
}
2021-06-17 13:08:05 +02:00
fn extend_prev_char ( cx : & mut Context ) {
2021-10-24 15:47:10 +02:00
will_find_char ( cx , find_prev_char_impl , true , true )
}
fn repeat_last_motion ( cx : & mut Context ) {
let last_motion = cx . editor . last_motion . take ( ) ;
if let Some ( m ) = & last_motion {
m . run ( cx . editor ) ;
cx . editor . last_motion = last_motion ;
}
2021-03-11 08:14:52 +01:00
}
2021-06-17 13:08:05 +02:00
fn replace ( cx : & mut Context ) {
2021-06-21 20:08:05 +02:00
let mut buf = [ 0 u8 ; 4 ] ; // To hold utf8 encoded char.
2021-03-19 10:01:08 +01:00
// need to wait for next key
cx . on_next_key ( move | cx , event | {
2021-06-21 20:08:05 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-06-08 08:44:26 +02:00
let ch = match event {
KeyEvent {
code : KeyCode ::Char ( ch ) ,
..
2021-06-21 20:08:05 +02:00
} = > Some ( & ch . encode_utf8 ( & mut buf [ .. ] ) [ .. ] ) ,
2021-06-08 08:44:26 +02:00
KeyEvent {
code : KeyCode ::Enter ,
..
2021-06-21 20:08:05 +02:00
} = > Some ( doc . line_ending . as_str ( ) ) ,
2021-06-08 08:44:26 +02:00
_ = > None ,
} ;
2021-07-29 00:57:00 +02:00
let selection = doc . selection ( view . id ) ;
2021-06-07 21:28:04 +02:00
2021-07-01 18:51:24 +02:00
if let Some ( ch ) = ch {
2021-07-29 00:57:00 +02:00
let transaction = Transaction ::change_by_selection ( doc . text ( ) , selection , | range | {
2021-07-01 18:51:24 +02:00
if ! range . is_empty ( ) {
let text : String =
RopeGraphemes ::new ( doc . text ( ) . slice ( range . from ( ) .. range . to ( ) ) )
. map ( | g | {
let cow : Cow < str > = g . into ( ) ;
if str_is_line_ending ( & cow ) {
cow
} else {
ch . into ( )
}
} )
. collect ( ) ;
( range . from ( ) , range . to ( ) , Some ( text . into ( ) ) )
} else {
// No change.
( range . from ( ) , range . to ( ) , None )
}
} ) ;
2021-03-19 10:01:08 +01:00
2021-04-01 03:39:46 +02:00
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
2021-03-19 10:01:08 +01:00
}
} )
}
2021-09-04 17:29:08 +02:00
fn switch_case_impl < F > ( cx : & mut Context , change_fn : F )
where
F : Fn ( Cow < str > ) -> Tendril ,
{
2021-07-16 18:12:59 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-07-29 00:57:00 +02:00
let selection = doc . selection ( view . id ) ;
let transaction = Transaction ::change_by_selection ( doc . text ( ) , selection , | range | {
2021-09-04 17:29:08 +02:00
let text : Tendril = change_fn ( range . fragment ( doc . text ( ) . slice ( .. ) ) ) ;
( range . from ( ) , range . to ( ) , Some ( text ) )
} ) ;
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
}
fn switch_case ( cx : & mut Context ) {
switch_case_impl ( cx , | string | {
string
2021-07-17 19:57:58 +02:00
. chars ( )
. flat_map ( | ch | {
if ch . is_lowercase ( ) {
ch . to_uppercase ( ) . collect ( )
} else if ch . is_uppercase ( ) {
ch . to_lowercase ( ) . collect ( )
} else {
vec! [ ch ]
}
} )
2021-09-04 17:29:08 +02:00
. collect ( )
2021-07-17 19:57:58 +02:00
} ) ;
2021-07-16 18:12:59 +02:00
}
fn switch_to_uppercase ( cx : & mut Context ) {
2021-09-04 17:29:08 +02:00
switch_case_impl ( cx , | string | string . to_uppercase ( ) . into ( ) ) ;
2021-07-16 18:12:59 +02:00
}
fn switch_to_lowercase ( cx : & mut Context ) {
2021-09-04 17:29:08 +02:00
switch_case_impl ( cx , | string | string . to_lowercase ( ) . into ( ) ) ;
2021-07-16 18:12:59 +02:00
}
2021-08-10 07:35:20 +02:00
pub fn scroll ( cx : & mut Context , offset : usize , direction : Direction ) {
2021-03-04 08:13:26 +01:00
use Direction ::* ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-08-20 06:42:01 +02:00
let range = doc . selection ( view . id ) . primary ( ) ;
let text = doc . text ( ) . slice ( .. ) ;
let cursor = coords_at_pos ( text , range . cursor ( text ) ) ;
2021-08-20 03:30:45 +02:00
let doc_last_line = doc . text ( ) . len_lines ( ) . saturating_sub ( 1 ) ;
2021-03-04 08:13:26 +01:00
2021-03-23 09:47:40 +01:00
let last_line = view . last_line ( doc ) ;
2021-03-04 08:13:26 +01:00
2021-08-19 05:52:07 +02:00
if direction = = Backward & & view . offset . row = = 0
2021-03-04 08:13:26 +01:00
| | direction = = Forward & & last_line = = doc_last_line
{
2020-10-07 07:15:32 +02:00
return ;
}
2021-08-19 06:19:15 +02:00
let height = view . inner_area ( ) . height ;
let scrolloff = cx . editor . config . scrolloff . min ( height as usize / 2 ) ;
2021-03-04 08:13:26 +01:00
2021-08-19 05:52:07 +02:00
view . offset . row = match direction {
Forward = > view . offset . row + offset ,
Backward = > view . offset . row . saturating_sub ( offset ) ,
2020-10-07 06:58:13 +02:00
}
2021-03-04 08:13:26 +01:00
. min ( doc_last_line ) ;
2021-04-14 07:27:47 +02:00
// recalculate last line
let last_line = view . last_line ( doc ) ;
2021-03-04 08:13:26 +01:00
// clamp into viewport
2021-06-02 06:19:21 +02:00
let line = cursor
. row
2021-08-19 05:52:07 +02:00
. max ( view . offset . row + scrolloff )
2021-06-04 04:36:28 +02:00
. min ( last_line . saturating_sub ( scrolloff ) ) ;
2021-03-04 08:13:26 +01:00
2021-08-20 06:42:01 +02:00
let head = pos_at_coords ( text , Position ::new ( line , cursor . col ) , true ) ; // this func will properly truncate to line end
let anchor = if doc . mode = = Mode ::Select {
range . anchor
} else {
head
} ;
2021-03-23 09:47:40 +01:00
2021-04-14 07:27:47 +02:00
// TODO: only manipulate main selection
2021-08-20 06:42:01 +02:00
doc . set_selection ( view . id , Selection ::single ( anchor , head ) ) ;
2020-10-05 17:18:29 +02:00
}
2021-06-17 13:08:05 +02:00
fn page_up ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let view = view! ( cx . editor ) ;
2021-08-19 06:19:15 +02:00
let offset = view . inner_area ( ) . height as usize ;
2021-03-23 09:47:40 +01:00
scroll ( cx , offset , Direction ::Backward ) ;
2021-03-04 08:13:26 +01:00
}
2020-10-06 10:32:30 +02:00
2021-06-17 13:08:05 +02:00
fn page_down ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let view = view! ( cx . editor ) ;
2021-08-19 06:19:15 +02:00
let offset = view . inner_area ( ) . height as usize ;
2021-03-23 09:47:40 +01:00
scroll ( cx , offset , Direction ::Forward ) ;
2020-10-05 17:18:29 +02:00
}
2021-06-17 13:08:05 +02:00
fn half_page_up ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let view = view! ( cx . editor ) ;
2021-08-19 06:19:15 +02:00
let offset = view . inner_area ( ) . height as usize / 2 ;
2021-03-23 09:47:40 +01:00
scroll ( cx , offset , Direction ::Backward ) ;
2020-10-05 17:18:29 +02:00
}
2021-06-17 13:08:05 +02:00
fn half_page_down ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let view = view! ( cx . editor ) ;
2021-08-19 06:19:15 +02:00
let offset = view . inner_area ( ) . height as usize / 2 ;
2021-03-23 09:47:40 +01:00
scroll ( cx , offset , Direction ::Forward ) ;
2020-10-05 17:18:29 +02:00
}
2020-09-07 10:08:28 +02:00
2021-08-04 04:55:01 +02:00
fn copy_selection_on_line ( cx : & mut Context , direction : Direction ) {
2021-07-17 14:09:46 +02:00
let count = cx . count ( ) ;
let ( view , doc ) = current! ( cx . editor ) ;
let text = doc . text ( ) . slice ( .. ) ;
2021-08-04 04:55:01 +02:00
let selection = doc . selection ( view . id ) ;
let mut ranges = SmallVec ::with_capacity ( selection . ranges ( ) . len ( ) * ( count + 1 ) ) ;
ranges . extend_from_slice ( selection . ranges ( ) ) ;
let mut primary_index = 0 ;
for range in selection . iter ( ) {
let is_primary = * range = = selection . primary ( ) ;
let head_pos = coords_at_pos ( text , range . head ) ;
let anchor_pos = coords_at_pos ( text , range . anchor ) ;
let height = std ::cmp ::max ( head_pos . row , anchor_pos . row )
- std ::cmp ::min ( head_pos . row , anchor_pos . row )
+ 1 ;
if is_primary {
primary_index = ranges . len ( ) ;
}
ranges . push ( * range ) ;
let mut sels = 0 ;
let mut i = 0 ;
while sels < count {
let offset = ( i + 1 ) * height ;
let anchor_row = match direction {
Direction ::Forward = > anchor_pos . row + offset ,
Direction ::Backward = > anchor_pos . row . saturating_sub ( offset ) ,
} ;
let head_row = match direction {
Direction ::Forward = > head_pos . row + offset ,
Direction ::Backward = > head_pos . row . saturating_sub ( offset ) ,
} ;
if anchor_row > = text . len_lines ( ) | | head_row > = text . len_lines ( ) {
break ;
}
let anchor = pos_at_coords ( text , Position ::new ( anchor_row , anchor_pos . col ) , true ) ;
let head = pos_at_coords ( text , Position ::new ( head_row , head_pos . col ) , true ) ;
// skip lines that are too short
if coords_at_pos ( text , anchor ) . col = = anchor_pos . col
& & coords_at_pos ( text , head ) . col = = head_pos . col
{
if is_primary {
primary_index = ranges . len ( ) ;
}
ranges . push ( Range ::new ( anchor , head ) ) ;
sels + = 1 ;
}
i + = 1 ;
}
2021-07-17 14:09:46 +02:00
}
2021-08-04 04:55:01 +02:00
let selection = Selection ::new ( ranges , primary_index ) ;
doc . set_selection ( view . id , selection ) ;
}
fn copy_selection_on_prev_line ( cx : & mut Context ) {
copy_selection_on_line ( cx , Direction ::Backward )
2021-07-17 14:09:46 +02:00
}
fn copy_selection_on_next_line ( cx : & mut Context ) {
2021-08-04 04:55:01 +02:00
copy_selection_on_line ( cx , Direction ::Forward )
2021-07-17 14:09:46 +02:00
}
2021-06-17 13:08:05 +02:00
fn select_all ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-02-09 07:39:26 +01:00
2021-07-09 01:45:19 +02:00
let end = doc . text ( ) . len_chars ( ) ;
2021-04-01 03:39:46 +02:00
doc . set_selection ( view . id , Selection ::single ( 0 , end ) )
2021-02-09 07:39:26 +01:00
}
2021-06-17 13:08:05 +02:00
fn select_regex ( cx : & mut Context ) {
2021-09-08 07:52:09 +02:00
let reg = cx . register . unwrap_or ( '/' ) ;
2021-09-21 18:03:12 +02:00
let prompt = ui ::regex_prompt (
cx ,
" select: " . into ( ) ,
Some ( reg ) ,
2021-11-04 04:26:01 +01:00
| _input : & str | Vec ::new ( ) ,
2021-09-21 18:03:12 +02:00
move | view , doc , regex , event | {
if event ! = PromptEvent ::Update {
return ;
}
let text = doc . text ( ) . slice ( .. ) ;
if let Some ( selection ) =
selection ::select_on_matches ( text , doc . selection ( view . id ) , & regex )
{
doc . set_selection ( view . id , selection ) ;
}
} ,
) ;
2021-02-21 11:04:31 +01:00
cx . push_layer ( Box ::new ( prompt ) ) ;
2021-01-22 09:13:14 +01:00
}
2021-06-17 13:08:05 +02:00
fn split_selection ( cx : & mut Context ) {
2021-09-08 07:52:09 +02:00
let reg = cx . register . unwrap_or ( '/' ) ;
2021-09-21 18:03:12 +02:00
let prompt = ui ::regex_prompt (
cx ,
" split: " . into ( ) ,
Some ( reg ) ,
2021-11-04 04:26:01 +01:00
| _input : & str | Vec ::new ( ) ,
2021-09-21 18:03:12 +02:00
move | view , doc , regex , event | {
if event ! = PromptEvent ::Update {
return ;
}
let text = doc . text ( ) . slice ( .. ) ;
let selection = selection ::split_on_matches ( text , doc . selection ( view . id ) , & regex ) ;
doc . set_selection ( view . id , selection ) ;
} ,
) ;
2020-12-14 06:12:54 +01:00
2021-02-21 11:04:31 +01:00
cx . push_layer ( Box ::new ( prompt ) ) ;
2020-12-14 06:12:54 +01:00
}
2021-06-17 13:08:05 +02:00
fn split_selection_on_newline ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-02-18 10:34:22 +01:00
let text = doc . text ( ) . slice ( .. ) ;
2020-09-28 18:01:27 +02:00
// only compile the regex once
2020-10-01 05:23:07 +02:00
#[ allow(clippy::trivial_regex) ]
2021-06-21 20:22:07 +02:00
static REGEX : Lazy < Regex > =
Lazy ::new ( | | Regex ::new ( r "\r\n|[\n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{2029}]" ) . unwrap ( ) ) ;
2021-04-01 03:39:46 +02:00
let selection = selection ::split_on_matches ( text , doc . selection ( view . id ) , & REGEX ) ;
doc . set_selection ( view . id , selection ) ;
2020-09-28 18:01:27 +02:00
}
2021-11-06 09:33:30 +01:00
fn search_impl (
doc : & mut Document ,
view : & mut View ,
contents : & str ,
regex : & Regex ,
movement : Movement ,
direction : Direction ,
2021-11-09 03:11:45 +01:00
scrolloff : usize ,
2021-11-06 09:33:30 +01:00
) {
2021-07-26 17:50:26 +02:00
let text = doc . text ( ) . slice ( .. ) ;
2021-04-14 08:21:49 +02:00
let selection = doc . selection ( view . id ) ;
2021-07-29 00:57:00 +02:00
2021-11-06 09:33:30 +01:00
// Get the right side of the primary block cursor for forward search, or the
//grapheme before the start of the selection for reverse search.
let start = match direction {
Direction ::Forward = > text . char_to_byte ( graphemes ::next_grapheme_boundary (
text ,
selection . primary ( ) . to ( ) ,
) ) ,
Direction ::Backward = > text . char_to_byte ( graphemes ::prev_grapheme_boundary (
text ,
selection . primary ( ) . from ( ) ,
) ) ,
} ;
//A regex::Match returns byte-positions in the str. In the case where we
//do a reverse search and wraparound to the end, we don't need to search
//the text before the current cursor position for matches, but by slicing
//it out, we need to add it back to the position of the selection.
let mut offset = 0 ;
2021-02-12 10:10:05 +01:00
2021-04-08 08:58:20 +02:00
// use find_at to find the next match after the cursor, loop around the end
2021-06-08 06:20:15 +02:00
// Careful, `Regex` uses `bytes` as offsets, not character indices!
2021-11-06 09:33:30 +01:00
let mat = match direction {
Direction ::Forward = > regex
. find_at ( contents , start )
. or_else ( | | regex . find ( contents ) ) ,
Direction ::Backward = > regex . find_iter ( & contents [ .. start ] ) . last ( ) . or_else ( | | {
offset = start ;
regex . find_iter ( & contents [ start .. ] ) . last ( )
} ) ,
} ;
2021-05-08 08:36:27 +02:00
// TODO: message on wraparound
2021-04-08 08:58:20 +02:00
if let Some ( mat ) = mat {
2021-11-06 09:33:30 +01:00
let start = text . byte_to_char ( mat . start ( ) + offset ) ;
let end = text . byte_to_char ( mat . end ( ) + offset ) ;
2021-04-09 17:21:13 +02:00
2021-05-16 11:58:43 +02:00
if end = = 0 {
// skip empty matches that don't make sense
return ;
}
2021-11-06 09:41:30 +01:00
// Determine range direction based on the primary range
let primary = selection . primary ( ) ;
let range = if primary . head < primary . anchor {
Range ::new ( end , start )
} else {
Range ::new ( start , end )
} ;
2021-11-06 09:33:30 +01:00
let selection = match movement {
2021-11-06 09:41:30 +01:00
Movement ::Extend = > selection . clone ( ) . push ( range ) ,
Movement ::Move = > selection . clone ( ) . replace ( selection . primary_index ( ) , range ) ,
2021-04-09 17:21:13 +02:00
} ;
2021-04-14 08:21:49 +02:00
doc . set_selection ( view . id , selection ) ;
2021-11-09 03:11:45 +01:00
if view . is_cursor_in_view ( doc , 0 ) {
view . ensure_cursor_in_view ( doc , scrolloff ) ;
} else {
align_view ( doc , view , Align ::Center )
}
2021-02-12 10:10:05 +01:00
} ;
}
2021-11-04 04:26:01 +01:00
fn search_completions ( cx : & mut Context , reg : Option < char > ) -> Vec < String > {
let mut items = reg
. and_then ( | reg | cx . editor . registers . get ( reg ) )
. map_or ( Vec ::new ( ) , | reg | reg . read ( ) . iter ( ) . take ( 200 ) . collect ( ) ) ;
items . sort_unstable ( ) ;
items . dedup ( ) ;
items . into_iter ( ) . cloned ( ) . collect ( )
}
2021-02-12 10:10:05 +01:00
// TODO: use one function for search vs extend
2021-06-17 13:08:05 +02:00
fn search ( cx : & mut Context ) {
2021-11-06 09:33:30 +01:00
searcher ( cx , Direction ::Forward )
}
fn rsearch ( cx : & mut Context ) {
searcher ( cx , Direction ::Backward )
}
// TODO: use one function for search vs extend
fn searcher ( cx : & mut Context , direction : Direction ) {
2021-09-08 07:52:09 +02:00
let reg = cx . register . unwrap_or ( '/' ) ;
2021-11-09 03:11:45 +01:00
let scrolloff = cx . editor . config . scrolloff ;
2021-07-01 20:57:12 +02:00
let ( _ , doc ) = current! ( cx . editor ) ;
2021-02-12 10:10:05 +01:00
// TODO: could probably share with select_on_matches?
// HAXX: sadly we can't avoid allocating a single string for the whole buffer since we can't
// feed chunks into the regex yet
let contents = doc . text ( ) . slice ( .. ) . to_string ( ) ;
2021-11-04 04:26:01 +01:00
let completions = search_completions ( cx , Some ( reg ) ) ;
2021-02-12 10:10:05 +01:00
2021-09-21 18:03:12 +02:00
let prompt = ui ::regex_prompt (
cx ,
" search: " . into ( ) ,
Some ( reg ) ,
2021-11-04 04:26:01 +01:00
move | input : & str | {
completions
. iter ( )
. filter ( | comp | comp . starts_with ( input ) )
. map ( | comp | ( 0 .. , std ::borrow ::Cow ::Owned ( comp . clone ( ) ) ) )
. collect ( )
} ,
2021-09-21 18:03:12 +02:00
move | view , doc , regex , event | {
if event ! = PromptEvent ::Update {
return ;
}
2021-11-09 03:11:45 +01:00
search_impl (
doc ,
view ,
& contents ,
& regex ,
Movement ::Move ,
direction ,
scrolloff ,
) ;
2021-09-21 18:03:12 +02:00
} ,
) ;
2021-02-12 10:10:05 +01:00
2021-02-21 11:04:31 +01:00
cx . push_layer ( Box ::new ( prompt ) ) ;
2021-02-12 10:10:05 +01:00
}
2021-11-06 09:33:30 +01:00
fn search_next_or_prev_impl ( cx : & mut Context , movement : Movement , direction : Direction ) {
2021-11-09 03:11:45 +01:00
let scrolloff = cx . editor . config . scrolloff ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-09-08 07:52:09 +02:00
let registers = & cx . editor . registers ;
2021-08-20 04:14:57 +02:00
if let Some ( query ) = registers . read ( '/' ) {
2021-09-08 07:52:09 +02:00
let query = query . last ( ) . unwrap ( ) ;
2021-02-12 10:10:05 +01:00
let contents = doc . text ( ) . slice ( .. ) . to_string ( ) ;
2021-09-20 06:45:07 +02:00
let case_insensitive = if cx . editor . config . smart_case {
! query . chars ( ) . any ( char ::is_uppercase )
} else {
false
} ;
if let Ok ( regex ) = RegexBuilder ::new ( query )
. case_insensitive ( case_insensitive )
. build ( )
{
2021-11-09 03:11:45 +01:00
search_impl ( doc , view , & contents , & regex , movement , direction , scrolloff ) ;
2021-09-17 10:22:17 +02:00
} else {
// get around warning `mutable_borrow_reservation_conflict`
// which will be a hard error in the future
// see: https://github.com/rust-lang/rust/issues/59159
let query = query . clone ( ) ;
cx . editor . set_error ( format! ( " Invalid regex: {} " , query ) ) ;
}
2021-02-12 10:10:05 +01:00
}
}
2021-06-17 13:08:05 +02:00
fn search_next ( cx : & mut Context ) {
2021-11-06 09:33:30 +01:00
search_next_or_prev_impl ( cx , Movement ::Move , Direction ::Forward ) ;
2021-04-09 17:21:13 +02:00
}
2021-11-06 09:33:30 +01:00
fn search_prev ( cx : & mut Context ) {
search_next_or_prev_impl ( cx , Movement ::Move , Direction ::Backward ) ;
}
2021-06-17 13:08:05 +02:00
fn extend_search_next ( cx : & mut Context ) {
2021-11-06 09:33:30 +01:00
search_next_or_prev_impl ( cx , Movement ::Extend , Direction ::Forward ) ;
}
fn extend_search_prev ( cx : & mut Context ) {
search_next_or_prev_impl ( cx , Movement ::Extend , Direction ::Backward ) ;
2021-04-09 17:21:13 +02:00
}
2021-06-17 13:08:05 +02:00
fn search_selection ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-02-22 07:14:02 +01:00
let contents = doc . text ( ) . slice ( .. ) ;
2021-04-01 03:39:46 +02:00
let query = doc . selection ( view . id ) . primary ( ) . fragment ( contents ) ;
2021-02-22 07:14:02 +01:00
let regex = regex ::escape ( & query ) ;
2021-09-08 07:52:09 +02:00
cx . editor . registers . get_mut ( '/' ) . push ( regex ) ;
2021-09-08 07:58:11 +02:00
let msg = format! ( " register ' {} ' set to ' {} ' " , '\\' , query ) ;
cx . editor . set_status ( msg ) ;
2021-02-22 07:14:02 +01:00
}
2021-09-21 18:03:12 +02:00
fn global_search ( cx : & mut Context ) {
let ( all_matches_sx , all_matches_rx ) =
tokio ::sync ::mpsc ::unbounded_channel ::< ( usize , PathBuf ) > ( ) ;
2021-09-24 03:27:16 +02:00
let smart_case = cx . editor . config . smart_case ;
2021-11-20 15:23:36 +01:00
let file_picker_config = cx . editor . config . file_picker . clone ( ) ;
2021-11-04 04:26:01 +01:00
let completions = search_completions ( cx , None ) ;
2021-09-21 18:03:12 +02:00
let prompt = ui ::regex_prompt (
cx ,
2021-11-12 17:34:49 +01:00
" global-search: " . into ( ) ,
2021-09-21 18:03:12 +02:00
None ,
2021-11-04 04:26:01 +01:00
move | input : & str | {
completions
. iter ( )
. filter ( | comp | comp . starts_with ( input ) )
. map ( | comp | ( 0 .. , std ::borrow ::Cow ::Owned ( comp . clone ( ) ) ) )
. collect ( )
} ,
2021-09-21 18:03:12 +02:00
move | _view , _doc , regex , event | {
if event ! = PromptEvent ::Validate {
return ;
}
2021-09-24 03:27:16 +02:00
if let Ok ( matcher ) = RegexMatcherBuilder ::new ( )
. case_smart ( smart_case )
. build ( regex . as_str ( ) )
{
2021-09-21 18:03:12 +02:00
let searcher = SearcherBuilder ::new ( )
. binary_detection ( BinaryDetection ::quit ( b '\x00' ) )
. build ( ) ;
let search_root = std ::env ::current_dir ( )
. expect ( " Global search error: Failed to get current dir " ) ;
2021-11-20 15:23:36 +01:00
WalkBuilder ::new ( search_root )
. hidden ( file_picker_config . hidden )
. parents ( file_picker_config . parents )
. ignore ( file_picker_config . ignore )
. git_ignore ( file_picker_config . git_ignore )
. git_global ( file_picker_config . git_global )
. git_exclude ( file_picker_config . git_exclude )
. max_depth ( file_picker_config . max_depth )
. build_parallel ( )
. run ( | | {
let mut searcher_cl = searcher . clone ( ) ;
let matcher_cl = matcher . clone ( ) ;
let all_matches_sx_cl = all_matches_sx . clone ( ) ;
Box ::new ( move | dent : Result < DirEntry , ignore ::Error > | -> WalkState {
let dent = match dent {
Ok ( dent ) = > dent ,
Err ( _ ) = > return WalkState ::Continue ,
} ;
match dent . file_type ( ) {
Some ( fi ) = > {
if ! fi . is_file ( ) {
return WalkState ::Continue ;
}
2021-09-21 18:03:12 +02:00
}
2021-11-20 15:23:36 +01:00
None = > return WalkState ::Continue ,
2021-09-21 18:03:12 +02:00
}
2021-11-20 15:23:36 +01:00
let result_sink = sinks ::UTF8 ( | line_num , _ | {
match all_matches_sx_cl
. send ( ( line_num as usize - 1 , dent . path ( ) . to_path_buf ( ) ) )
{
Ok ( _ ) = > Ok ( true ) ,
Err ( _ ) = > Ok ( false ) ,
}
} ) ;
let result =
searcher_cl . search_path ( & matcher_cl , dent . path ( ) , result_sink ) ;
if let Err ( err ) = result {
log ::error! (
" Global search error: {}, {} " ,
dent . path ( ) . display ( ) ,
err
) ;
2021-09-21 18:03:12 +02:00
}
2021-11-20 15:23:36 +01:00
WalkState ::Continue
} )
} ) ;
2021-09-21 18:03:12 +02:00
} else {
// Otherwise do nothing
// log::warn!("Global Search Invalid Pattern")
}
} ,
) ;
cx . push_layer ( Box ::new ( prompt ) ) ;
2021-10-08 04:08:10 +02:00
let current_path = doc_mut! ( cx . editor ) . path ( ) . cloned ( ) ;
2021-09-21 18:03:12 +02:00
let show_picker = async move {
let all_matches : Vec < ( usize , PathBuf ) > =
UnboundedReceiverStream ::new ( all_matches_rx ) . collect ( ) . await ;
let call : job ::Callback =
Box ::new ( move | editor : & mut Editor , compositor : & mut Compositor | {
if all_matches . is_empty ( ) {
editor . set_status ( " No matches found " . to_string ( ) ) ;
return ;
}
2021-10-08 04:08:10 +02:00
2021-09-21 18:03:12 +02:00
let picker = FilePicker ::new (
all_matches ,
2021-10-03 01:41:52 +02:00
move | ( _line_num , path ) | {
2021-10-08 04:08:10 +02:00
let relative_path = helix_core ::path ::get_relative_path ( path )
2021-10-03 01:41:52 +02:00
. to_str ( )
. unwrap ( )
2021-10-08 04:08:10 +02:00
. to_owned ( ) ;
if current_path . as_ref ( ) . map ( | p | p = = path ) . unwrap_or ( false ) {
format! ( " {} (*) " , relative_path ) . into ( )
} else {
relative_path . into ( )
}
2021-10-03 01:41:52 +02:00
} ,
2021-09-21 18:03:12 +02:00
move | editor : & mut Editor , ( line_num , path ) , action | {
match editor . open ( path . into ( ) , action ) {
Ok ( _ ) = > { }
Err ( e ) = > {
editor . set_error ( format! (
" Failed to open file '{}': {} " ,
path . display ( ) ,
e
) ) ;
return ;
}
}
let line_num = * line_num ;
let ( view , doc ) = current! ( editor ) ;
let text = doc . text ( ) ;
let start = text . line_to_char ( line_num ) ;
let end = text . line_to_char ( ( line_num + 1 ) . min ( text . len_lines ( ) ) ) ;
doc . set_selection ( view . id , Selection ::single ( start , end ) ) ;
align_view ( doc , view , Align ::Center ) ;
} ,
| _editor , ( line_num , path ) | Some ( ( path . clone ( ) , Some ( ( * line_num , * line_num ) ) ) ) ,
) ;
compositor . push ( Box ::new ( picker ) ) ;
} ) ;
Ok ( call )
} ;
cx . jobs . callback ( show_picker ) ;
}
2021-06-17 13:08:05 +02:00
fn extend_line ( cx : & mut Context ) {
2021-06-08 05:24:27 +02:00
let count = cx . count ( ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-01 06:41:02 +01:00
let text = doc . text ( ) ;
2021-07-29 00:57:00 +02:00
let range = doc . selection ( view . id ) . primary ( ) ;
2021-03-01 06:41:02 +01:00
2021-07-20 21:40:58 +02:00
let ( start_line , end_line ) = range . line_range ( text . slice ( .. ) ) ;
2021-07-17 20:28:20 +02:00
let start = text . line_to_char ( start_line ) ;
2021-07-20 21:40:58 +02:00
let mut end = text . line_to_char ( ( end_line + count ) . min ( text . len_lines ( ) ) ) ;
2021-06-23 04:07:19 +02:00
2021-07-20 02:44:18 +02:00
if range . from ( ) = = start & & range . to ( ) = = end {
2021-07-20 21:40:58 +02:00
end = text . line_to_char ( ( end_line + count + 1 ) . min ( text . len_lines ( ) ) ) ;
2021-06-23 04:07:19 +02:00
}
2021-03-01 06:41:02 +01:00
2021-04-01 03:39:46 +02:00
doc . set_selection ( view . id , Selection ::single ( start , end ) ) ;
2021-03-01 06:41:02 +01:00
}
2020-10-07 11:31:04 +02:00
2021-07-05 03:12:34 +02:00
fn extend_to_line_bounds ( cx : & mut Context ) {
let ( view , doc ) = current! ( cx . editor ) ;
2021-07-06 05:27:49 +02:00
doc . set_selection (
view . id ,
doc . selection ( view . id ) . clone ( ) . transform ( | range | {
let text = doc . text ( ) ;
2021-07-05 03:12:34 +02:00
2021-07-20 21:40:58 +02:00
let ( start_line , end_line ) = range . line_range ( text . slice ( .. ) ) ;
2021-07-20 02:44:18 +02:00
let start = text . line_to_char ( start_line ) ;
2021-07-20 21:40:58 +02:00
let end = text . line_to_char ( ( end_line + 1 ) . min ( text . len_lines ( ) ) ) ;
2021-07-20 02:44:18 +02:00
if range . anchor < = range . head {
2021-07-06 05:27:49 +02:00
Range ::new ( start , end )
} else {
Range ::new ( end , start )
}
} ) ,
) ;
2021-07-05 03:12:34 +02:00
}
2021-11-24 08:46:40 +01:00
enum Operation {
Delete ,
Change ,
}
fn delete_selection_impl ( cx : & mut Context , op : Operation ) {
let ( view , doc ) = current! ( cx . editor ) ;
2021-07-01 18:51:24 +02:00
let text = doc . text ( ) . slice ( .. ) ;
2021-11-24 08:46:40 +01:00
let selection = doc . selection ( view . id ) ;
2021-04-07 08:40:15 +02:00
2021-11-24 08:46:40 +01:00
if cx . register ! = Some ( '_' ) {
// first yank the selection
let values : Vec < String > = selection . fragments ( text ) . map ( Cow ::into_owned ) . collect ( ) ;
let reg_name = cx . register . unwrap_or ( '"' ) ;
let registers = & mut cx . editor . registers ;
let reg = registers . get_mut ( reg_name ) ;
reg . write ( values ) ;
} ;
2021-04-07 08:40:15 +02:00
// then delete
2021-07-29 00:57:00 +02:00
let transaction = Transaction ::change_by_selection ( doc . text ( ) , selection , | range | {
2021-07-01 18:51:24 +02:00
( range . from ( ) , range . to ( ) , None )
} ) ;
2021-11-24 08:46:40 +01:00
doc . apply ( & transaction , view . id ) ;
match op {
Operation ::Delete = > {
doc . append_changes_to_history ( view . id ) ;
// exit select mode, if currently in select mode
exit_select_mode ( cx ) ;
}
Operation ::Change = > {
enter_insert_mode ( doc ) ;
}
}
2020-12-21 05:42:47 +01:00
}
2021-11-15 16:31:20 +01:00
#[ inline ]
fn delete_selection_insert_mode ( doc : & mut Document , view : & View , selection : & Selection ) {
let view_id = view . id ;
// then delete
let transaction = Transaction ::change_by_selection ( doc . text ( ) , selection , | range | {
( range . from ( ) , range . to ( ) , None )
} ) ;
doc . apply ( & transaction , view_id ) ;
}
2021-06-17 13:08:05 +02:00
fn delete_selection ( cx : & mut Context ) {
2021-11-24 08:46:40 +01:00
delete_selection_impl ( cx , Operation ::Delete ) ;
}
2021-06-04 03:30:18 +02:00
2021-11-24 08:46:40 +01:00
fn delete_selection_noyank ( cx : & mut Context ) {
cx . register = Some ( '_' ) ;
delete_selection_impl ( cx , Operation ::Delete ) ;
2020-09-28 18:00:35 +02:00
}
2021-06-17 13:08:05 +02:00
fn change_selection ( cx : & mut Context ) {
2021-11-24 08:46:40 +01:00
delete_selection_impl ( cx , Operation ::Change ) ;
}
fn change_selection_noyank ( cx : & mut Context ) {
cx . register = Some ( '_' ) ;
delete_selection_impl ( cx , Operation ::Change ) ;
2020-09-28 18:00:35 +02:00
}
2021-06-17 13:08:05 +02:00
fn collapse_selection ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-07-24 02:06:14 +02:00
let text = doc . text ( ) . slice ( .. ) ;
2020-10-23 05:06:33 +02:00
2021-07-24 02:06:14 +02:00
let selection = doc . selection ( view . id ) . clone ( ) . transform ( | range | {
2021-07-26 17:40:30 +02:00
let pos = range . cursor ( text ) ;
2021-07-24 02:06:14 +02:00
Range ::new ( pos , pos )
} ) ;
2021-04-01 03:39:46 +02:00
doc . set_selection ( view . id , selection ) ;
2020-10-01 11:44:46 +02:00
}
2021-06-17 13:08:05 +02:00
fn flip_selections ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2020-10-23 05:06:33 +02:00
2021-02-21 11:04:31 +01:00
let selection = doc
2021-04-01 03:39:46 +02:00
. selection ( view . id )
2021-07-24 02:06:14 +02:00
. clone ( )
2020-10-23 05:06:33 +02:00
. transform ( | range | Range ::new ( range . head , range . anchor ) ) ;
2021-04-01 03:39:46 +02:00
doc . set_selection ( view . id , selection ) ;
2020-10-09 09:58:43 +02:00
}
2021-02-21 11:04:31 +01:00
fn enter_insert_mode ( doc : & mut Document ) {
doc . mode = Mode ::Insert ;
2020-10-06 07:44:18 +02:00
}
2020-12-21 05:42:47 +01:00
2020-09-07 10:08:28 +02:00
// inserts at the start of each selection
2021-06-17 13:08:05 +02:00
fn insert_mode ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-23 09:47:40 +01:00
enter_insert_mode ( doc ) ;
2020-09-07 10:08:28 +02:00
2021-02-21 11:04:31 +01:00
let selection = doc
2021-04-01 03:39:46 +02:00
. selection ( view . id )
2021-07-24 02:06:14 +02:00
. clone ( )
2020-10-23 05:06:33 +02:00
. transform ( | range | Range ::new ( range . to ( ) , range . from ( ) ) ) ;
2021-04-01 03:39:46 +02:00
doc . set_selection ( view . id , selection ) ;
2020-09-07 10:08:28 +02:00
}
// inserts at the end of each selection
2021-06-17 13:08:05 +02:00
fn append_mode ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-23 09:47:40 +01:00
enter_insert_mode ( doc ) ;
2021-01-21 09:00:08 +01:00
doc . restore_cursor = true ;
2021-02-18 10:34:22 +01:00
let text = doc . text ( ) . slice ( .. ) ;
2021-05-15 03:46:59 +02:00
2021-07-23 23:27:12 +02:00
// Make sure there's room at the end of the document if the last
// selection butts up against it.
2021-05-15 03:46:59 +02:00
let end = text . len_chars ( ) ;
2021-07-29 00:57:00 +02:00
let last_range = doc . selection ( view . id ) . iter ( ) . last ( ) . unwrap ( ) ;
2021-07-23 23:27:12 +02:00
if ! last_range . is_empty ( ) & & last_range . head = = end {
2021-05-15 03:46:59 +02:00
let transaction = Transaction ::change (
doc . text ( ) ,
2021-12-18 06:57:49 +01:00
[ ( end , end , Some ( doc . line_ending . as_str ( ) . into ( ) ) ) ] . into_iter ( ) ,
2021-05-15 03:46:59 +02:00
) ;
doc . apply ( & transaction , view . id ) ;
}
2021-07-29 00:57:00 +02:00
let selection = doc . selection ( view . id ) . clone ( ) . transform ( | range | {
2021-07-24 02:06:14 +02:00
Range ::new (
range . from ( ) ,
graphemes ::next_grapheme_boundary ( doc . text ( ) . slice ( .. ) , range . to ( ) ) ,
)
} ) ;
2021-04-01 03:39:46 +02:00
doc . set_selection ( view . id , selection ) ;
2020-09-05 15:01:05 +02:00
}
2021-11-17 14:30:11 +01:00
pub mod cmd {
2021-05-07 10:08:07 +02:00
use super ::* ;
use std ::collections ::HashMap ;
use helix_view ::editor ::Action ;
2021-05-07 10:19:45 +02:00
use ui ::completers ::{ self , Completer } ;
2021-05-07 10:08:07 +02:00
#[ derive(Clone) ]
2021-06-17 13:08:05 +02:00
pub struct TypableCommand {
2021-05-07 10:08:07 +02:00
pub name : & 'static str ,
2021-10-03 04:41:41 +02:00
pub aliases : & 'static [ & 'static str ] ,
2021-05-07 10:08:07 +02:00
pub doc : & 'static str ,
// params, flags, helper, completer
2021-12-12 13:13:33 +01:00
pub fun : fn ( & mut compositor ::Context , & [ Cow < str > ] , PromptEvent ) -> anyhow ::Result < ( ) > ,
2021-05-07 10:19:45 +02:00
pub completer : Option < Completer > ,
2021-05-07 10:08:07 +02:00
}
2021-06-26 19:50:44 +02:00
fn quit (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
_args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-05-07 10:08:07 +02:00
// last view and we have unsaved changes
2021-06-26 19:50:44 +02:00
if cx . editor . tree . views ( ) . count ( ) = = 1 {
buffers_remaining_impl ( cx . editor ) ?
2021-05-07 10:08:07 +02:00
}
2021-06-26 19:50:44 +02:00
helix-term/commands: implement buffer-close (bc, bclose) (#1035)
* helix-view/view: impl method to remove document from jumps
* helix-view/editor: impl close_document
* helix-view/editor: remove close_buffer argument from `close`
According to archseer, this was never implemented or used properly. Now
that we have a proper "buffer close" function, we can get rid of this.
* helix-term/commands: implement buffer-close (bc, bclose)
This behaves the same as Kakoune's `delete-buffer` / `db` command:
* With 3 files opened by the user with `:o ab`, `:o cd`, and `:o ef`:
* `buffer-close` once closes `ef` and switches to `cd`
* `buffer-close` again closes `cd` and switches to `ab`
* `buffer-close` again closes `ab` and switches to a scratch buffer
* With 3 files opened from the command line with `hx -- ab cd ef`:
* `buffer-close` once closes `ab` and switches to `cd`
* `buffer-close` again closes `cd` and switches to `ef`
* `buffer-close` again closes `ef` and switches to a scratch buffer
* With 1 file opened (`ab`):
* `buffer-close` once closes `ab` and switches to a scratch buffer
* `buffer-close` again closes the scratch buffer and switches to a new
scratch buffer
* helix-term/commands: implement buffer-close! (bclose!, bc!)
Namely, if you have a document open in multiple splits, all the splits
will be closed at the same time, leaving only splits without that
document focused (or a scratch buffer if they were all focused on that
buffer).
* helix-view/tree: reset focus if Tree is empty
2021-11-15 16:30:45 +01:00
cx . editor . close ( view! ( cx . editor ) . id ) ;
2021-06-26 19:50:44 +02:00
Ok ( ( ) )
2021-05-07 10:08:07 +02:00
}
2021-06-26 19:50:44 +02:00
fn force_quit (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
_args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
helix-term/commands: implement buffer-close (bc, bclose) (#1035)
* helix-view/view: impl method to remove document from jumps
* helix-view/editor: impl close_document
* helix-view/editor: remove close_buffer argument from `close`
According to archseer, this was never implemented or used properly. Now
that we have a proper "buffer close" function, we can get rid of this.
* helix-term/commands: implement buffer-close (bc, bclose)
This behaves the same as Kakoune's `delete-buffer` / `db` command:
* With 3 files opened by the user with `:o ab`, `:o cd`, and `:o ef`:
* `buffer-close` once closes `ef` and switches to `cd`
* `buffer-close` again closes `cd` and switches to `ab`
* `buffer-close` again closes `ab` and switches to a scratch buffer
* With 3 files opened from the command line with `hx -- ab cd ef`:
* `buffer-close` once closes `ab` and switches to `cd`
* `buffer-close` again closes `cd` and switches to `ef`
* `buffer-close` again closes `ef` and switches to a scratch buffer
* With 1 file opened (`ab`):
* `buffer-close` once closes `ab` and switches to a scratch buffer
* `buffer-close` again closes the scratch buffer and switches to a new
scratch buffer
* helix-term/commands: implement buffer-close! (bclose!, bc!)
Namely, if you have a document open in multiple splits, all the splits
will be closed at the same time, leaving only splits without that
document focused (or a scratch buffer if they were all focused on that
buffer).
* helix-view/tree: reset focus if Tree is empty
2021-11-15 16:30:45 +01:00
cx . editor . close ( view! ( cx . editor ) . id ) ;
2021-06-26 19:50:44 +02:00
Ok ( ( ) )
2021-05-07 10:08:07 +02:00
}
2021-06-26 19:50:44 +02:00
fn open (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-12-12 13:13:33 +01:00
ensure! ( ! args . is_empty ( ) , " wrong argument count " ) ;
for arg in args {
let _ = cx . editor . open ( arg . as_ref ( ) . into ( ) , Action ::Replace ) ? ;
}
2021-07-11 09:35:57 +02:00
Ok ( ( ) )
2021-05-07 10:08:07 +02:00
}
helix-term/commands: implement buffer-close (bc, bclose) (#1035)
* helix-view/view: impl method to remove document from jumps
* helix-view/editor: impl close_document
* helix-view/editor: remove close_buffer argument from `close`
According to archseer, this was never implemented or used properly. Now
that we have a proper "buffer close" function, we can get rid of this.
* helix-term/commands: implement buffer-close (bc, bclose)
This behaves the same as Kakoune's `delete-buffer` / `db` command:
* With 3 files opened by the user with `:o ab`, `:o cd`, and `:o ef`:
* `buffer-close` once closes `ef` and switches to `cd`
* `buffer-close` again closes `cd` and switches to `ab`
* `buffer-close` again closes `ab` and switches to a scratch buffer
* With 3 files opened from the command line with `hx -- ab cd ef`:
* `buffer-close` once closes `ab` and switches to `cd`
* `buffer-close` again closes `cd` and switches to `ef`
* `buffer-close` again closes `ef` and switches to a scratch buffer
* With 1 file opened (`ab`):
* `buffer-close` once closes `ab` and switches to a scratch buffer
* `buffer-close` again closes the scratch buffer and switches to a new
scratch buffer
* helix-term/commands: implement buffer-close! (bclose!, bc!)
Namely, if you have a document open in multiple splits, all the splits
will be closed at the same time, leaving only splits without that
document focused (or a scratch buffer if they were all focused on that
buffer).
* helix-view/tree: reset focus if Tree is empty
2021-11-15 16:30:45 +01:00
fn buffer_close (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
_args : & [ Cow < str > ] ,
helix-term/commands: implement buffer-close (bc, bclose) (#1035)
* helix-view/view: impl method to remove document from jumps
* helix-view/editor: impl close_document
* helix-view/editor: remove close_buffer argument from `close`
According to archseer, this was never implemented or used properly. Now
that we have a proper "buffer close" function, we can get rid of this.
* helix-term/commands: implement buffer-close (bc, bclose)
This behaves the same as Kakoune's `delete-buffer` / `db` command:
* With 3 files opened by the user with `:o ab`, `:o cd`, and `:o ef`:
* `buffer-close` once closes `ef` and switches to `cd`
* `buffer-close` again closes `cd` and switches to `ab`
* `buffer-close` again closes `ab` and switches to a scratch buffer
* With 3 files opened from the command line with `hx -- ab cd ef`:
* `buffer-close` once closes `ab` and switches to `cd`
* `buffer-close` again closes `cd` and switches to `ef`
* `buffer-close` again closes `ef` and switches to a scratch buffer
* With 1 file opened (`ab`):
* `buffer-close` once closes `ab` and switches to a scratch buffer
* `buffer-close` again closes the scratch buffer and switches to a new
scratch buffer
* helix-term/commands: implement buffer-close! (bclose!, bc!)
Namely, if you have a document open in multiple splits, all the splits
will be closed at the same time, leaving only splits without that
document focused (or a scratch buffer if they were all focused on that
buffer).
* helix-view/tree: reset focus if Tree is empty
2021-11-15 16:30:45 +01:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
let view = view! ( cx . editor ) ;
let doc_id = view . doc ;
cx . editor . close_document ( doc_id , false ) ? ;
Ok ( ( ) )
}
fn force_buffer_close (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
_args : & [ Cow < str > ] ,
helix-term/commands: implement buffer-close (bc, bclose) (#1035)
* helix-view/view: impl method to remove document from jumps
* helix-view/editor: impl close_document
* helix-view/editor: remove close_buffer argument from `close`
According to archseer, this was never implemented or used properly. Now
that we have a proper "buffer close" function, we can get rid of this.
* helix-term/commands: implement buffer-close (bc, bclose)
This behaves the same as Kakoune's `delete-buffer` / `db` command:
* With 3 files opened by the user with `:o ab`, `:o cd`, and `:o ef`:
* `buffer-close` once closes `ef` and switches to `cd`
* `buffer-close` again closes `cd` and switches to `ab`
* `buffer-close` again closes `ab` and switches to a scratch buffer
* With 3 files opened from the command line with `hx -- ab cd ef`:
* `buffer-close` once closes `ab` and switches to `cd`
* `buffer-close` again closes `cd` and switches to `ef`
* `buffer-close` again closes `ef` and switches to a scratch buffer
* With 1 file opened (`ab`):
* `buffer-close` once closes `ab` and switches to a scratch buffer
* `buffer-close` again closes the scratch buffer and switches to a new
scratch buffer
* helix-term/commands: implement buffer-close! (bclose!, bc!)
Namely, if you have a document open in multiple splits, all the splits
will be closed at the same time, leaving only splits without that
document focused (or a scratch buffer if they were all focused on that
buffer).
* helix-view/tree: reset focus if Tree is empty
2021-11-15 16:30:45 +01:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
let view = view! ( cx . editor ) ;
let doc_id = view . doc ;
cx . editor . close_document ( doc_id , true ) ? ;
Ok ( ( ) )
}
2021-12-12 13:13:33 +01:00
fn write_impl ( cx : & mut compositor ::Context , path : Option < & Cow < str > > ) -> anyhow ::Result < ( ) > {
2021-06-28 14:48:38 +02:00
let jobs = & mut cx . jobs ;
2021-07-01 20:57:12 +02:00
let ( _ , doc ) = current! ( cx . editor ) ;
2021-06-13 07:31:25 +02:00
2021-11-28 02:19:54 +01:00
if let Some ( ref path ) = path {
2021-12-12 13:13:33 +01:00
doc . set_path ( Some ( path . as_ref ( ) . as_ref ( ) ) )
2021-10-28 03:23:46 +02:00
. context ( " invalid filepath " ) ? ;
2021-06-01 07:47:21 +02:00
}
2021-05-07 10:08:07 +02:00
if doc . path ( ) . is_none ( ) {
2021-06-26 19:50:44 +02:00
bail! ( " cannot write a buffer without a filename " ) ;
2021-05-07 10:08:07 +02:00
}
2021-06-30 05:36:33 +02:00
let fmt = doc . auto_format ( ) . map ( | fmt | {
let shared = fmt . shared ( ) ;
2021-06-30 05:52:09 +02:00
let callback = make_format_callback (
doc . id ( ) ,
doc . version ( ) ,
Modified ::SetUnmodified ,
shared . clone ( ) ,
) ;
2021-06-30 05:36:33 +02:00
jobs . callback ( callback ) ;
shared
} ) ;
2021-08-19 04:36:56 +02:00
let future = doc . format_and_save ( fmt ) ;
cx . jobs . add ( Job ::new ( future ) . wait_before_exiting ( ) ) ;
2021-11-28 02:19:54 +01:00
if path . is_some ( ) {
let id = doc . id ( ) ;
let _ = cx . editor . refresh_language_server ( id ) ;
}
2021-08-19 04:36:56 +02:00
Ok ( ( ) )
2021-06-13 07:31:25 +02:00
}
2021-06-26 19:50:44 +02:00
fn write (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-08-19 04:36:56 +02:00
write_impl ( cx , args . first ( ) )
2021-05-07 10:08:07 +02:00
}
2021-05-12 10:24:55 +02:00
2021-06-26 19:50:44 +02:00
fn new_file (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
_args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-06-22 21:49:55 +02:00
cx . editor . new_file ( Action ::Replace ) ;
2021-06-26 19:50:44 +02:00
Ok ( ( ) )
2021-05-07 10:08:07 +02:00
}
2021-06-26 19:50:44 +02:00
fn format (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
_args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-06-23 20:35:39 +02:00
let ( _ , doc ) = current! ( cx . editor ) ;
2021-05-12 10:24:55 +02:00
2021-06-23 20:35:39 +02:00
if let Some ( format ) = doc . format ( ) {
2021-06-30 05:52:09 +02:00
let callback =
make_format_callback ( doc . id ( ) , doc . version ( ) , Modified ::LeaveModified , format ) ;
2021-06-28 14:48:38 +02:00
cx . jobs . callback ( callback ) ;
2021-06-23 20:35:39 +02:00
}
2021-05-12 10:24:55 +02:00
2021-06-26 19:50:44 +02:00
Ok ( ( ) )
}
fn set_indent_style (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-06-14 03:09:22 +02:00
use IndentStyle ::* ;
2021-06-15 04:43:24 +02:00
// If no argument, report current indent style.
if args . is_empty ( ) {
2021-11-06 15:52:26 +01:00
let style = doc! ( cx . editor ) . indent_style ;
2021-06-22 21:49:55 +02:00
cx . editor . set_status ( match style {
2021-06-15 04:43:24 +02:00
Tabs = > " tabs " . into ( ) ,
Spaces ( 1 ) = > " 1 space " . into ( ) ,
Spaces ( n ) if ( 2 ..= 8 ) . contains ( & n ) = > format! ( " {} spaces " , n ) ,
_ = > " error " . into ( ) , // Shouldn't happen.
} ) ;
2021-06-26 19:50:44 +02:00
return Ok ( ( ) ) ;
2021-06-15 04:43:24 +02:00
}
// Attempt to parse argument as an indent style.
2021-06-14 03:09:22 +02:00
let style = match args . get ( 0 ) {
Some ( arg ) if " tabs " . starts_with ( & arg . to_lowercase ( ) ) = > Some ( Tabs ) ,
2021-12-12 13:13:33 +01:00
Some ( Cow ::Borrowed ( " 0 " ) ) = > Some ( Tabs ) ,
2021-06-14 22:22:25 +02:00
Some ( arg ) = > arg
. parse ::< u8 > ( )
. ok ( )
. filter ( | n | ( 1 ..= 8 ) . contains ( n ) )
. map ( Spaces ) ,
2021-06-14 03:09:22 +02:00
_ = > None ,
} ;
2021-07-11 09:35:57 +02:00
let style = style . context ( " invalid indent style " ) ? ;
let doc = doc_mut! ( cx . editor ) ;
doc . indent_style = style ;
2021-06-26 19:50:44 +02:00
2021-07-11 09:35:57 +02:00
Ok ( ( ) )
2021-06-14 03:09:22 +02:00
}
2021-06-21 21:36:01 +02:00
/// Sets or reports the current document's line ending setting.
2021-06-26 19:50:44 +02:00
fn set_line_ending (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-06-21 21:36:01 +02:00
use LineEnding ::* ;
// If no argument, report current line ending setting.
if args . is_empty ( ) {
2021-11-06 15:52:26 +01:00
let line_ending = doc! ( cx . editor ) . line_ending ;
2021-06-22 21:49:55 +02:00
cx . editor . set_status ( match line_ending {
2021-06-21 21:36:01 +02:00
Crlf = > " crlf " . into ( ) ,
LF = > " line feed " . into ( ) ,
FF = > " form feed " . into ( ) ,
CR = > " carriage return " . into ( ) ,
Nel = > " next line " . into ( ) ,
// These should never be a document's default line ending.
VT | LS | PS = > " error " . into ( ) ,
} ) ;
2021-06-26 19:50:44 +02:00
return Ok ( ( ) ) ;
2021-06-21 21:36:01 +02:00
}
2021-08-20 04:02:28 +02:00
let arg = args
. get ( 0 )
. context ( " argument missing " ) ?
. to_ascii_lowercase ( ) ;
2021-08-20 03:30:45 +02:00
2021-06-21 21:36:01 +02:00
// Attempt to parse argument as a line ending.
2021-08-20 03:30:45 +02:00
let line_ending = match arg {
2021-06-21 21:36:01 +02:00
// We check for CR first because it shares a common prefix with CRLF.
2021-08-20 03:30:45 +02:00
arg if arg . starts_with ( " cr " ) = > CR ,
arg if arg . starts_with ( " crlf " ) = > Crlf ,
arg if arg . starts_with ( " lf " ) = > LF ,
arg if arg . starts_with ( " ff " ) = > FF ,
arg if arg . starts_with ( " nel " ) = > Nel ,
2021-08-20 04:02:28 +02:00
_ = > bail! ( " invalid line ending " ) ,
2021-06-21 21:36:01 +02:00
} ;
2021-07-11 09:35:57 +02:00
doc_mut! ( cx . editor ) . line_ending = line_ending ;
Ok ( ( ) )
2021-06-21 21:36:01 +02:00
}
2021-06-26 19:50:44 +02:00
fn earlier (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-11-14 16:16:47 +01:00
let uk = args . join ( " " ) . parse ::< UndoKind > ( ) . map_err ( | s | anyhow! ( s ) ) ? ;
2021-06-26 19:50:44 +02:00
2021-06-22 21:49:55 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-11-11 14:32:44 +01:00
let success = doc . earlier ( view . id , uk ) ;
if ! success {
cx . editor . set_status ( " Already at oldest change " . to_owned ( ) ) ;
}
2021-06-26 19:50:44 +02:00
Ok ( ( ) )
2021-06-11 15:06:13 +02:00
}
2021-06-26 19:50:44 +02:00
fn later (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-11-14 16:16:47 +01:00
let uk = args . join ( " " ) . parse ::< UndoKind > ( ) . map_err ( | s | anyhow! ( s ) ) ? ;
2021-06-22 21:49:55 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-11-11 14:32:44 +01:00
let success = doc . later ( view . id , uk ) ;
if ! success {
cx . editor . set_status ( " Already at newest change " . to_owned ( ) ) ;
}
2021-06-26 19:50:44 +02:00
Ok ( ( ) )
2021-06-11 15:06:13 +02:00
}
2021-06-26 19:50:44 +02:00
fn write_quit (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-08-19 04:36:56 +02:00
write_impl ( cx , args . first ( ) ) ? ;
2021-06-26 19:50:44 +02:00
quit ( cx , & [ ] , event )
2021-06-13 07:31:25 +02:00
}
2021-06-26 19:50:44 +02:00
fn force_write_quit (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-08-19 04:36:56 +02:00
write_impl ( cx , args . first ( ) ) ? ;
2021-06-26 19:50:44 +02:00
force_quit ( cx , & [ ] , event )
2021-06-13 07:31:25 +02:00
}
2021-06-26 19:50:44 +02:00
/// Results an error if there are modified buffers remaining and sets editor error,
/// otherwise returns `Ok(())`
2021-09-17 07:42:14 +02:00
pub ( super ) fn buffers_remaining_impl ( editor : & mut Editor ) -> anyhow ::Result < ( ) > {
2021-06-13 11:14:53 +02:00
let modified : Vec < _ > = editor
2021-06-13 11:00:55 +02:00
. documents ( )
. filter ( | doc | doc . is_modified ( ) )
. map ( | doc | {
doc . relative_path ( )
2021-06-18 08:19:34 +02:00
. map ( | path | path . to_string_lossy ( ) . to_string ( ) )
2021-11-11 04:22:15 +01:00
. unwrap_or_else ( | | SCRATCH_BUFFER_NAME . into ( ) )
2021-06-13 11:00:55 +02:00
} )
2021-06-13 11:14:53 +02:00
. collect ( ) ;
if ! modified . is_empty ( ) {
2021-06-26 19:50:44 +02:00
bail! (
2021-06-13 11:14:53 +02:00
" {} unsaved buffer(s) remaining: {:?} " ,
modified . len ( ) ,
modified
) ;
}
2021-06-26 19:50:44 +02:00
Ok ( ( ) )
2021-06-13 11:00:55 +02:00
}
2021-06-15 06:29:03 +02:00
fn write_all_impl (
2021-08-19 04:36:56 +02:00
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
_args : & [ Cow < str > ] ,
2021-07-01 20:57:12 +02:00
_event : PromptEvent ,
2021-06-15 06:29:03 +02:00
quit : bool ,
force : bool ,
2021-06-26 19:50:44 +02:00
) -> anyhow ::Result < ( ) > {
2021-06-13 11:00:55 +02:00
let mut errors = String ::new ( ) ;
2021-06-13 07:31:25 +02:00
2021-06-13 11:00:55 +02:00
// save all documents
2021-11-04 05:43:45 +01:00
for doc in & mut cx . editor . documents . values_mut ( ) {
2021-06-13 11:00:55 +02:00
if doc . path ( ) . is_none ( ) {
errors . push_str ( " cannot write a buffer without a filename \n " ) ;
continue ;
}
2021-07-01 21:08:00 +02:00
// TODO: handle error.
2021-08-19 04:36:56 +02:00
let handle = doc . save ( ) ;
cx . jobs . add ( Job ::new ( handle ) . wait_before_exiting ( ) ) ;
2021-06-13 11:00:55 +02:00
}
if quit {
2021-06-26 19:50:44 +02:00
if ! force {
2021-08-19 04:36:56 +02:00
buffers_remaining_impl ( cx . editor ) ? ;
2021-06-13 07:31:25 +02:00
}
2021-06-13 11:00:55 +02:00
// close all views
2021-08-19 04:36:56 +02:00
let views : Vec < _ > = cx . editor . tree . views ( ) . map ( | ( view , _ ) | view . id ) . collect ( ) ;
2021-06-13 11:00:55 +02:00
for view_id in views {
helix-term/commands: implement buffer-close (bc, bclose) (#1035)
* helix-view/view: impl method to remove document from jumps
* helix-view/editor: impl close_document
* helix-view/editor: remove close_buffer argument from `close`
According to archseer, this was never implemented or used properly. Now
that we have a proper "buffer close" function, we can get rid of this.
* helix-term/commands: implement buffer-close (bc, bclose)
This behaves the same as Kakoune's `delete-buffer` / `db` command:
* With 3 files opened by the user with `:o ab`, `:o cd`, and `:o ef`:
* `buffer-close` once closes `ef` and switches to `cd`
* `buffer-close` again closes `cd` and switches to `ab`
* `buffer-close` again closes `ab` and switches to a scratch buffer
* With 3 files opened from the command line with `hx -- ab cd ef`:
* `buffer-close` once closes `ab` and switches to `cd`
* `buffer-close` again closes `cd` and switches to `ef`
* `buffer-close` again closes `ef` and switches to a scratch buffer
* With 1 file opened (`ab`):
* `buffer-close` once closes `ab` and switches to a scratch buffer
* `buffer-close` again closes the scratch buffer and switches to a new
scratch buffer
* helix-term/commands: implement buffer-close! (bclose!, bc!)
Namely, if you have a document open in multiple splits, all the splits
will be closed at the same time, leaving only splits without that
document focused (or a scratch buffer if they were all focused on that
buffer).
* helix-view/tree: reset focus if Tree is empty
2021-11-15 16:30:45 +01:00
cx . editor . close ( view_id ) ;
2021-06-13 11:00:55 +02:00
}
2021-06-13 07:31:25 +02:00
}
2021-06-26 19:50:44 +02:00
bail! ( errors )
2021-06-13 07:31:25 +02:00
}
2021-06-26 19:50:44 +02:00
fn write_all (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-08-19 04:36:56 +02:00
write_all_impl ( cx , args , event , false , false )
2021-06-13 07:31:25 +02:00
}
2021-06-26 19:50:44 +02:00
fn write_all_quit (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-08-19 04:36:56 +02:00
write_all_impl ( cx , args , event , true , false )
2021-06-13 07:31:25 +02:00
}
2021-06-26 19:50:44 +02:00
fn force_write_all_quit (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-08-19 04:36:56 +02:00
write_all_impl ( cx , args , event , true , true )
2021-06-13 07:31:25 +02:00
}
2021-06-26 19:50:44 +02:00
fn quit_all_impl (
editor : & mut Editor ,
2021-12-12 13:13:33 +01:00
_args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
_event : PromptEvent ,
force : bool ,
) -> anyhow ::Result < ( ) > {
if ! force {
buffers_remaining_impl ( editor ) ? ;
2021-06-13 11:06:06 +02:00
}
// close all views
let views : Vec < _ > = editor . tree . views ( ) . map ( | ( view , _ ) | view . id ) . collect ( ) ;
for view_id in views {
helix-term/commands: implement buffer-close (bc, bclose) (#1035)
* helix-view/view: impl method to remove document from jumps
* helix-view/editor: impl close_document
* helix-view/editor: remove close_buffer argument from `close`
According to archseer, this was never implemented or used properly. Now
that we have a proper "buffer close" function, we can get rid of this.
* helix-term/commands: implement buffer-close (bc, bclose)
This behaves the same as Kakoune's `delete-buffer` / `db` command:
* With 3 files opened by the user with `:o ab`, `:o cd`, and `:o ef`:
* `buffer-close` once closes `ef` and switches to `cd`
* `buffer-close` again closes `cd` and switches to `ab`
* `buffer-close` again closes `ab` and switches to a scratch buffer
* With 3 files opened from the command line with `hx -- ab cd ef`:
* `buffer-close` once closes `ab` and switches to `cd`
* `buffer-close` again closes `cd` and switches to `ef`
* `buffer-close` again closes `ef` and switches to a scratch buffer
* With 1 file opened (`ab`):
* `buffer-close` once closes `ab` and switches to a scratch buffer
* `buffer-close` again closes the scratch buffer and switches to a new
scratch buffer
* helix-term/commands: implement buffer-close! (bclose!, bc!)
Namely, if you have a document open in multiple splits, all the splits
will be closed at the same time, leaving only splits without that
document focused (or a scratch buffer if they were all focused on that
buffer).
* helix-view/tree: reset focus if Tree is empty
2021-11-15 16:30:45 +01:00
editor . close ( view_id ) ;
2021-06-13 11:06:06 +02:00
}
2021-06-26 19:50:44 +02:00
Ok ( ( ) )
2021-06-13 11:06:06 +02:00
}
2021-06-26 19:50:44 +02:00
fn quit_all (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-12-03 04:48:07 +01:00
quit_all_impl ( cx . editor , args , event , false )
2021-06-13 11:06:06 +02:00
}
2021-06-26 19:50:44 +02:00
fn force_quit_all (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-12-03 04:48:07 +01:00
quit_all_impl ( cx . editor , args , event , true )
2021-06-13 11:06:06 +02:00
}
2021-11-15 05:06:12 +01:00
fn cquit (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-11-15 05:06:12 +01:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
let exit_code = args
. first ( )
. and_then ( | code | code . parse ::< i32 > ( ) . ok ( ) )
. unwrap_or ( 1 ) ;
cx . editor . exit_code = exit_code ;
let views : Vec < _ > = cx . editor . tree . views ( ) . map ( | ( view , _ ) | view . id ) . collect ( ) ;
for view_id in views {
2021-11-15 16:37:30 +01:00
cx . editor . close ( view_id ) ;
2021-11-15 05:06:12 +01:00
}
Ok ( ( ) )
}
2021-06-26 19:50:44 +02:00
fn theme (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-11-14 13:26:48 +01:00
let theme = args . first ( ) . context ( " Theme not provided " ) ? ;
let theme = cx
. editor
. theme_loader
. load ( theme )
. with_context ( | | format! ( " Failed setting theme {} " , theme ) ) ? ;
let true_color = cx . editor . config . true_color | | crate ::true_color ( ) ;
if ! ( true_color | | theme . is_16_color ( ) ) {
bail! ( " Unsupported theme: theme requires true color support " ) ;
}
cx . editor . set_theme ( theme ) ;
Ok ( ( ) )
2021-06-19 13:27:32 +02:00
}
2021-07-01 20:57:12 +02:00
fn yank_main_selection_to_clipboard (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
_args : & [ Cow < str > ] ,
2021-07-01 20:57:12 +02:00
_event : PromptEvent ,
2021-06-26 19:50:44 +02:00
) -> anyhow ::Result < ( ) > {
2021-12-03 04:48:07 +01:00
yank_main_selection_to_clipboard_impl ( cx . editor , ClipboardType ::Clipboard )
2021-06-14 23:37:17 +02:00
}
2021-06-26 19:50:44 +02:00
fn yank_joined_to_clipboard (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-06-22 21:49:55 +02:00
let ( _ , doc ) = current! ( cx . editor ) ;
2021-12-12 13:13:33 +01:00
let default_sep = Cow ::Borrowed ( doc . line_ending . as_str ( ) ) ;
let separator = args . first ( ) . unwrap_or ( & default_sep ) ;
2021-12-03 04:48:07 +01:00
yank_joined_to_clipboard_impl ( cx . editor , separator , ClipboardType ::Clipboard )
2021-08-12 04:53:48 +02:00
}
fn yank_main_selection_to_primary_clipboard (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
_args : & [ Cow < str > ] ,
2021-08-12 04:53:48 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-12-03 04:48:07 +01:00
yank_main_selection_to_clipboard_impl ( cx . editor , ClipboardType ::Selection )
2021-08-12 04:53:48 +02:00
}
fn yank_joined_to_primary_clipboard (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-08-12 04:53:48 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
let ( _ , doc ) = current! ( cx . editor ) ;
2021-12-12 13:13:33 +01:00
let default_sep = Cow ::Borrowed ( doc . line_ending . as_str ( ) ) ;
let separator = args . first ( ) . unwrap_or ( & default_sep ) ;
2021-12-03 04:48:07 +01:00
yank_joined_to_clipboard_impl ( cx . editor , separator , ClipboardType ::Selection )
2021-06-14 23:37:17 +02:00
}
2021-06-26 19:50:44 +02:00
fn paste_clipboard_after (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
_args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-12-14 09:49:29 +01:00
paste_clipboard_impl ( cx . editor , Paste ::After , ClipboardType ::Clipboard , 1 )
2021-06-14 23:37:17 +02:00
}
2021-06-26 19:50:44 +02:00
fn paste_clipboard_before (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
_args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-12-14 09:49:29 +01:00
paste_clipboard_impl ( cx . editor , Paste ::After , ClipboardType ::Clipboard , 1 )
2021-06-14 23:37:17 +02:00
}
2021-08-12 04:53:48 +02:00
fn paste_primary_clipboard_after (
2021-07-01 20:57:12 +02:00
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
_args : & [ Cow < str > ] ,
2021-07-01 20:57:12 +02:00
_event : PromptEvent ,
2021-08-12 04:53:48 +02:00
) -> anyhow ::Result < ( ) > {
2021-12-14 09:49:29 +01:00
paste_clipboard_impl ( cx . editor , Paste ::After , ClipboardType ::Selection , 1 )
2021-08-12 04:53:48 +02:00
}
fn paste_primary_clipboard_before (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
_args : & [ Cow < str > ] ,
2021-08-12 04:53:48 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-12-14 09:49:29 +01:00
paste_clipboard_impl ( cx . editor , Paste ::After , ClipboardType ::Selection , 1 )
2021-08-12 04:53:48 +02:00
}
fn replace_selections_with_clipboard_impl (
cx : & mut compositor ::Context ,
clipboard_type : ClipboardType ,
2021-06-26 19:50:44 +02:00
) -> anyhow ::Result < ( ) > {
2021-06-22 21:49:55 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-06-14 23:37:17 +02:00
2021-08-12 04:53:48 +02:00
match cx . editor . clipboard_provider . get_contents ( clipboard_type ) {
2021-06-14 23:37:17 +02:00
Ok ( contents ) = > {
2021-07-29 00:57:00 +02:00
let selection = doc . selection ( view . id ) ;
2021-06-14 23:37:17 +02:00
let transaction =
2021-07-29 21:41:24 +02:00
Transaction ::change_by_selection ( doc . text ( ) , selection , | range | {
2021-07-01 18:51:24 +02:00
( range . from ( ) , range . to ( ) , Some ( contents . as_str ( ) . into ( ) ) )
2021-06-14 23:37:17 +02:00
} ) ;
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
2021-06-26 19:50:44 +02:00
Ok ( ( ) )
}
2021-07-11 09:35:57 +02:00
Err ( e ) = > Err ( e . context ( " Couldn't get system clipboard contents " ) ) ,
2021-06-14 23:37:17 +02:00
}
}
2021-08-12 04:53:48 +02:00
fn replace_selections_with_clipboard (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
_args : & [ Cow < str > ] ,
2021-08-12 04:53:48 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
replace_selections_with_clipboard_impl ( cx , ClipboardType ::Clipboard )
}
fn replace_selections_with_primary_clipboard (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
_args : & [ Cow < str > ] ,
2021-08-12 04:53:48 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
replace_selections_with_clipboard_impl ( cx , ClipboardType ::Selection )
}
2021-06-26 19:50:44 +02:00
fn show_clipboard_provider (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
_args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-06-22 21:49:55 +02:00
cx . editor
2021-08-12 04:53:48 +02:00
. set_status ( cx . editor . clipboard_provider . name ( ) . to_string ( ) ) ;
2021-06-26 19:50:44 +02:00
Ok ( ( ) )
2021-06-14 23:37:17 +02:00
}
2021-06-26 19:50:44 +02:00
fn change_current_directory (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-09-02 04:03:42 +02:00
let dir = helix_core ::path ::expand_tilde (
args . first ( )
. context ( " target directory not provided " ) ?
2021-12-12 13:13:33 +01:00
. as_ref ( )
2021-09-02 04:03:42 +02:00
. as_ref ( ) ,
) ;
2021-06-21 17:40:27 +02:00
if let Err ( e ) = std ::env ::set_current_dir ( dir ) {
2021-08-20 06:42:17 +02:00
bail! ( " Couldn't change the current working directory: {} " , e ) ;
2021-06-21 17:40:27 +02:00
}
2021-07-11 09:35:57 +02:00
let cwd = std ::env ::current_dir ( ) . context ( " Couldn't get the new working directory " ) ? ;
cx . editor . set_status ( format! (
" Current working directory is now {} " ,
cwd . display ( )
) ) ;
Ok ( ( ) )
2021-06-21 17:40:27 +02:00
}
2021-06-26 19:50:44 +02:00
fn show_current_directory (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
_args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-07-11 09:35:57 +02:00
let cwd = std ::env ::current_dir ( ) . context ( " Couldn't get the new working directory " ) ? ;
cx . editor
. set_status ( format! ( " Current working directory is {} " , cwd . display ( ) ) ) ;
Ok ( ( ) )
2021-06-21 17:49:21 +02:00
}
2021-07-02 16:54:50 +02:00
/// Sets the [`Document`]'s encoding..
2021-06-26 19:50:44 +02:00
fn set_encoding (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-07-02 16:54:50 +02:00
let ( _ , doc ) = current! ( cx . editor ) ;
if let Some ( label ) = args . first ( ) {
doc . set_encoding ( label )
} else {
let encoding = doc . encoding ( ) . name ( ) . to_string ( ) ;
2021-06-26 19:50:44 +02:00
cx . editor . set_status ( encoding ) ;
Ok ( ( ) )
2021-07-02 16:54:50 +02:00
}
}
/// Reload the [`Document`] from its source file.
2021-06-26 19:50:44 +02:00
fn reload (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
_args : & [ Cow < str > ] ,
2021-06-26 19:50:44 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-07-02 16:54:50 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-06-26 19:50:44 +02:00
doc . reload ( view . id )
2021-07-02 16:54:50 +02:00
}
2021-08-13 06:15:36 +02:00
fn tree_sitter_scopes (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
_args : & [ Cow < str > ] ,
2021-08-13 06:15:36 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
let ( view , doc ) = current! ( cx . editor ) ;
let text = doc . text ( ) . slice ( .. ) ;
let pos = doc . selection ( view . id ) . primary ( ) . cursor ( text ) ;
let scopes = indent ::get_scopes ( doc . syntax ( ) , text , pos ) ;
cx . editor . set_status ( format! ( " scopes: {:?} " , & scopes ) ) ;
Ok ( ( ) )
}
2021-08-24 02:37:44 +02:00
fn vsplit (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-08-24 02:37:44 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-11-06 09:54:04 +01:00
let id = view! ( cx . editor ) . doc ;
2021-08-24 02:37:44 +02:00
2021-12-12 13:13:33 +01:00
if args . is_empty ( ) {
2021-08-24 02:37:44 +02:00
cx . editor . switch ( id , Action ::VerticalSplit ) ;
2021-12-12 13:13:33 +01:00
} else {
for arg in args {
cx . editor
. open ( PathBuf ::from ( arg . as_ref ( ) ) , Action ::VerticalSplit ) ? ;
}
2021-08-24 02:37:44 +02:00
}
Ok ( ( ) )
}
fn hsplit (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-08-24 02:37:44 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-11-06 09:54:04 +01:00
let id = view! ( cx . editor ) . doc ;
2021-08-24 02:37:44 +02:00
2021-12-12 13:13:33 +01:00
if args . is_empty ( ) {
2021-08-24 02:37:44 +02:00
cx . editor . switch ( id , Action ::HorizontalSplit ) ;
2021-12-12 13:13:33 +01:00
} else {
for arg in args {
cx . editor
. open ( PathBuf ::from ( arg . as_ref ( ) ) , Action ::HorizontalSplit ) ? ;
}
2021-08-24 02:37:44 +02:00
}
Ok ( ( ) )
}
2021-10-28 03:23:46 +02:00
fn tutor (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
_args : & [ Cow < str > ] ,
2021-10-28 03:23:46 +02:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
let path = helix_core ::runtime_dir ( ) . join ( " tutor.txt " ) ;
cx . editor . open ( path , Action ::Replace ) ? ;
// Unset path to prevent accidentally saving to the original tutor file.
doc_mut! ( cx . editor ) . set_path ( None ) ? ;
Ok ( ( ) )
}
2021-11-24 07:26:55 +01:00
pub ( super ) fn goto_line_number (
cx : & mut compositor ::Context ,
2021-12-12 13:13:33 +01:00
args : & [ Cow < str > ] ,
2021-11-24 07:26:55 +01:00
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
2021-11-30 04:59:19 +01:00
ensure! ( ! args . is_empty ( ) , " Line number required " ) ;
2021-11-25 03:02:51 +01:00
2021-11-24 07:26:55 +01:00
let line = args [ 0 ] . parse ::< usize > ( ) ? ;
2021-12-03 04:48:07 +01:00
goto_line_impl ( cx . editor , NonZeroUsize ::new ( line ) ) ;
2021-11-24 07:26:55 +01:00
let ( view , doc ) = current! ( cx . editor ) ;
view . ensure_cursor_in_view ( doc , line ) ;
2021-12-26 02:04:33 +01:00
Ok ( ( ) )
}
fn setting (
cx : & mut compositor ::Context ,
args : & [ Cow < str > ] ,
_event : PromptEvent ,
) -> anyhow ::Result < ( ) > {
let runtime_config = & mut cx . editor . config ;
if args . len ( ) ! = 2 {
anyhow ::bail! ( " Bad arguments. Usage: `:set key field` " ) ;
}
let ( key , arg ) = ( & args [ 0 ] . to_lowercase ( ) , & args [ 1 ] ) ;
match key . as_ref ( ) {
" scrolloff " = > runtime_config . scrolloff = arg . parse ( ) ? ,
" scroll-lines " = > runtime_config . scroll_lines = arg . parse ( ) ? ,
" mouse " = > runtime_config . mouse = arg . parse ( ) ? ,
" line-number " = > runtime_config . line_number = arg . parse ( ) ? ,
" middle-click_paste " = > runtime_config . middle_click_paste = arg . parse ( ) ? ,
" smart-case " = > runtime_config . smart_case = arg . parse ( ) ? ,
" auto-pairs " = > runtime_config . auto_pairs = arg . parse ( ) ? ,
" auto-completion " = > runtime_config . auto_completion = arg . parse ( ) ? ,
" completion-trigger-len " = > runtime_config . completion_trigger_len = arg . parse ( ) ? ,
" auto-info " = > runtime_config . auto_info = arg . parse ( ) ? ,
" true-color " = > runtime_config . true_color = arg . parse ( ) ? ,
_ = > anyhow ::bail! ( " Unknown key `{}`. " , args [ 0 ] ) ,
}
2021-11-24 07:26:55 +01:00
Ok ( ( ) )
}
2021-06-17 13:08:05 +02:00
pub const TYPABLE_COMMAND_LIST : & [ TypableCommand ] = & [
TypableCommand {
2021-05-07 10:08:07 +02:00
name : " quit " ,
2021-10-03 04:41:41 +02:00
aliases : & [ " q " ] ,
2021-05-07 10:08:07 +02:00
doc : " Close the current view. " ,
fun : quit ,
2021-05-07 10:19:45 +02:00
completer : None ,
2021-05-07 10:08:07 +02:00
} ,
2021-06-17 13:08:05 +02:00
TypableCommand {
2021-05-07 10:08:07 +02:00
name : " quit! " ,
2021-10-03 04:41:41 +02:00
aliases : & [ " q! " ] ,
2021-10-25 02:25:47 +02:00
doc : " Close the current view forcefully (ignoring unsaved changes). " ,
2021-05-07 10:08:07 +02:00
fun : force_quit ,
2021-05-07 10:19:45 +02:00
completer : None ,
2021-05-07 10:08:07 +02:00
} ,
2021-06-17 13:08:05 +02:00
TypableCommand {
2021-05-07 10:08:07 +02:00
name : " open " ,
2021-10-03 04:41:41 +02:00
aliases : & [ " o " ] ,
2021-05-07 10:08:07 +02:00
doc : " Open a file from disk into the current view. " ,
fun : open ,
2021-05-07 10:19:45 +02:00
completer : Some ( completers ::filename ) ,
2021-05-07 10:08:07 +02:00
} ,
helix-term/commands: implement buffer-close (bc, bclose) (#1035)
* helix-view/view: impl method to remove document from jumps
* helix-view/editor: impl close_document
* helix-view/editor: remove close_buffer argument from `close`
According to archseer, this was never implemented or used properly. Now
that we have a proper "buffer close" function, we can get rid of this.
* helix-term/commands: implement buffer-close (bc, bclose)
This behaves the same as Kakoune's `delete-buffer` / `db` command:
* With 3 files opened by the user with `:o ab`, `:o cd`, and `:o ef`:
* `buffer-close` once closes `ef` and switches to `cd`
* `buffer-close` again closes `cd` and switches to `ab`
* `buffer-close` again closes `ab` and switches to a scratch buffer
* With 3 files opened from the command line with `hx -- ab cd ef`:
* `buffer-close` once closes `ab` and switches to `cd`
* `buffer-close` again closes `cd` and switches to `ef`
* `buffer-close` again closes `ef` and switches to a scratch buffer
* With 1 file opened (`ab`):
* `buffer-close` once closes `ab` and switches to a scratch buffer
* `buffer-close` again closes the scratch buffer and switches to a new
scratch buffer
* helix-term/commands: implement buffer-close! (bclose!, bc!)
Namely, if you have a document open in multiple splits, all the splits
will be closed at the same time, leaving only splits without that
document focused (or a scratch buffer if they were all focused on that
buffer).
* helix-view/tree: reset focus if Tree is empty
2021-11-15 16:30:45 +01:00
TypableCommand {
name : " buffer-close " ,
aliases : & [ " bc " , " bclose " ] ,
doc : " Close the current buffer. " ,
fun : buffer_close ,
completer : None , // FIXME: buffer completer
} ,
TypableCommand {
name : " buffer-close! " ,
aliases : & [ " bc! " , " bclose! " ] ,
doc : " Close the current buffer forcefully (ignoring unsaved changes). " ,
fun : force_buffer_close ,
completer : None , // FIXME: buffer completer
} ,
2021-06-17 13:08:05 +02:00
TypableCommand {
2021-05-07 10:08:07 +02:00
name : " write " ,
2021-10-03 04:41:41 +02:00
aliases : & [ " w " ] ,
2021-06-01 07:47:21 +02:00
doc : " Write changes to disk. Accepts an optional path (:write some/path.txt) " ,
2021-05-07 10:08:07 +02:00
fun : write ,
2021-05-07 10:19:45 +02:00
completer : Some ( completers ::filename ) ,
2021-05-07 10:08:07 +02:00
} ,
2021-06-17 13:08:05 +02:00
TypableCommand {
2021-05-07 10:08:07 +02:00
name : " new " ,
2021-10-03 04:41:41 +02:00
aliases : & [ " n " ] ,
2021-05-07 10:08:07 +02:00
doc : " Create a new scratch buffer. " ,
fun : new_file ,
2021-05-07 10:19:45 +02:00
completer : Some ( completers ::filename ) ,
2021-05-07 10:08:07 +02:00
} ,
2021-06-17 13:08:05 +02:00
TypableCommand {
2021-05-12 10:24:55 +02:00
name : " format " ,
2021-10-03 04:41:41 +02:00
aliases : & [ " fmt " ] ,
2021-11-17 14:30:11 +01:00
doc : " Format the file using the LSP formatter. " ,
2021-05-12 10:24:55 +02:00
fun : format ,
completer : None ,
} ,
2021-06-17 13:08:05 +02:00
TypableCommand {
2021-06-14 22:22:25 +02:00
name : " indent-style " ,
2021-10-03 04:41:41 +02:00
aliases : & [ ] ,
2021-06-14 03:09:22 +02:00
doc : " Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.) " ,
fun : set_indent_style ,
completer : None ,
} ,
2021-06-21 21:36:01 +02:00
TypableCommand {
name : " line-ending " ,
2021-10-03 04:41:41 +02:00
aliases : & [ ] ,
2021-06-21 21:36:01 +02:00
doc : " Set the document's default line ending. Options: crlf, lf, cr, ff, nel. " ,
fun : set_line_ending ,
completer : None ,
} ,
2021-06-17 13:08:05 +02:00
TypableCommand {
2021-06-11 15:06:13 +02:00
name : " earlier " ,
2021-10-03 04:41:41 +02:00
aliases : & [ " ear " ] ,
2021-06-11 15:06:13 +02:00
doc : " Jump back to an earlier point in edit history. Accepts a number of steps or a time span. " ,
fun : earlier ,
completer : None ,
} ,
2021-06-17 13:08:05 +02:00
TypableCommand {
2021-06-11 15:06:13 +02:00
name : " later " ,
2021-10-03 04:41:41 +02:00
aliases : & [ " lat " ] ,
2021-06-11 15:06:13 +02:00
doc : " Jump to a later point in edit history. Accepts a number of steps or a time span. " ,
fun : later ,
completer : None ,
} ,
2021-06-17 13:08:05 +02:00
TypableCommand {
2021-06-13 07:31:25 +02:00
name : " write-quit " ,
2021-10-03 04:41:41 +02:00
aliases : & [ " wq " , " x " ] ,
2021-10-25 02:25:47 +02:00
doc : " Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt) " ,
2021-06-13 07:31:25 +02:00
fun : write_quit ,
completer : Some ( completers ::filename ) ,
} ,
2021-06-17 13:08:05 +02:00
TypableCommand {
2021-06-13 07:31:25 +02:00
name : " write-quit! " ,
2021-10-03 04:41:41 +02:00
aliases : & [ " wq! " , " x! " ] ,
2021-10-25 02:25:47 +02:00
doc : " Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt) " ,
2021-06-13 07:31:25 +02:00
fun : force_write_quit ,
completer : Some ( completers ::filename ) ,
} ,
2021-06-17 13:08:05 +02:00
TypableCommand {
2021-06-13 07:31:25 +02:00
name : " write-all " ,
2021-10-03 04:41:41 +02:00
aliases : & [ " wa " ] ,
2021-10-25 02:25:47 +02:00
doc : " Write changes from all views to disk. " ,
2021-06-13 07:31:25 +02:00
fun : write_all ,
completer : None ,
} ,
2021-06-17 13:08:05 +02:00
TypableCommand {
2021-06-13 11:06:06 +02:00
name : " write-quit-all " ,
2021-10-03 04:41:41 +02:00
aliases : & [ " wqa " , " xa " ] ,
2021-10-25 02:25:47 +02:00
doc : " Write changes from all views to disk and close all views. " ,
2021-06-13 07:31:25 +02:00
fun : write_all_quit ,
completer : None ,
} ,
2021-06-17 13:08:05 +02:00
TypableCommand {
2021-06-13 11:06:06 +02:00
name : " write-quit-all! " ,
2021-10-03 04:41:41 +02:00
aliases : & [ " wqa! " , " xa! " ] ,
2021-10-25 02:25:47 +02:00
doc : " Write changes from all views to disk and close all views forcefully (ignoring unsaved changes). " ,
2021-06-13 07:31:25 +02:00
fun : force_write_all_quit ,
completer : None ,
} ,
2021-06-17 13:08:05 +02:00
TypableCommand {
2021-06-13 11:06:06 +02:00
name : " quit-all " ,
2021-10-03 04:41:41 +02:00
aliases : & [ " qa " ] ,
2021-06-13 11:06:06 +02:00
doc : " Close all views. " ,
fun : quit_all ,
completer : None ,
} ,
2021-06-17 13:08:05 +02:00
TypableCommand {
2021-06-13 11:06:06 +02:00
name : " quit-all! " ,
2021-10-03 04:41:41 +02:00
aliases : & [ " qa! " ] ,
2021-06-13 11:06:06 +02:00
doc : " Close all views forcefully (ignoring unsaved changes). " ,
fun : force_quit_all ,
completer : None ,
} ,
2021-11-15 05:06:12 +01:00
TypableCommand {
name : " cquit " ,
aliases : & [ " cq " ] ,
doc : " Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2). " ,
fun : cquit ,
completer : None ,
} ,
2021-06-19 13:27:32 +02:00
TypableCommand {
name : " theme " ,
2021-10-03 04:41:41 +02:00
aliases : & [ ] ,
2021-11-17 14:30:11 +01:00
doc : " Change the editor theme. " ,
2021-06-19 13:27:32 +02:00
fun : theme ,
completer : Some ( completers ::theme ) ,
} ,
2021-06-14 23:37:17 +02:00
TypableCommand {
name : " clipboard-yank " ,
2021-10-03 04:41:41 +02:00
aliases : & [ ] ,
2021-06-14 23:37:17 +02:00
doc : " Yank main selection into system clipboard. " ,
fun : yank_main_selection_to_clipboard ,
completer : None ,
} ,
TypableCommand {
name : " clipboard-yank-join " ,
2021-10-03 04:41:41 +02:00
aliases : & [ ] ,
2021-06-19 08:27:43 +02:00
doc : " Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline. " , // FIXME: current UI can't display long doc.
2021-06-14 23:37:17 +02:00
fun : yank_joined_to_clipboard ,
completer : None ,
} ,
2021-08-12 04:53:48 +02:00
TypableCommand {
name : " primary-clipboard-yank " ,
2021-10-03 04:41:41 +02:00
aliases : & [ ] ,
2021-08-12 04:53:48 +02:00
doc : " Yank main selection into system primary clipboard. " ,
fun : yank_main_selection_to_primary_clipboard ,
completer : None ,
} ,
TypableCommand {
name : " primary-clipboard-yank-join " ,
2021-10-03 04:41:41 +02:00
aliases : & [ ] ,
2021-08-12 04:53:48 +02:00
doc : " Yank joined selections into system primary clipboard. A separator can be provided as first argument. Default value is newline. " , // FIXME: current UI can't display long doc.
fun : yank_joined_to_primary_clipboard ,
completer : None ,
} ,
2021-06-14 23:37:17 +02:00
TypableCommand {
name : " clipboard-paste-after " ,
2021-10-03 04:41:41 +02:00
aliases : & [ ] ,
2021-06-14 23:37:17 +02:00
doc : " Paste system clipboard after selections. " ,
fun : paste_clipboard_after ,
completer : None ,
} ,
TypableCommand {
name : " clipboard-paste-before " ,
2021-10-03 04:41:41 +02:00
aliases : & [ ] ,
2021-06-14 23:37:17 +02:00
doc : " Paste system clipboard before selections. " ,
fun : paste_clipboard_before ,
completer : None ,
} ,
TypableCommand {
name : " clipboard-paste-replace " ,
2021-10-03 04:41:41 +02:00
aliases : & [ ] ,
2021-06-14 23:37:17 +02:00
doc : " Replace selections with content of system clipboard. " ,
fun : replace_selections_with_clipboard ,
completer : None ,
} ,
2021-08-12 04:53:48 +02:00
TypableCommand {
name : " primary-clipboard-paste-after " ,
2021-10-03 04:41:41 +02:00
aliases : & [ ] ,
2021-08-12 04:53:48 +02:00
doc : " Paste primary clipboard after selections. " ,
fun : paste_primary_clipboard_after ,
completer : None ,
} ,
TypableCommand {
name : " primary-clipboard-paste-before " ,
2021-10-03 04:41:41 +02:00
aliases : & [ ] ,
2021-08-12 04:53:48 +02:00
doc : " Paste primary clipboard before selections. " ,
fun : paste_primary_clipboard_before ,
completer : None ,
} ,
TypableCommand {
name : " primary-clipboard-paste-replace " ,
2021-10-03 04:41:41 +02:00
aliases : & [ ] ,
2021-08-12 04:53:48 +02:00
doc : " Replace selections with content of system primary clipboard. " ,
fun : replace_selections_with_primary_clipboard ,
completer : None ,
} ,
2021-06-14 23:37:17 +02:00
TypableCommand {
name : " show-clipboard-provider " ,
2021-10-03 04:41:41 +02:00
aliases : & [ ] ,
2021-06-14 23:37:17 +02:00
doc : " Show clipboard provider name in status bar. " ,
fun : show_clipboard_provider ,
completer : None ,
} ,
2021-06-21 17:40:27 +02:00
TypableCommand {
name : " change-current-directory " ,
2021-10-03 04:41:41 +02:00
aliases : & [ " cd " ] ,
2021-11-17 14:30:11 +01:00
doc : " Change the current working directory. " ,
2021-06-21 17:40:27 +02:00
fun : change_current_directory ,
completer : Some ( completers ::directory ) ,
} ,
2021-06-21 17:49:21 +02:00
TypableCommand {
name : " show-directory " ,
2021-10-03 04:41:41 +02:00
aliases : & [ " pwd " ] ,
2021-06-21 17:49:21 +02:00
doc : " Show the current working directory. " ,
fun : show_current_directory ,
completer : None ,
} ,
2021-07-02 16:54:50 +02:00
TypableCommand {
name : " encoding " ,
2021-10-03 04:41:41 +02:00
aliases : & [ ] ,
2021-07-02 16:54:50 +02:00
doc : " Set encoding based on `https://encoding.spec.whatwg.org` " ,
fun : set_encoding ,
completer : None ,
} ,
TypableCommand {
name : " reload " ,
2021-10-03 04:41:41 +02:00
aliases : & [ ] ,
2021-07-02 16:54:50 +02:00
doc : " Discard changes and reload from the source file. " ,
fun : reload ,
completer : None ,
2021-08-13 06:15:36 +02:00
} ,
TypableCommand {
name : " tree-sitter-scopes " ,
2021-10-03 04:41:41 +02:00
aliases : & [ ] ,
2021-08-13 06:15:36 +02:00
doc : " Display tree sitter scopes, primarily for theming and development. " ,
fun : tree_sitter_scopes ,
completer : None ,
2021-08-24 02:37:44 +02:00
} ,
TypableCommand {
name : " vsplit " ,
2021-10-03 04:41:41 +02:00
aliases : & [ " vs " ] ,
2021-08-24 02:37:44 +02:00
doc : " Open the file in a vertical split. " ,
fun : vsplit ,
completer : Some ( completers ::filename ) ,
} ,
TypableCommand {
name : " hsplit " ,
2021-10-03 04:41:41 +02:00
aliases : & [ " hs " , " sp " ] ,
2021-08-24 02:37:44 +02:00
doc : " Open the file in a horizontal split. " ,
fun : hsplit ,
completer : Some ( completers ::filename ) ,
2021-10-28 03:23:46 +02:00
} ,
TypableCommand {
name : " tutor " ,
aliases : & [ ] ,
doc : " Open the tutorial. " ,
fun : tutor ,
completer : None ,
} ,
2021-11-24 07:26:55 +01:00
TypableCommand {
name : " goto " ,
aliases : & [ " g " ] ,
doc : " Go to line number. " ,
fun : goto_line_number ,
completer : None ,
2021-12-26 02:04:33 +01:00
} ,
TypableCommand {
name : " set-option " ,
aliases : & [ " set " ] ,
doc : " Set a config option at runtime " ,
fun : setting ,
completer : Some ( completers ::setting ) ,
2021-11-24 07:26:55 +01:00
}
2021-05-07 10:08:07 +02:00
] ;
2021-12-04 15:47:18 +01:00
pub static TYPABLE_COMMAND_MAP : Lazy < HashMap < & 'static str , & 'static TypableCommand > > =
Lazy ::new ( | | {
TYPABLE_COMMAND_LIST
. iter ( )
. flat_map ( | cmd | {
std ::iter ::once ( ( cmd . name , cmd ) )
. chain ( cmd . aliases . iter ( ) . map ( move | & alias | ( alias , cmd ) ) )
} )
. collect ( )
} ) ;
2021-05-07 10:08:07 +02:00
}
2021-03-01 10:02:31 +01:00
2021-06-17 13:08:05 +02:00
fn command_mode ( cx : & mut Context ) {
2021-05-08 10:33:06 +02:00
let mut prompt = Prompt ::new (
2021-08-31 11:29:24 +02:00
" : " . into ( ) ,
2021-07-24 10:48:45 +02:00
Some ( ':' ) ,
2021-03-01 10:02:31 +01:00
| input : & str | {
2021-06-08 06:20:15 +02:00
// we use .this over split_whitespace() because we care about empty segments
2021-03-01 10:02:31 +01:00
let parts = input . split ( ' ' ) . collect ::< Vec < & str > > ( ) ;
2021-05-07 10:19:45 +02:00
// simple heuristic: if there's no just one part, complete command name.
// if there's a space, per command completion kicks in.
2021-03-01 10:02:31 +01:00
if parts . len ( ) < = 1 {
2021-03-21 06:13:49 +01:00
let end = 0 .. ;
2021-06-17 13:08:05 +02:00
cmd ::TYPABLE_COMMAND_LIST
2021-03-01 10:02:31 +01:00
. iter ( )
2021-05-07 10:08:07 +02:00
. filter ( | command | command . name . contains ( input ) )
. map ( | command | ( end . clone ( ) , Cow ::Borrowed ( command . name ) ) )
2021-03-01 10:02:31 +01:00
. collect ( )
} else {
let part = parts . last ( ) . unwrap ( ) ;
2021-06-17 13:08:05 +02:00
if let Some ( cmd ::TypableCommand {
2021-05-07 10:19:45 +02:00
completer : Some ( completer ) ,
..
2021-12-04 15:47:18 +01:00
} ) = cmd ::TYPABLE_COMMAND_MAP . get ( parts [ 0 ] )
2021-05-07 10:19:45 +02:00
{
completer ( part )
. into_iter ( )
. map ( | ( range , file ) | {
// offset ranges to input
let offset = input . len ( ) - part . len ( ) ;
let range = ( range . start + offset ) .. ;
( range , file )
} )
. collect ( )
} else {
Vec ::new ( )
}
2021-03-01 10:02:31 +01:00
}
2021-02-21 11:04:31 +01:00
} , // completion
2021-06-22 21:49:55 +02:00
move | cx : & mut compositor ::Context , input : & str , event : PromptEvent | {
2021-02-21 11:04:31 +01:00
if event ! = PromptEvent ::Validate {
return ;
}
2021-02-16 10:23:44 +01:00
2021-06-08 06:20:15 +02:00
let parts = input . split_whitespace ( ) . collect ::< Vec < & str > > ( ) ;
2021-06-02 03:48:21 +02:00
if parts . is_empty ( ) {
return ;
}
2021-02-21 11:04:31 +01:00
2021-11-24 07:26:55 +01:00
// If command is numeric, interpret as line number and go there.
if parts . len ( ) = = 1 & & parts [ 0 ] . parse ::< usize > ( ) . ok ( ) . is_some ( ) {
2021-12-12 13:13:33 +01:00
if let Err ( e ) = cmd ::goto_line_number ( cx , & [ Cow ::from ( parts [ 0 ] ) ] , event ) {
2021-11-24 07:26:55 +01:00
cx . editor . set_error ( format! ( " {} " , e ) ) ;
}
return ;
}
// Handle typable commands
2021-12-04 15:47:18 +01:00
if let Some ( cmd ) = cmd ::TYPABLE_COMMAND_MAP . get ( parts [ 0 ] ) {
2021-12-12 13:13:33 +01:00
let args = shellwords ::shellwords ( input ) ;
if let Err ( e ) = ( cmd . fun ) ( cx , & args [ 1 .. ] , event ) {
2021-06-26 19:50:44 +02:00
cx . editor . set_error ( format! ( " {} " , e ) ) ;
}
2021-05-07 10:08:07 +02:00
} else {
2021-06-22 21:49:55 +02:00
cx . editor
. set_error ( format! ( " no such command: ' {} ' " , parts [ 0 ] ) ) ;
2021-05-07 10:08:07 +02:00
} ;
2020-12-21 08:23:05 +01:00
} ,
2021-02-21 11:04:31 +01:00
) ;
2021-05-08 10:33:06 +02:00
prompt . doc_fn = Box ::new ( | input : & str | {
let part = input . split ( ' ' ) . next ( ) . unwrap_or_default ( ) ;
2021-12-04 15:47:18 +01:00
if let Some ( cmd ::TypableCommand { doc , .. } ) = cmd ::TYPABLE_COMMAND_MAP . get ( part ) {
2021-05-08 10:33:06 +02:00
return Some ( doc ) ;
}
None
} ) ;
2021-02-21 11:04:31 +01:00
cx . push_layer ( Box ::new ( prompt ) ) ;
2020-11-03 10:57:12 +01:00
}
2021-03-29 10:04:12 +02:00
2021-06-17 13:08:05 +02:00
fn file_picker ( cx : & mut Context ) {
2021-03-29 10:04:12 +02:00
let root = find_root ( None ) . unwrap_or_else ( | | PathBuf ::from ( " ./ " ) ) ;
2021-11-20 15:23:36 +01:00
let picker = ui ::file_picker ( root , & cx . editor . config ) ;
2021-02-21 11:04:31 +01:00
cx . push_layer ( Box ::new ( picker ) ) ;
2020-12-21 08:23:05 +01:00
}
2021-06-17 13:08:05 +02:00
fn buffer_picker ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let current = view! ( cx . editor ) . doc ;
2021-03-24 08:26:53 +01:00
2021-11-10 02:52:39 +01:00
struct BufferMeta {
id : DocumentId ,
path : Option < PathBuf > ,
is_modified : bool ,
is_current : bool ,
}
impl BufferMeta {
fn format ( & self ) -> Cow < str > {
let path = self
. path
. as_deref ( )
. map ( helix_core ::path ::get_relative_path ) ;
let path = match path . as_deref ( ) . and_then ( Path ::to_str ) {
Some ( path ) = > path ,
2021-11-11 04:22:15 +01:00
None = > return Cow ::Borrowed ( SCRATCH_BUFFER_NAME ) ,
2021-11-10 02:52:39 +01:00
} ;
let mut flags = Vec ::new ( ) ;
if self . is_modified {
flags . push ( " + " ) ;
}
if self . is_current {
flags . push ( " * " ) ;
}
let flag = if flags . is_empty ( ) {
" " . into ( )
} else {
format! ( " ( {} ) " , flags . join ( " " ) )
} ;
Cow ::Owned ( format! ( " {} {} " , path , flag ) )
}
}
let new_meta = | doc : & Document | BufferMeta {
id : doc . id ( ) ,
path : doc . path ( ) . cloned ( ) ,
is_modified : doc . is_modified ( ) ,
is_current : doc . id ( ) = = current ,
} ;
2021-08-12 09:00:42 +02:00
let picker = FilePicker ::new (
2021-03-24 08:26:53 +01:00
cx . editor
. documents
. iter ( )
2021-11-10 02:52:39 +01:00
. map ( | ( _ , doc ) | new_meta ( doc ) )
2021-03-24 08:26:53 +01:00
. collect ( ) ,
2021-11-10 02:52:39 +01:00
BufferMeta ::format ,
| editor : & mut Editor , meta , _action | {
editor . switch ( meta . id , Action ::Replace ) ;
2021-03-24 08:26:53 +01:00
} ,
2021-11-10 02:52:39 +01:00
| editor , meta | {
let doc = & editor . documents . get ( & meta . id ) ? ;
2021-08-12 09:00:42 +02:00
let & view_id = doc . selections ( ) . keys ( ) . next ( ) ? ;
let line = doc
. selection ( view_id )
. primary ( )
. cursor_line ( doc . text ( ) . slice ( .. ) ) ;
2021-11-10 02:52:39 +01:00
Some ( ( meta . path . clone ( ) ? , Some ( ( line , line ) ) ) )
2021-08-12 09:00:42 +02:00
} ,
2021-03-24 08:26:53 +01:00
) ;
cx . push_layer ( Box ::new ( picker ) ) ;
2020-12-17 10:08:16 +01:00
}
2020-11-03 10:57:12 +01:00
2021-06-17 13:08:05 +02:00
fn symbol_picker ( cx : & mut Context ) {
2021-06-12 14:45:21 +02:00
fn nested_to_flat (
list : & mut Vec < lsp ::SymbolInformation > ,
file : & lsp ::TextDocumentIdentifier ,
symbol : lsp ::DocumentSymbol ,
) {
#[ allow(deprecated) ]
list . push ( lsp ::SymbolInformation {
name : symbol . name ,
kind : symbol . kind ,
tags : symbol . tags ,
deprecated : symbol . deprecated ,
location : lsp ::Location ::new ( file . uri . clone ( ) , symbol . selection_range ) ,
container_name : None ,
} ) ;
for child in symbol . children . into_iter ( ) . flatten ( ) {
nested_to_flat ( list , file , child ) ;
}
}
2021-07-01 20:57:12 +02:00
let ( _ , doc ) = current! ( cx . editor ) ;
2021-06-12 14:45:21 +02:00
let language_server = match doc . language_server ( ) {
Some ( language_server ) = > language_server ,
None = > return ,
} ;
let offset_encoding = language_server . offset_encoding ( ) ;
let future = language_server . document_symbols ( doc . identifier ( ) ) ;
cx . callback (
future ,
move | editor : & mut Editor ,
compositor : & mut Compositor ,
response : Option < lsp ::DocumentSymbolResponse > | {
if let Some ( symbols ) = response {
// lsp has two ways to represent symbols (flat/nested)
// convert the nested variant to flat, so that we have a homogeneous list
let symbols = match symbols {
lsp ::DocumentSymbolResponse ::Flat ( symbols ) = > symbols ,
lsp ::DocumentSymbolResponse ::Nested ( symbols ) = > {
2021-06-18 00:09:10 +02:00
let ( _view , doc ) = current! ( editor ) ;
2021-06-12 14:45:21 +02:00
let mut flat_symbols = Vec ::new ( ) ;
for symbol in symbols {
nested_to_flat ( & mut flat_symbols , & doc . identifier ( ) , symbol )
}
flat_symbols
}
} ;
2021-11-14 16:12:56 +01:00
let mut picker = FilePicker ::new (
2021-06-12 14:45:21 +02:00
symbols ,
| symbol | ( & symbol . name ) . into ( ) ,
move | editor : & mut Editor , symbol , _action | {
push_jump ( editor ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( editor ) ;
2021-06-12 14:45:21 +02:00
if let Some ( range ) =
lsp_range_to_range ( doc . text ( ) , symbol . location . range , offset_encoding )
{
2021-08-22 08:00:07 +02:00
// we flip the range so that the cursor sits on the start of the symbol
// (for example start of the function).
doc . set_selection ( view . id , Selection ::single ( range . head , range . anchor ) ) ;
2021-06-12 14:45:21 +02:00
align_view ( doc , view , Align ::Center ) ;
}
} ,
2021-08-12 09:00:42 +02:00
move | _editor , symbol | {
let path = symbol . location . uri . to_file_path ( ) . unwrap ( ) ;
2021-08-24 06:21:06 +02:00
let line = Some ( (
symbol . location . range . start . line as usize ,
symbol . location . range . end . line as usize ,
) ) ;
2021-08-12 09:00:42 +02:00
Some ( ( path , line ) )
} ,
2021-06-12 14:45:21 +02:00
) ;
2021-11-14 16:12:56 +01:00
picker . truncate_start = false ;
compositor . push ( Box ::new ( picker ) )
}
} ,
)
}
fn workspace_symbol_picker ( cx : & mut Context ) {
let ( _ , doc ) = current! ( cx . editor ) ;
let language_server = match doc . language_server ( ) {
Some ( language_server ) = > language_server ,
None = > return ,
} ;
let offset_encoding = language_server . offset_encoding ( ) ;
let future = language_server . workspace_symbols ( " " . to_string ( ) ) ;
let current_path = doc_mut! ( cx . editor ) . path ( ) . cloned ( ) ;
cx . callback (
future ,
move | _editor : & mut Editor ,
compositor : & mut Compositor ,
response : Option < Vec < lsp ::SymbolInformation > > | {
if let Some ( symbols ) = response {
let mut picker = FilePicker ::new (
symbols ,
move | symbol | {
let path = symbol . location . uri . to_file_path ( ) . unwrap ( ) ;
if current_path . as_ref ( ) . map ( | p | p = = & path ) . unwrap_or ( false ) {
( & symbol . name ) . into ( )
} else {
let relative_path = helix_core ::path ::get_relative_path ( path . as_path ( ) )
. to_str ( )
. unwrap ( )
. to_owned ( ) ;
format! ( " {} ( {} ) " , & symbol . name , relative_path ) . into ( )
}
} ,
move | editor : & mut Editor , symbol , action | {
let path = symbol . location . uri . to_file_path ( ) . unwrap ( ) ;
editor . open ( path , action ) . expect ( " editor.open failed " ) ;
let ( view , doc ) = current! ( editor ) ;
if let Some ( range ) =
lsp_range_to_range ( doc . text ( ) , symbol . location . range , offset_encoding )
{
// we flip the range so that the cursor sits on the start of the symbol
// (for example start of the function).
doc . set_selection ( view . id , Selection ::single ( range . head , range . anchor ) ) ;
align_view ( doc , view , Align ::Center ) ;
}
} ,
move | _editor , symbol | {
let path = symbol . location . uri . to_file_path ( ) . unwrap ( ) ;
let line = Some ( (
symbol . location . range . start . line as usize ,
symbol . location . range . end . line as usize ,
) ) ;
Some ( ( path , line ) )
} ,
) ;
picker . truncate_start = false ;
2021-06-12 14:45:21 +02:00
compositor . push ( Box ::new ( picker ) )
}
} ,
)
}
2021-07-24 03:26:43 +02:00
pub fn code_action ( cx : & mut Context ) {
let ( view , doc ) = current! ( cx . editor ) ;
let language_server = match doc . language_server ( ) {
Some ( language_server ) = > language_server ,
None = > return ,
} ;
let range = range_to_lsp_range (
doc . text ( ) ,
doc . selection ( view . id ) . primary ( ) ,
language_server . offset_encoding ( ) ,
) ;
let future = language_server . code_actions ( doc . identifier ( ) , range ) ;
let offset_encoding = language_server . offset_encoding ( ) ;
cx . callback (
future ,
move | _editor : & mut Editor ,
compositor : & mut Compositor ,
response : Option < lsp ::CodeActionResponse > | {
if let Some ( actions ) = response {
let picker = Picker ::new (
2021-08-12 09:00:42 +02:00
true ,
2021-07-24 03:26:43 +02:00
actions ,
| action | match action {
lsp ::CodeActionOrCommand ::CodeAction ( action ) = > {
action . title . as_str ( ) . into ( )
}
lsp ::CodeActionOrCommand ::Command ( command ) = > command . title . as_str ( ) . into ( ) ,
} ,
move | editor , code_action , _action | match code_action {
lsp ::CodeActionOrCommand ::Command ( command ) = > {
log ::debug! ( " code action command: {:?} " , command ) ;
2021-12-21 10:21:45 +01:00
execute_lsp_command ( editor , command . clone ( ) ) ;
2021-07-24 03:26:43 +02:00
}
lsp ::CodeActionOrCommand ::CodeAction ( code_action ) = > {
log ::debug! ( " code action: {:?} " , code_action ) ;
if let Some ( ref workspace_edit ) = code_action . edit {
2021-12-21 10:21:45 +01:00
log ::debug! ( " edit: {:?} " , workspace_edit ) ;
apply_workspace_edit ( editor , offset_encoding , workspace_edit ) ;
}
// if code action provides both edit and command first the edit
// should be applied and then the command
if let Some ( command ) = & code_action . command {
execute_lsp_command ( editor , command . clone ( ) ) ;
2021-07-24 03:26:43 +02:00
}
}
} ,
) ;
compositor . push ( Box ::new ( picker ) )
}
} ,
)
}
2021-12-21 10:21:45 +01:00
pub fn execute_lsp_command ( editor : & mut Editor , cmd : lsp ::Command ) {
let ( _view , doc ) = current! ( editor ) ;
let language_server = match doc . language_server ( ) {
Some ( language_server ) = > language_server ,
None = > return ,
} ;
// the command is executed on the server and communicated back
// to the client asynchronously using workspace edits
let command_future = language_server . command ( cmd ) ;
tokio ::spawn ( async move {
let res = command_future . await ;
if let Err ( e ) = res {
log ::error! ( " execute LSP command: {} " , e ) ;
}
} ) ;
}
2021-11-08 16:17:54 +01:00
pub fn apply_document_resource_op ( op : & lsp ::ResourceOp ) -> std ::io ::Result < ( ) > {
use lsp ::ResourceOp ;
use std ::fs ;
match op {
ResourceOp ::Create ( op ) = > {
let path = op . uri . to_file_path ( ) . unwrap ( ) ;
let ignore_if_exists = if let Some ( options ) = & op . options {
! options . overwrite . unwrap_or ( false ) & & options . ignore_if_exists . unwrap_or ( false )
} else {
false
} ;
if ignore_if_exists & & path . exists ( ) {
Ok ( ( ) )
} else {
fs ::write ( & path , [ ] )
}
}
ResourceOp ::Delete ( op ) = > {
let path = op . uri . to_file_path ( ) . unwrap ( ) ;
if path . is_dir ( ) {
let recursive = if let Some ( options ) = & op . options {
options . recursive . unwrap_or ( false )
} else {
false
} ;
if recursive {
fs ::remove_dir_all ( & path )
} else {
fs ::remove_dir ( & path )
}
} else if path . is_file ( ) {
fs ::remove_file ( & path )
} else {
Ok ( ( ) )
}
}
ResourceOp ::Rename ( op ) = > {
let from = op . old_uri . to_file_path ( ) . unwrap ( ) ;
let to = op . new_uri . to_file_path ( ) . unwrap ( ) ;
let ignore_if_exists = if let Some ( options ) = & op . options {
! options . overwrite . unwrap_or ( false ) & & options . ignore_if_exists . unwrap_or ( false )
} else {
false
} ;
if ignore_if_exists & & to . exists ( ) {
Ok ( ( ) )
} else {
fs ::rename ( & from , & to )
}
}
}
}
2021-12-21 10:21:45 +01:00
pub fn apply_workspace_edit (
2021-07-24 03:26:43 +02:00
editor : & mut Editor ,
offset_encoding : OffsetEncoding ,
workspace_edit : & lsp ::WorkspaceEdit ,
) {
2021-11-08 16:17:54 +01:00
let mut apply_edits = | uri : & helix_lsp ::Url , text_edits : Vec < lsp ::TextEdit > | {
let path = uri
. to_file_path ( )
. expect ( " unable to convert URI to filepath " ) ;
let current_view_id = view! ( editor ) . id ;
let doc_id = editor . open ( path , Action ::Load ) . unwrap ( ) ;
let doc = editor
. document_mut ( doc_id )
. expect ( " Document for document_changes not found " ) ;
// Need to determine a view for apply/append_changes_to_history
let selections = doc . selections ( ) ;
let view_id = if selections . contains_key ( & current_view_id ) {
// use current if possible
current_view_id
} else {
// Hack: we take the first available view_id
selections
. keys ( )
. next ( )
. copied ( )
. expect ( " No view_id available " )
} ;
let transaction = helix_lsp ::util ::generate_transaction_from_edits (
doc . text ( ) ,
text_edits ,
offset_encoding ,
) ;
doc . apply ( & transaction , view_id ) ;
doc . append_changes_to_history ( view_id ) ;
} ;
2021-07-24 03:26:43 +02:00
if let Some ( ref changes ) = workspace_edit . changes {
log ::debug! ( " workspace changes: {:?} " , changes ) ;
2021-11-08 16:17:54 +01:00
for ( uri , text_edits ) in changes {
let text_edits = text_edits . to_vec ( ) ;
apply_edits ( uri , text_edits ) ;
}
2021-07-24 03:26:43 +02:00
return ;
// Not sure if it works properly, it'll be safer to just panic here to avoid breaking some parts of code on which code actions will be used
// TODO: find some example that uses workspace changes, and test it
// for (url, edits) in changes.iter() {
// let file_path = url.origin().ascii_serialization();
// let file_path = std::path::PathBuf::from(file_path);
// let file = std::fs::File::open(file_path).unwrap();
// let mut text = Rope::from_reader(file).unwrap();
// let transaction = edits_to_changes(&text, edits);
// transaction.apply(&mut text);
// }
}
if let Some ( ref document_changes ) = workspace_edit . document_changes {
match document_changes {
lsp ::DocumentChanges ::Edits ( document_edits ) = > {
for document_edit in document_edits {
let edits = document_edit
. edits
. iter ( )
. map ( | edit | match edit {
lsp ::OneOf ::Left ( text_edit ) = > text_edit ,
lsp ::OneOf ::Right ( annotated_text_edit ) = > {
& annotated_text_edit . text_edit
}
} )
2021-07-24 23:37:33 +02:00
. cloned ( )
2021-07-24 03:26:43 +02:00
. collect ( ) ;
2021-11-08 16:17:54 +01:00
apply_edits ( & document_edit . text_document . uri , edits ) ;
2021-07-24 03:26:43 +02:00
}
}
lsp ::DocumentChanges ::Operations ( operations ) = > {
log ::debug! ( " document changes - operations: {:?} " , operations ) ;
2021-11-08 16:17:54 +01:00
for operateion in operations {
match operateion {
lsp ::DocumentChangeOperation ::Op ( op ) = > {
apply_document_resource_op ( op ) . unwrap ( ) ;
}
lsp ::DocumentChangeOperation ::Edit ( document_edit ) = > {
let edits = document_edit
. edits
. iter ( )
. map ( | edit | match edit {
lsp ::OneOf ::Left ( text_edit ) = > text_edit ,
lsp ::OneOf ::Right ( annotated_text_edit ) = > {
& annotated_text_edit . text_edit
}
} )
. cloned ( )
. collect ( ) ;
apply_edits ( & document_edit . text_document . uri , edits ) ;
}
}
}
2021-07-24 03:26:43 +02:00
}
}
}
}
2021-07-18 06:24:07 +02:00
fn last_picker ( cx : & mut Context ) {
2021-08-08 06:50:03 +02:00
// TODO: last picker does not seem to work well with buffer_picker
2021-12-12 13:16:48 +01:00
cx . callback = Some ( Box ::new ( | compositor : & mut Compositor , _ | {
2021-07-18 06:24:07 +02:00
if let Some ( picker ) = compositor . last_picker . take ( ) {
compositor . push ( picker ) ;
}
// XXX: figure out how to show error when no last picker lifetime
// cx.editor.set_error("no last picker".to_owned())
} ) ) ;
}
2021-06-10 13:10:05 +02:00
// I inserts at the first nonwhitespace character of each line with a selection
2021-06-17 13:08:05 +02:00
fn prepend_to_line ( cx : & mut Context ) {
2021-07-03 17:39:26 +02:00
goto_first_nonwhitespace ( cx ) ;
2021-06-18 00:09:10 +02:00
let doc = doc_mut! ( cx . editor ) ;
2021-03-23 09:47:40 +01:00
enter_insert_mode ( doc ) ;
2020-09-13 13:04:16 +02:00
}
2020-09-07 10:08:28 +02:00
// A inserts at the end of each line with a selection
2021-06-17 13:08:05 +02:00
fn append_to_line ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-23 09:47:40 +01:00
enter_insert_mode ( doc ) ;
2020-09-13 13:04:16 +02:00
2021-07-24 02:06:14 +02:00
let selection = doc . selection ( view . id ) . clone ( ) . transform ( | range | {
let text = doc . text ( ) . slice ( .. ) ;
2021-07-26 17:40:30 +02:00
let line = range . cursor_line ( text ) ;
2021-07-24 02:06:14 +02:00
let pos = line_end_char_index ( & text , line ) ;
2021-03-30 11:27:45 +02:00
Range ::new ( pos , pos )
} ) ;
2021-04-01 03:39:46 +02:00
doc . set_selection ( view . id , selection ) ;
2020-09-13 13:04:16 +02:00
}
2020-09-13 12:51:42 +02:00
2021-06-30 05:52:09 +02:00
/// Sometimes when applying formatting changes we want to mark the buffer as unmodified, for
/// example because we just applied the same changes while saving.
enum Modified {
SetUnmodified ,
LeaveModified ,
}
2021-06-23 20:35:39 +02:00
// Creates an LspCallback that waits for formatting changes to be computed. When they're done,
// it applies them, but only if the doc hasn't changed.
2021-06-30 05:36:33 +02:00
//
// TODO: provide some way to cancel this, probably as part of a more general job cancellation
// scheme
2021-06-28 15:00:44 +02:00
async fn make_format_callback (
2021-06-23 20:35:39 +02:00
doc_id : DocumentId ,
doc_version : i32 ,
2021-06-30 05:52:09 +02:00
modified : Modified ,
2021-06-23 20:35:39 +02:00
format : impl Future < Output = helix_lsp ::util ::LspFormatting > + Send + 'static ,
2021-06-28 15:00:44 +02:00
) -> anyhow ::Result < job ::Callback > {
let format = format . await ;
2021-07-01 20:57:12 +02:00
let call : job ::Callback = Box ::new ( move | editor : & mut Editor , _compositor : & mut Compositor | {
2021-06-28 15:00:44 +02:00
let view_id = view! ( editor ) . id ;
if let Some ( doc ) = editor . document_mut ( doc_id ) {
if doc . version ( ) = = doc_version {
doc . apply ( & Transaction ::from ( format ) , view_id ) ;
doc . append_changes_to_history ( view_id ) ;
2021-06-30 05:52:09 +02:00
if let Modified ::SetUnmodified = modified {
2021-06-28 15:00:44 +02:00
doc . reset_modified ( ) ;
2021-06-23 20:35:39 +02:00
}
2021-06-28 15:00:44 +02:00
} else {
log ::info! ( " discarded formatting changes because the document changed " ) ;
}
}
} ) ;
Ok ( call )
2021-06-23 20:35:39 +02:00
}
2021-04-01 04:36:59 +02:00
enum Open {
Below ,
Above ,
}
fn open ( cx : & mut Context , open : Open ) {
2021-06-08 05:24:27 +02:00
let count = cx . count ( ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-23 09:47:40 +01:00
enter_insert_mode ( doc ) ;
2021-01-21 08:55:46 +01:00
2021-04-01 04:01:11 +02:00
let text = doc . text ( ) . slice ( .. ) ;
2021-06-16 18:11:54 +02:00
let contents = doc . text ( ) ;
2021-05-18 11:27:52 +02:00
let selection = doc . selection ( view . id ) ;
2020-09-13 12:51:42 +02:00
2021-05-18 11:27:52 +02:00
let mut ranges = SmallVec ::with_capacity ( selection . len ( ) ) ;
2021-06-16 18:11:54 +02:00
let mut offs = 0 ;
2021-05-18 11:27:52 +02:00
2021-06-16 18:11:54 +02:00
let mut transaction = Transaction ::change_by_selection ( contents , selection , | range | {
2021-07-26 17:40:30 +02:00
let line = range . cursor_line ( text ) ;
2020-09-13 12:51:42 +02:00
2021-06-16 18:11:54 +02:00
let line = match open {
// adjust position to the end of the line (next line - 1)
Open ::Below = > line + 1 ,
// adjust position to the end of the previous line (current line - 1)
Open ::Above = > line ,
} ;
2021-04-01 04:36:59 +02:00
2021-06-24 08:53:22 +02:00
// Index to insert newlines after, as well as the char width
// to use to compensate for those inserted newlines.
let ( line_end_index , line_end_offset_width ) = if line = = 0 {
( 0 , 0 )
} else {
(
line_end_char_index ( & doc . text ( ) . slice ( .. ) , line . saturating_sub ( 1 ) ) ,
doc . line_ending . len_chars ( ) ,
)
} ;
2021-03-18 05:45:57 +01:00
2021-06-16 18:11:54 +02:00
// TODO: share logic with insert_newline for indentation
let indent_level = indent ::suggested_indent_for_pos (
doc . language_config ( ) ,
doc . syntax ( ) ,
text ,
2021-06-23 17:58:14 +02:00
line_end_index ,
2021-06-16 18:11:54 +02:00
true ,
) ;
let indent = doc . indent_unit ( ) . repeat ( indent_level ) ;
2021-06-18 10:50:25 +02:00
let indent_len = indent . len ( ) ;
let mut text = String ::with_capacity ( 1 + indent_len ) ;
2021-06-21 20:59:03 +02:00
text . push_str ( doc . line_ending . as_str ( ) ) ;
2021-06-16 18:11:54 +02:00
text . push_str ( & indent ) ;
let text = text . repeat ( count ) ;
2021-06-17 08:19:02 +02:00
// calculate new selection ranges
2021-06-24 08:53:22 +02:00
let pos = offs + line_end_index + line_end_offset_width ;
2021-06-17 08:19:02 +02:00
for i in 0 .. count {
2021-06-18 10:50:25 +02:00
// pos -> beginning of reference line,
// + (i * (1+indent_len)) -> beginning of i'th line from pos
// + indent_len -> -> indent for i'th line
ranges . push ( Range ::point ( pos + ( i * ( 1 + indent_len ) ) + indent_len ) ) ;
2021-06-17 08:19:02 +02:00
}
2021-06-16 18:11:54 +02:00
offs + = text . chars ( ) . count ( ) ;
2021-06-23 17:58:14 +02:00
( line_end_index , line_end_index , Some ( text . into ( ) ) )
2021-06-16 18:11:54 +02:00
} ) ;
2020-09-13 12:51:42 +02:00
2021-06-16 18:11:54 +02:00
transaction = transaction . with_selection ( Selection ::new ( ranges , selection . primary_index ( ) ) ) ;
2020-09-13 12:51:42 +02:00
2021-04-01 03:39:46 +02:00
doc . apply ( & transaction , view . id ) ;
2020-09-13 12:51:42 +02:00
}
2021-04-01 04:36:59 +02:00
// o inserts a new line after each line with a selection
2021-12-01 05:40:54 +01:00
fn open_below ( cx : & mut Context ) {
2021-04-01 04:36:59 +02:00
open ( cx , Open ::Below )
}
2020-09-13 12:51:42 +02:00
// O inserts a new line before each line with a selection
2021-12-01 05:40:54 +01:00
fn open_above ( cx : & mut Context ) {
2021-04-01 04:36:59 +02:00
open ( cx , Open ::Above )
2021-03-19 16:16:34 +01:00
}
2020-09-07 10:08:28 +02:00
2021-06-17 13:08:05 +02:00
fn normal_mode ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2020-10-07 11:31:16 +02:00
2021-09-08 09:53:10 +02:00
if doc . mode = = Mode ::Normal {
return ;
}
2021-02-21 11:04:31 +01:00
doc . mode = Mode ::Normal ;
2020-10-07 11:31:16 +02:00
2021-12-21 10:17:55 +01:00
try_restore_indent ( doc , view . id ) ;
2021-04-01 03:39:46 +02:00
doc . append_changes_to_history ( view . id ) ;
2021-01-21 08:55:46 +01:00
2020-10-01 11:44:12 +02:00
// if leaving append mode, move cursor back by 1
2021-01-21 09:00:08 +01:00
if doc . restore_cursor {
2021-02-18 10:34:22 +01:00
let text = doc . text ( ) . slice ( .. ) ;
2021-07-24 02:06:14 +02:00
let selection = doc . selection ( view . id ) . clone ( ) . transform ( | range | {
2020-10-01 11:44:12 +02:00
Range ::new (
range . from ( ) ,
graphemes ::prev_grapheme_boundary ( text , range . to ( ) ) ,
)
} ) ;
2021-04-01 03:39:46 +02:00
doc . set_selection ( view . id , selection ) ;
2020-10-01 11:44:12 +02:00
2021-01-21 09:00:08 +01:00
doc . restore_cursor = false ;
2020-10-01 11:44:12 +02:00
}
2020-09-05 15:01:05 +02:00
}
2021-12-21 10:17:55 +01:00
fn try_restore_indent ( doc : & mut Document , view_id : ViewId ) {
let doc_changes = doc . changes ( ) . changes ( ) ;
let text = doc . text ( ) . slice ( .. ) ;
let pos = doc . selection ( view_id ) . primary ( ) . cursor ( text ) ;
let mut can_restore_indent = false ;
// Removes trailing whitespace if insert mode is exited after starting a blank new line.
use helix_core ::chars ::char_is_whitespace ;
use helix_core ::Operation ;
if let [ Operation ::Retain ( move_pos ) , Operation ::Insert ( ref inserted_str ) , Operation ::Retain ( _ ) ] =
doc_changes
{
if move_pos + inserted_str . len32 ( ) as usize = = pos
& & inserted_str . starts_with ( '\n' )
& & inserted_str . chars ( ) . skip ( 1 ) . all ( char_is_whitespace )
{
can_restore_indent = true ;
}
}
if can_restore_indent {
let transaction =
Transaction ::change_by_selection ( doc . text ( ) , doc . selection ( view_id ) , | range | {
let line_start_pos = text . line_to_char ( range . cursor_line ( text ) ) ;
( line_start_pos , pos , None )
} ) ;
doc . apply ( & transaction , view_id ) ;
}
}
2021-03-29 09:32:42 +02:00
// Store a jump on the jumplist.
2021-05-06 10:15:49 +02:00
fn push_jump ( editor : & mut Editor ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( editor ) ;
2021-06-08 18:40:38 +02:00
let jump = ( doc . id ( ) , doc . selection ( view . id ) . clone ( ) ) ;
2021-04-01 03:39:46 +02:00
view . jumps . push ( jump ) ;
2021-03-29 09:32:42 +02:00
}
2021-07-28 09:55:34 +02:00
fn goto_line ( cx : & mut Context ) {
2021-12-03 04:48:07 +01:00
goto_line_impl ( cx . editor , cx . count )
2021-11-24 07:26:55 +01:00
}
2021-07-28 09:55:34 +02:00
2021-11-24 07:26:55 +01:00
fn goto_line_impl ( editor : & mut Editor , count : Option < NonZeroUsize > ) {
if let Some ( count ) = count {
push_jump ( editor ) ;
let ( view , doc ) = current! ( editor ) ;
2021-08-03 15:49:40 +02:00
let max_line = if doc . text ( ) . line ( doc . text ( ) . len_lines ( ) - 1 ) . len_chars ( ) = = 0 {
// If the last line is blank, don't jump to it.
doc . text ( ) . len_lines ( ) . saturating_sub ( 2 )
} else {
doc . text ( ) . len_lines ( ) - 1
} ;
let line_idx = std ::cmp ::min ( count . get ( ) - 1 , max_line ) ;
2021-10-29 03:07:07 +02:00
let text = doc . text ( ) . slice ( .. ) ;
2021-07-28 09:55:34 +02:00
let pos = doc . text ( ) . line_to_char ( line_idx ) ;
2021-10-29 03:07:07 +02:00
let selection = doc
. selection ( view . id )
. clone ( )
. transform ( | range | range . put_cursor ( text , pos , doc . mode = = Mode ::Select ) ) ;
doc . set_selection ( view . id , selection ) ;
2021-07-28 09:55:34 +02:00
}
}
2021-08-03 15:49:40 +02:00
fn goto_last_line ( cx : & mut Context ) {
push_jump ( cx . editor ) ;
let ( view , doc ) = current! ( cx . editor ) ;
let line_idx = if doc . text ( ) . line ( doc . text ( ) . len_lines ( ) - 1 ) . len_chars ( ) = = 0 {
// If the last line is blank, don't jump to it.
doc . text ( ) . len_lines ( ) . saturating_sub ( 2 )
} else {
doc . text ( ) . len_lines ( ) - 1
} ;
2021-10-29 03:07:07 +02:00
let text = doc . text ( ) . slice ( .. ) ;
2021-08-03 15:49:40 +02:00
let pos = doc . text ( ) . line_to_char ( line_idx ) ;
2021-10-29 03:07:07 +02:00
let selection = doc
. selection ( view . id )
. clone ( )
. transform ( | range | range . put_cursor ( text , pos , doc . mode = = Mode ::Select ) ) ;
doc . set_selection ( view . id , selection ) ;
2021-08-03 15:49:40 +02:00
}
2021-07-03 17:39:26 +02:00
fn goto_last_accessed_file ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let alternate_file = view! ( cx . editor ) . last_accessed_doc ;
2021-06-12 14:21:06 +02:00
if let Some ( alt ) = alternate_file {
cx . editor . switch ( alt , Action ::Replace ) ;
} else {
2021-06-12 17:59:04 +02:00
cx . editor . set_error ( " no last accessed buffer " . to_owned ( ) )
2021-06-12 14:21:06 +02:00
}
}
2021-11-14 16:11:53 +01:00
fn goto_last_modification ( cx : & mut Context ) {
let ( view , doc ) = current! ( cx . editor ) ;
let pos = doc . history . get_mut ( ) . last_edit_pos ( ) ;
let text = doc . text ( ) . slice ( .. ) ;
if let Some ( pos ) = pos {
let selection = doc
. selection ( view . id )
. clone ( )
. transform ( | range | range . put_cursor ( text , pos , doc . mode = = Mode ::Select ) ) ;
doc . set_selection ( view . id , selection ) ;
}
}
2021-12-02 05:46:57 +01:00
fn goto_last_modified_file ( cx : & mut Context ) {
let view = view! ( cx . editor ) ;
let alternate_file = view
. last_modified_docs
. into_iter ( )
. flatten ( )
. find ( | & id | id ! = view . doc ) ;
if let Some ( alt ) = alternate_file {
cx . editor . switch ( alt , Action ::Replace ) ;
} else {
cx . editor . set_error ( " no last modified buffer " . to_owned ( ) )
}
}
2021-07-03 17:39:26 +02:00
fn select_mode ( cx : & mut Context ) {
2021-07-02 18:51:29 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-07-24 02:06:14 +02:00
let text = doc . text ( ) . slice ( .. ) ;
2021-07-02 18:51:29 +02:00
2021-07-29 00:57:00 +02:00
// Make sure end-of-document selections are also 1-width.
2021-07-02 18:51:29 +02:00
// (With the exception of being in an empty document, of course.)
2021-07-24 02:06:14 +02:00
let selection = doc . selection ( view . id ) . clone ( ) . transform ( | range | {
if range . is_empty ( ) & & range . head = = text . len_chars ( ) {
Range ::new (
graphemes ::prev_grapheme_boundary ( text , range . anchor ) ,
range . head ,
)
} else {
2021-07-29 00:57:00 +02:00
range
2021-07-24 02:06:14 +02:00
}
} ) ;
doc . set_selection ( view . id , selection ) ;
2021-07-02 18:51:29 +02:00
2021-07-03 17:39:26 +02:00
doc_mut! ( cx . editor ) . mode = Mode ::Select ;
}
fn exit_select_mode ( cx : & mut Context ) {
2021-08-10 03:57:07 +02:00
let doc = doc_mut! ( cx . editor ) ;
if doc . mode = = Mode ::Select {
doc . mode = Mode ::Normal ;
}
2021-07-03 17:39:26 +02:00
}
2021-06-15 06:29:03 +02:00
fn goto_impl (
2021-05-06 10:15:49 +02:00
editor : & mut Editor ,
compositor : & mut Compositor ,
locations : Vec < lsp ::Location > ,
offset_encoding : OffsetEncoding ,
) {
push_jump ( editor ) ;
2021-03-29 09:32:42 +02:00
2021-04-14 08:30:15 +02:00
fn jump_to (
editor : & mut Editor ,
location : & lsp ::Location ,
offset_encoding : OffsetEncoding ,
action : Action ,
) {
2021-06-25 06:20:15 +02:00
let path = location
. uri
. to_file_path ( )
. expect ( " unable to convert URI to filepath " ) ;
2021-07-01 20:57:12 +02:00
let _id = editor . open ( path , action ) . expect ( " editor.open failed " ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( editor ) ;
2021-03-29 08:44:03 +02:00
let definition_pos = location . range . start ;
2021-04-14 08:30:15 +02:00
// TODO: convert inside server
2021-06-12 09:04:30 +02:00
let new_pos =
if let Some ( new_pos ) = lsp_pos_to_pos ( doc . text ( ) , definition_pos , offset_encoding ) {
new_pos
} else {
return ;
} ;
2021-04-01 03:39:46 +02:00
doc . set_selection ( view . id , Selection ::point ( new_pos ) ) ;
2021-05-08 08:36:27 +02:00
align_view ( doc , view , Align ::Center ) ;
2021-03-29 08:44:03 +02:00
}
2021-02-28 00:39:13 +01:00
2021-08-03 02:29:03 +02:00
let cwdir = std ::env ::current_dir ( ) . expect ( " couldn't determine current directory " ) ;
2021-03-11 05:15:25 +01:00
match locations . as_slice ( ) {
2021-02-28 00:39:13 +01:00
[ location ] = > {
2021-05-06 10:15:49 +02:00
jump_to ( editor , location , offset_encoding , Action ::Replace ) ;
2021-02-28 00:39:13 +01:00
}
2021-05-08 08:39:42 +02:00
[ ] = > {
editor . set_error ( " No definition found. " . to_string ( ) ) ;
}
2021-03-11 05:15:25 +01:00
_locations = > {
2021-08-12 09:00:42 +02:00
let picker = FilePicker ::new (
2021-03-11 05:15:25 +01:00
locations ,
2021-08-03 02:29:03 +02:00
move | location | {
let file : Cow < '_ , str > = ( location . uri . scheme ( ) = = " file " )
. then ( | | {
location
. uri
. to_file_path ( )
. map ( | path | {
// strip root prefix
path . strip_prefix ( & cwdir )
. map ( | path | path . to_path_buf ( ) )
. unwrap_or ( path )
} )
. ok ( )
. and_then ( | path | path . to_str ( ) . map ( | path | path . to_owned ( ) . into ( ) ) )
} )
. flatten ( )
. unwrap_or_else ( | | location . uri . as_str ( ) . into ( ) ) ;
2021-03-29 08:44:03 +02:00
let line = location . range . start . line ;
2021-02-28 00:39:13 +01:00
format! ( " {} : {} " , file , line ) . into ( )
} ,
2021-04-14 08:30:15 +02:00
move | editor : & mut Editor , location , action | {
jump_to ( editor , location , offset_encoding , action )
} ,
2021-08-12 09:00:42 +02:00
| _editor , location | {
let path = location . uri . to_file_path ( ) . unwrap ( ) ;
2021-08-24 06:21:06 +02:00
let line = Some ( (
location . range . start . line as usize ,
location . range . end . line as usize ,
) ) ;
2021-08-12 09:00:42 +02:00
Some ( ( path , line ) )
} ,
2021-02-28 00:39:13 +01:00
) ;
2021-05-06 10:15:49 +02:00
compositor . push ( Box ::new ( picker ) ) ;
2021-02-28 00:39:13 +01:00
}
}
2021-02-21 23:43:28 +01:00
}
2021-02-21 23:22:38 +01:00
2021-06-17 13:08:05 +02:00
fn goto_definition ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-18 06:40:22 +01:00
let language_server = match doc . language_server ( ) {
2021-03-07 19:41:49 +01:00
Some ( language_server ) = > language_server ,
None = > return ,
} ;
2021-04-14 08:30:15 +02:00
let offset_encoding = language_server . offset_encoding ( ) ;
2021-07-19 18:25:10 +02:00
let pos = pos_to_lsp_pos (
doc . text ( ) ,
2021-07-26 17:40:30 +02:00
doc . selection ( view . id )
. primary ( )
. cursor ( doc . text ( ) . slice ( .. ) ) ,
2021-07-19 18:25:10 +02:00
offset_encoding ,
) ;
2021-03-07 19:41:49 +01:00
2021-06-18 05:39:37 +02:00
let future = language_server . goto_definition ( doc . identifier ( ) , pos , None ) ;
2021-05-06 10:15:49 +02:00
cx . callback (
future ,
move | editor : & mut Editor ,
compositor : & mut Compositor ,
response : Option < lsp ::GotoDefinitionResponse > | {
let items = match response {
Some ( lsp ::GotoDefinitionResponse ::Scalar ( location ) ) = > vec! [ location ] ,
Some ( lsp ::GotoDefinitionResponse ::Array ( locations ) ) = > locations ,
Some ( lsp ::GotoDefinitionResponse ::Link ( locations ) ) = > locations
. into_iter ( )
. map ( | location_link | lsp ::Location {
uri : location_link . target_uri ,
range : location_link . target_range ,
} )
. collect ( ) ,
None = > Vec ::new ( ) ,
} ;
2021-06-15 06:29:03 +02:00
goto_impl ( editor , compositor , items , offset_encoding ) ;
2021-05-06 10:15:49 +02:00
} ,
) ;
2021-03-07 19:41:49 +01:00
}
2021-06-17 13:08:05 +02:00
fn goto_type_definition ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-18 06:40:22 +01:00
let language_server = match doc . language_server ( ) {
2021-03-07 19:41:49 +01:00
Some ( language_server ) = > language_server ,
None = > return ,
} ;
2021-04-14 08:30:15 +02:00
let offset_encoding = language_server . offset_encoding ( ) ;
2021-07-19 18:25:10 +02:00
let pos = pos_to_lsp_pos (
doc . text ( ) ,
2021-07-26 17:40:30 +02:00
doc . selection ( view . id )
. primary ( )
. cursor ( doc . text ( ) . slice ( .. ) ) ,
2021-07-19 18:25:10 +02:00
offset_encoding ,
) ;
2021-03-07 19:41:49 +01:00
2021-06-18 05:39:37 +02:00
let future = language_server . goto_type_definition ( doc . identifier ( ) , pos , None ) ;
2021-05-06 10:15:49 +02:00
cx . callback (
future ,
move | editor : & mut Editor ,
compositor : & mut Compositor ,
response : Option < lsp ::GotoDefinitionResponse > | {
let items = match response {
Some ( lsp ::GotoDefinitionResponse ::Scalar ( location ) ) = > vec! [ location ] ,
Some ( lsp ::GotoDefinitionResponse ::Array ( locations ) ) = > locations ,
Some ( lsp ::GotoDefinitionResponse ::Link ( locations ) ) = > locations
. into_iter ( )
. map ( | location_link | lsp ::Location {
uri : location_link . target_uri ,
range : location_link . target_range ,
} )
. collect ( ) ,
None = > Vec ::new ( ) ,
} ;
2021-06-15 06:29:03 +02:00
goto_impl ( editor , compositor , items , offset_encoding ) ;
2021-05-06 10:15:49 +02:00
} ,
) ;
2021-03-07 19:41:49 +01:00
}
2021-06-17 13:08:05 +02:00
fn goto_implementation ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-18 06:40:22 +01:00
let language_server = match doc . language_server ( ) {
2021-03-07 19:41:49 +01:00
Some ( language_server ) = > language_server ,
None = > return ,
} ;
2021-04-14 08:30:15 +02:00
let offset_encoding = language_server . offset_encoding ( ) ;
2021-07-19 18:25:10 +02:00
let pos = pos_to_lsp_pos (
doc . text ( ) ,
2021-07-26 17:40:30 +02:00
doc . selection ( view . id )
. primary ( )
. cursor ( doc . text ( ) . slice ( .. ) ) ,
2021-07-19 18:25:10 +02:00
offset_encoding ,
) ;
2021-03-07 19:41:49 +01:00
2021-06-18 05:39:37 +02:00
let future = language_server . goto_implementation ( doc . identifier ( ) , pos , None ) ;
2021-05-06 10:15:49 +02:00
cx . callback (
future ,
move | editor : & mut Editor ,
compositor : & mut Compositor ,
response : Option < lsp ::GotoDefinitionResponse > | {
let items = match response {
Some ( lsp ::GotoDefinitionResponse ::Scalar ( location ) ) = > vec! [ location ] ,
Some ( lsp ::GotoDefinitionResponse ::Array ( locations ) ) = > locations ,
Some ( lsp ::GotoDefinitionResponse ::Link ( locations ) ) = > locations
. into_iter ( )
. map ( | location_link | lsp ::Location {
uri : location_link . target_uri ,
range : location_link . target_range ,
} )
. collect ( ) ,
None = > Vec ::new ( ) ,
} ;
2021-06-15 06:29:03 +02:00
goto_impl ( editor , compositor , items , offset_encoding ) ;
2021-05-06 10:15:49 +02:00
} ,
) ;
2021-03-07 19:41:49 +01:00
}
2021-06-17 13:08:05 +02:00
fn goto_reference ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-18 06:40:22 +01:00
let language_server = match doc . language_server ( ) {
2021-03-07 19:41:49 +01:00
Some ( language_server ) = > language_server ,
None = > return ,
} ;
2021-04-14 08:30:15 +02:00
let offset_encoding = language_server . offset_encoding ( ) ;
2021-07-19 18:25:10 +02:00
let pos = pos_to_lsp_pos (
doc . text ( ) ,
2021-07-26 17:40:30 +02:00
doc . selection ( view . id )
. primary ( )
. cursor ( doc . text ( ) . slice ( .. ) ) ,
2021-07-19 18:25:10 +02:00
offset_encoding ,
) ;
2021-03-07 19:41:49 +01:00
2021-06-18 05:39:37 +02:00
let future = language_server . goto_reference ( doc . identifier ( ) , pos , None ) ;
2021-05-06 10:15:49 +02:00
cx . callback (
future ,
move | editor : & mut Editor ,
compositor : & mut Compositor ,
items : Option < Vec < lsp ::Location > > | {
2021-06-15 06:29:03 +02:00
goto_impl (
2021-05-06 10:15:49 +02:00
editor ,
compositor ,
items . unwrap_or_default ( ) ,
offset_encoding ,
) ;
} ,
) ;
2021-03-07 19:41:49 +01:00
}
2021-06-06 11:59:32 +02:00
fn goto_pos ( editor : & mut Editor , pos : usize ) {
push_jump ( editor ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( editor ) ;
2021-06-06 11:59:32 +02:00
doc . set_selection ( view . id , Selection ::point ( pos ) ) ;
align_view ( doc , view , Align ::Center ) ;
}
2021-06-17 13:08:05 +02:00
fn goto_first_diag ( cx : & mut Context ) {
2021-06-06 11:59:32 +02:00
let editor = & mut cx . editor ;
2021-07-01 20:57:12 +02:00
let ( _ , doc ) = current! ( editor ) ;
2021-06-06 11:59:32 +02:00
2021-11-06 10:04:04 +01:00
let pos = match doc . diagnostics ( ) . first ( ) {
Some ( diag ) = > diag . range . start ,
None = > return ,
2021-06-06 11:59:32 +02:00
} ;
2021-11-06 10:04:04 +01:00
goto_pos ( editor , pos ) ;
2021-06-06 11:59:32 +02:00
}
2021-06-17 13:08:05 +02:00
fn goto_last_diag ( cx : & mut Context ) {
2021-06-06 11:59:32 +02:00
let editor = & mut cx . editor ;
2021-07-01 20:57:12 +02:00
let ( _ , doc ) = current! ( editor ) ;
2021-06-06 11:59:32 +02:00
2021-11-06 10:04:04 +01:00
let pos = match doc . diagnostics ( ) . last ( ) {
Some ( diag ) = > diag . range . start ,
None = > return ,
2021-06-06 11:59:32 +02:00
} ;
2021-11-06 10:04:04 +01:00
goto_pos ( editor , pos ) ;
2021-06-06 11:59:32 +02:00
}
2021-06-17 13:08:05 +02:00
fn goto_next_diag ( cx : & mut Context ) {
2021-06-06 11:59:32 +02:00
let editor = & mut cx . editor ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( editor ) ;
2021-06-06 11:59:32 +02:00
2021-07-26 17:40:30 +02:00
let cursor_pos = doc
. selection ( view . id )
. primary ( )
. cursor ( doc . text ( ) . slice ( .. ) ) ;
2021-11-06 10:04:04 +01:00
let diag = doc
2021-06-06 11:59:32 +02:00
. diagnostics ( )
. iter ( )
2021-11-06 10:04:04 +01:00
. find ( | diag | diag . range . start > cursor_pos )
. or_else ( | | doc . diagnostics ( ) . first ( ) ) ;
let pos = match diag {
Some ( diag ) = > diag . range . start ,
None = > return ,
2021-06-06 11:59:32 +02:00
} ;
2021-11-06 10:04:04 +01:00
goto_pos ( editor , pos ) ;
2021-06-06 11:59:32 +02:00
}
2021-06-17 13:08:05 +02:00
fn goto_prev_diag ( cx : & mut Context ) {
2021-06-06 11:59:32 +02:00
let editor = & mut cx . editor ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( editor ) ;
2021-06-06 11:59:32 +02:00
2021-07-26 17:40:30 +02:00
let cursor_pos = doc
. selection ( view . id )
. primary ( )
. cursor ( doc . text ( ) . slice ( .. ) ) ;
2021-11-06 10:04:04 +01:00
let diag = doc
2021-06-06 11:59:32 +02:00
. diagnostics ( )
. iter ( )
. rev ( )
2021-11-06 10:04:04 +01:00
. find ( | diag | diag . range . start < cursor_pos )
. or_else ( | | doc . diagnostics ( ) . last ( ) ) ;
let pos = match diag {
Some ( diag ) = > diag . range . start ,
None = > return ,
2021-06-06 11:59:32 +02:00
} ;
2021-11-06 10:04:04 +01:00
goto_pos ( editor , pos ) ;
2021-06-06 11:59:32 +02:00
}
2021-06-17 13:08:05 +02:00
fn signature_help ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-24 10:17:00 +01:00
let language_server = match doc . language_server ( ) {
Some ( language_server ) = > language_server ,
None = > return ,
} ;
2021-04-14 08:30:15 +02:00
let pos = pos_to_lsp_pos (
doc . text ( ) ,
2021-07-26 17:40:30 +02:00
doc . selection ( view . id )
. primary ( )
. cursor ( doc . text ( ) . slice ( .. ) ) ,
2021-04-14 08:30:15 +02:00
language_server . offset_encoding ( ) ,
) ;
2021-03-24 10:17:00 +01:00
2021-06-18 05:39:37 +02:00
let future = language_server . text_document_signature_help ( doc . identifier ( ) , pos , None ) ;
2021-03-24 10:17:00 +01:00
2021-05-06 10:15:49 +02:00
cx . callback (
future ,
2021-07-01 20:57:12 +02:00
move | _editor : & mut Editor ,
_compositor : & mut Compositor ,
2021-05-06 10:15:49 +02:00
response : Option < lsp ::SignatureHelp > | {
if let Some ( signature_help ) = response {
log ::info! ( " {:?} " , signature_help ) ;
// signatures
// active_signature
// active_parameter
// render as:
// signature
// ----------
// doc
// with active param highlighted
}
} ,
) ;
2021-03-24 10:17:00 +01:00
}
2020-10-14 05:09:55 +02:00
// NOTE: Transactions in this module get appended to history when we switch back to normal mode.
pub mod insert {
use super ::* ;
2021-03-22 04:18:48 +01:00
pub type Hook = fn ( & Rope , & Selection , char ) -> Option < Transaction > ;
2021-03-24 10:17:00 +01:00
pub type PostHook = fn ( & mut Context , char ) ;
2021-03-22 04:18:48 +01:00
2021-10-18 08:14:50 +02:00
// It trigger completion when idle timer reaches deadline
// Only trigger completion if the word under cursor is longer than n characters
pub fn idle_completion ( cx : & mut Context ) {
let ( view , doc ) = current! ( cx . editor ) ;
let text = doc . text ( ) . slice ( .. ) ;
let cursor = doc . selection ( view . id ) . primary ( ) . cursor ( text ) ;
use helix_core ::chars ::char_is_word ;
let mut iter = text . chars_at ( cursor ) ;
iter . reverse ( ) ;
for _ in 0 .. cx . editor . config . completion_trigger_len {
match iter . next ( ) {
Some ( c ) if char_is_word ( c ) = > { }
_ = > return ,
}
}
super ::completion ( cx ) ;
}
fn language_server_completion ( cx : & mut Context , ch : char ) {
2021-03-24 10:17:00 +01:00
// if ch matches completion char, trigger completion
2021-06-18 00:09:10 +02:00
let doc = doc_mut! ( cx . editor ) ;
2021-03-24 10:17:00 +01:00
let language_server = match doc . language_server ( ) {
Some ( language_server ) = > language_server ,
None = > return ,
} ;
let capabilities = language_server . capabilities ( ) ;
2021-10-18 08:14:50 +02:00
if let Some ( lsp ::CompletionOptions {
trigger_characters : Some ( triggers ) ,
2021-03-24 10:17:00 +01:00
..
2021-10-18 08:14:50 +02:00
} ) = & capabilities . completion_provider
2021-03-24 10:17:00 +01:00
{
// TODO: what if trigger is multiple chars long
2021-10-18 08:14:50 +02:00
if triggers . iter ( ) . any ( | trigger | trigger . contains ( ch ) ) {
cx . editor . clear_idle_timer ( ) ;
2021-03-24 10:17:00 +01:00
super ::completion ( cx ) ;
}
}
}
fn signature_help ( cx : & mut Context , ch : char ) {
// if ch matches signature_help char, trigger
2021-06-18 00:09:10 +02:00
let doc = doc_mut! ( cx . editor ) ;
2021-03-24 10:17:00 +01:00
let language_server = match doc . language_server ( ) {
Some ( language_server ) = > language_server ,
None = > return ,
} ;
let capabilities = language_server . capabilities ( ) ;
if let lsp ::ServerCapabilities {
signature_help_provider :
Some ( lsp ::SignatureHelpOptions {
trigger_characters : Some ( triggers ) ,
// TODO: retrigger_characters
..
} ) ,
..
} = capabilities
{
// TODO: what if trigger is multiple chars long
let is_trigger = triggers . iter ( ) . any ( | trigger | trigger . contains ( ch ) ) ;
if is_trigger {
super ::signature_help ( cx ) ;
}
}
2021-05-03 10:56:02 +02:00
2021-05-05 09:25:17 +02:00
// SignatureHelp {
// signatures: [
// SignatureInformation {
// label: "fn open(&mut self, path: PathBuf, action: Action) -> Result<DocumentId, Error>",
// documentation: None,
// parameters: Some(
// [ParameterInformation { label: Simple("path: PathBuf"), documentation: None },
// ParameterInformation { label: Simple("action: Action"), documentation: None }]
// ),
// active_parameter: Some(0)
// }
// ],
// active_signature: None, active_parameter: Some(0)
// }
2021-03-24 10:17:00 +01:00
}
2021-05-03 10:56:02 +02:00
// The default insert hook: simply insert the character
2021-05-05 09:25:17 +02:00
#[ allow(clippy::unnecessary_wraps) ] // need to use Option<> because of the Hook signature
2021-05-03 10:56:02 +02:00
fn insert ( doc : & Rope , selection : & Selection , ch : char ) -> Option < Transaction > {
2021-12-13 16:58:58 +01:00
let cursors = selection . clone ( ) . cursors ( doc . slice ( .. ) ) ;
2021-05-03 10:56:02 +02:00
let t = Tendril ::from_char ( ch ) ;
2021-12-13 16:58:58 +01:00
let transaction = Transaction ::insert ( doc , & cursors , t ) ;
2021-05-03 10:56:02 +02:00
Some ( transaction )
}
use helix_core ::auto_pairs ;
2021-03-24 10:17:00 +01:00
2020-10-30 06:09:59 +01:00
pub fn insert_char ( cx : & mut Context , c : char ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-22 04:18:48 +01:00
2021-09-24 03:28:44 +02:00
let hooks : & [ Hook ] = match cx . editor . config . auto_pairs {
true = > & [ auto_pairs ::hook , insert ] ,
false = > & [ insert ] ,
} ;
2021-05-03 10:56:02 +02:00
let text = doc . text ( ) ;
2021-12-13 16:58:58 +01:00
let selection = doc . selection ( view . id ) ;
2021-05-03 10:56:02 +02:00
2021-03-22 04:18:48 +01:00
// run through insert hooks, stopping on the first one that returns Some(t)
2021-09-24 03:28:44 +02:00
for hook in hooks {
2021-12-13 16:58:58 +01:00
if let Some ( transaction ) = hook ( text , selection , c ) {
2021-04-01 03:39:46 +02:00
doc . apply ( & transaction , view . id ) ;
2021-05-03 10:56:02 +02:00
break ;
2021-03-22 04:18:48 +01:00
}
}
2021-03-24 10:17:00 +01:00
// TODO: need a post insert hook too for certain triggers (autocomplete, signature help, etc)
// this could also generically look at Transaction, but it's a bit annoying to look at
// Operation instead of Change.
2021-10-18 08:14:50 +02:00
for hook in & [ language_server_completion , signature_help ] {
// for hook in &[signature_help] {
2021-03-24 10:17:00 +01:00
hook ( cx , c ) ;
}
2020-10-14 05:09:55 +02:00
}
2020-09-13 04:32:37 +02:00
2020-10-30 06:09:59 +01:00
pub fn insert_tab ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-22 05:47:39 +01:00
// TODO: round out to nearest indentation level (for example a line with 3 spaces should
// indent by one to reach 4 spaces).
let indent = Tendril ::from ( doc . indent_unit ( ) ) ;
2021-07-23 23:27:12 +02:00
let transaction = Transaction ::insert (
doc . text ( ) ,
& doc . selection ( view . id ) . clone ( ) . cursors ( doc . text ( ) . slice ( .. ) ) ,
indent ,
) ;
2021-04-01 03:39:46 +02:00
doc . apply ( & transaction , view . id ) ;
2020-10-14 05:09:55 +02:00
}
2020-10-01 21:16:24 +02:00
2020-10-30 06:09:59 +01:00
pub fn insert_newline ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-18 05:45:57 +01:00
let text = doc . text ( ) . slice ( .. ) ;
2021-05-06 15:59:59 +02:00
let contents = doc . text ( ) ;
2021-07-23 23:27:12 +02:00
let selection = doc . selection ( view . id ) . clone ( ) . cursors ( text ) ;
2021-05-06 15:59:59 +02:00
let mut ranges = SmallVec ::with_capacity ( selection . len ( ) ) ;
2021-05-15 03:26:41 +02:00
// TODO: this is annoying, but we need to do it to properly calculate pos after edits
let mut offs = 0 ;
2021-07-23 23:27:12 +02:00
let mut transaction = Transaction ::change_by_selection ( contents , & selection , | range | {
2021-05-06 15:59:59 +02:00
let pos = range . head ;
let prev = if pos = = 0 {
' '
} else {
contents . char ( pos - 1 )
} ;
2021-06-28 10:52:38 +02:00
let curr = contents . get_char ( pos ) . unwrap_or ( ' ' ) ;
2021-05-06 15:59:59 +02:00
// TODO: offset range.head by 1? when calculating?
2021-05-14 12:21:46 +02:00
let indent_level = indent ::suggested_indent_for_pos (
doc . language_config ( ) ,
doc . syntax ( ) ,
text ,
pos . saturating_sub ( 1 ) ,
true ,
) ;
2021-05-06 15:59:59 +02:00
let indent = doc . indent_unit ( ) . repeat ( indent_level ) ;
let mut text = String ::with_capacity ( 1 + indent . len ( ) ) ;
2021-06-21 18:52:21 +02:00
text . push_str ( doc . line_ending . as_str ( ) ) ;
2021-05-06 15:59:59 +02:00
text . push_str ( & indent ) ;
2021-06-07 02:26:49 +02:00
let head = pos + offs + text . chars ( ) . count ( ) ;
2021-05-06 15:59:59 +02:00
2021-05-27 17:00:51 +02:00
// TODO: range replace or extend
// range.replace(|range| range.is_empty(), head); -> fn extend if cond true, new head pos
// can be used with cx.mode to do replace or extend on most changes
2021-05-06 15:59:59 +02:00
ranges . push ( Range ::new (
2021-05-15 03:26:41 +02:00
if range . is_empty ( ) {
head
} else {
range . anchor + offs
} ,
2021-05-06 15:59:59 +02:00
head ,
) ) ;
// if between a bracket pair
if helix_core ::auto_pairs ::PAIRS . contains ( & ( prev , curr ) ) {
// another newline, indent the end bracket one level less
let indent = doc . indent_unit ( ) . repeat ( indent_level . saturating_sub ( 1 ) ) ;
2021-06-21 18:52:21 +02:00
text . push_str ( doc . line_ending . as_str ( ) ) ;
2021-03-31 10:17:01 +02:00
text . push_str ( & indent ) ;
2021-05-06 15:59:59 +02:00
}
2021-05-15 03:26:41 +02:00
2021-06-07 02:26:49 +02:00
offs + = text . chars ( ) . count ( ) ;
2021-05-15 03:26:41 +02:00
( pos , pos , Some ( text . into ( ) ) )
2021-05-06 15:59:59 +02:00
} ) ;
transaction = transaction . with_selection ( Selection ::new ( ranges , selection . primary_index ( ) ) ) ;
//
2021-04-01 03:39:46 +02:00
doc . apply ( & transaction , view . id ) ;
2020-10-14 05:09:55 +02:00
}
2020-10-01 01:15:42 +02:00
2020-10-30 06:09:59 +01:00
pub fn delete_char_backward ( cx : & mut Context ) {
2021-06-08 05:24:27 +02:00
let count = cx . count ( ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-02-18 10:34:22 +01:00
let text = doc . text ( ) . slice ( .. ) ;
2021-11-18 16:19:40 +01:00
let indent_unit = doc . indent_unit ( ) ;
let tab_size = doc . tab_width ( ) ;
2021-03-31 10:17:01 +02:00
let transaction =
2021-04-01 03:39:46 +02:00
Transaction ::change_by_selection ( doc . text ( ) , doc . selection ( view . id ) , | range | {
2021-07-29 00:57:00 +02:00
let pos = range . cursor ( text ) ;
2021-11-18 16:19:40 +01:00
let line_start_pos = text . line_to_char ( range . cursor_line ( text ) ) ;
// considier to delete by indent level if all characters before `pos` are indent units.
let fragment = Cow ::from ( text . slice ( line_start_pos .. pos ) ) ;
if ! fragment . is_empty ( ) & & fragment . chars ( ) . all ( | ch | ch . is_whitespace ( ) ) {
if text . get_char ( pos . saturating_sub ( 1 ) ) = = Some ( '\t' ) {
// fast path, delete one char
(
graphemes ::nth_prev_grapheme_boundary ( text , pos , 1 ) ,
pos ,
None ,
)
} else {
let unit_len = indent_unit . chars ( ) . count ( ) ;
// NOTE: indent_unit always contains 'only spaces' or 'only tab' according to `IndentStyle` definition.
let unit_size = if indent_unit . starts_with ( '\t' ) {
tab_size * unit_len
} else {
unit_len
} ;
let width : usize = fragment
. chars ( )
. map ( | ch | {
if ch = = '\t' {
tab_size
} else {
// it can be none if it still meet control characters other than '\t'
// here just set the width to 1 (or some value better?).
ch . width ( ) . unwrap_or ( 1 )
}
} )
. sum ( ) ;
let mut drop = width % unit_size ; // round down to nearest unit
if drop = = 0 {
drop = unit_size
} ; // if it's already at a unit, consume a whole unit
let mut chars = fragment . chars ( ) . rev ( ) ;
let mut start = pos ;
for _ in 0 .. drop {
// delete up to `drop` spaces
match chars . next ( ) {
Some ( ' ' ) = > start - = 1 ,
_ = > break ,
}
}
( start , pos , None ) // delete!
}
} else {
// delete char
(
graphemes ::nth_prev_grapheme_boundary ( text , pos , count ) ,
pos ,
None ,
)
}
2021-03-31 10:17:01 +02:00
} ) ;
2021-04-01 03:39:46 +02:00
doc . apply ( & transaction , view . id ) ;
2020-10-14 05:09:55 +02:00
}
2020-09-13 04:32:37 +02:00
2020-10-30 06:09:59 +01:00
pub fn delete_char_forward ( cx : & mut Context ) {
2021-06-08 05:24:27 +02:00
let count = cx . count ( ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-02-18 10:34:22 +01:00
let text = doc . text ( ) . slice ( .. ) ;
2021-03-31 10:17:01 +02:00
let transaction =
2021-04-01 03:39:46 +02:00
Transaction ::change_by_selection ( doc . text ( ) , doc . selection ( view . id ) , | range | {
2021-07-29 00:57:00 +02:00
let pos = range . cursor ( text ) ;
2021-03-31 10:17:01 +02:00
(
2021-07-29 00:57:00 +02:00
pos ,
graphemes ::nth_next_grapheme_boundary ( text , pos , count ) ,
2021-03-31 10:17:01 +02:00
None ,
)
} ) ;
2021-04-01 03:39:46 +02:00
doc . apply ( & transaction , view . id ) ;
2020-10-14 05:09:55 +02:00
}
2021-06-05 12:15:50 +02:00
pub fn delete_word_backward ( cx : & mut Context ) {
2021-06-08 05:24:27 +02:00
let count = cx . count ( ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-06-05 12:15:50 +02:00
let text = doc . text ( ) . slice ( .. ) ;
2021-06-28 20:40:07 +02:00
2021-06-11 14:57:07 +02:00
let selection = doc
. selection ( view . id )
2021-07-24 02:06:14 +02:00
. clone ( )
2021-06-11 14:57:07 +02:00
. transform ( | range | movement ::move_prev_word_start ( text , range , count ) ) ;
2021-11-15 16:31:20 +01:00
delete_selection_insert_mode ( doc , view , & selection ) ;
}
pub fn delete_word_forward ( cx : & mut Context ) {
let count = cx . count ( ) ;
let ( view , doc ) = current! ( cx . editor ) ;
let text = doc . text ( ) . slice ( .. ) ;
let selection = doc
. selection ( view . id )
. clone ( )
. transform ( | range | movement ::move_next_word_start ( text , range , count ) ) ;
delete_selection_insert_mode ( doc , view , & selection ) ;
2021-06-05 12:15:50 +02:00
}
2020-09-13 04:32:37 +02:00
}
2020-10-04 10:15:43 +02:00
// Undo / Redo
2020-12-21 05:58:54 +01:00
// TODO: each command could simply return a Option<transaction>, then the higher level handles
// storing it?
2020-10-04 10:15:43 +02:00
2021-06-17 13:08:05 +02:00
fn undo ( cx : & mut Context ) {
2021-11-14 16:16:47 +01:00
let count = cx . count ( ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-11-14 16:16:47 +01:00
for _ in 0 .. count {
if ! doc . undo ( view . id ) {
cx . editor . set_status ( " Already at oldest change " . to_owned ( ) ) ;
break ;
}
2021-11-05 02:20:06 +01:00
}
2020-10-04 10:15:43 +02:00
}
2021-06-17 13:08:05 +02:00
fn redo ( cx : & mut Context ) {
2021-11-14 16:16:47 +01:00
let count = cx . count ( ) ;
let ( view , doc ) = current! ( cx . editor ) ;
for _ in 0 .. count {
if ! doc . redo ( view . id ) {
cx . editor . set_status ( " Already at newest change " . to_owned ( ) ) ;
break ;
}
}
}
fn earlier ( cx : & mut Context ) {
let count = cx . count ( ) ;
let ( view , doc ) = current! ( cx . editor ) ;
for _ in 0 .. count {
// rather than doing in batch we do this so get error halfway
if ! doc . earlier ( view . id , UndoKind ::Steps ( 1 ) ) {
cx . editor . set_status ( " Already at oldest change " . to_owned ( ) ) ;
break ;
}
}
}
fn later ( cx : & mut Context ) {
let count = cx . count ( ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-11-14 16:16:47 +01:00
for _ in 0 .. count {
// rather than doing in batch we do this so get error halfway
if ! doc . later ( view . id , UndoKind ::Steps ( 1 ) ) {
cx . editor . set_status ( " Already at newest change " . to_owned ( ) ) ;
break ;
}
2021-11-05 02:20:06 +01:00
}
2020-10-04 10:15:43 +02:00
}
2020-10-06 09:00:23 +02:00
// Yank / Paste
2021-06-17 13:08:05 +02:00
fn yank ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-07-21 18:32:48 +02:00
let text = doc . text ( ) . slice ( .. ) ;
2021-03-29 09:47:02 +02:00
let values : Vec < String > = doc
2021-04-01 03:39:46 +02:00
. selection ( view . id )
2021-07-21 18:32:48 +02:00
. fragments ( text )
2021-03-31 16:42:16 +02:00
. map ( Cow ::into_owned )
2020-10-06 09:00:23 +02:00
. collect ( ) ;
2021-06-05 04:21:31 +02:00
let msg = format! (
" yanked {} selection(s) to register {} " ,
values . len ( ) ,
2021-09-08 07:52:09 +02:00
cx . register . unwrap_or ( '"' )
2021-06-05 04:21:31 +02:00
) ;
2021-03-29 09:47:02 +02:00
2021-06-15 05:26:05 +02:00
cx . editor
. registers
2021-09-08 07:52:09 +02:00
. write ( cx . register . unwrap_or ( '"' ) , values ) ;
2021-03-29 09:47:02 +02:00
2021-07-30 04:39:47 +02:00
cx . editor . set_status ( msg ) ;
exit_select_mode ( cx ) ;
2020-10-06 09:00:23 +02:00
}
2021-08-12 04:53:48 +02:00
fn yank_joined_to_clipboard_impl (
editor : & mut Editor ,
separator : & str ,
clipboard_type : ClipboardType ,
) -> anyhow ::Result < ( ) > {
2021-06-19 08:27:43 +02:00
let ( view , doc ) = current! ( editor ) ;
2021-07-21 18:32:48 +02:00
let text = doc . text ( ) . slice ( .. ) ;
2021-06-19 08:27:43 +02:00
let values : Vec < String > = doc
. selection ( view . id )
2021-07-21 18:32:48 +02:00
. fragments ( text )
2021-06-19 08:27:43 +02:00
. map ( Cow ::into_owned )
. collect ( ) ;
let msg = format! (
" joined and yanked {} selection(s) to system clipboard " ,
values . len ( ) ,
) ;
let joined = values . join ( separator ) ;
2021-07-11 09:35:57 +02:00
editor
. clipboard_provider
2021-08-12 04:53:48 +02:00
. set_contents ( joined , clipboard_type )
2021-07-11 09:35:57 +02:00
. context ( " Couldn't set system clipboard content " ) ? ;
2021-06-19 08:27:43 +02:00
editor . set_status ( msg ) ;
2021-06-26 19:50:44 +02:00
Ok ( ( ) )
2021-06-19 08:27:43 +02:00
}
fn yank_joined_to_clipboard ( cx : & mut Context ) {
2021-11-06 15:52:26 +01:00
let line_ending = doc! ( cx . editor ) . line_ending ;
2021-12-03 04:51:55 +01:00
let _ =
yank_joined_to_clipboard_impl ( cx . editor , line_ending . as_str ( ) , ClipboardType ::Clipboard ) ;
2021-07-30 04:39:47 +02:00
exit_select_mode ( cx ) ;
2021-06-19 08:27:43 +02:00
}
2021-08-12 04:53:48 +02:00
fn yank_main_selection_to_clipboard_impl (
editor : & mut Editor ,
clipboard_type : ClipboardType ,
) -> anyhow ::Result < ( ) > {
2021-06-19 08:27:43 +02:00
let ( view , doc ) = current! ( editor ) ;
2021-07-21 18:32:48 +02:00
let text = doc . text ( ) . slice ( .. ) ;
2021-06-19 08:27:43 +02:00
2021-07-29 00:57:00 +02:00
let value = doc . selection ( view . id ) . primary ( ) . fragment ( text ) ;
2021-06-19 08:27:43 +02:00
2021-08-12 04:53:48 +02:00
if let Err ( e ) = editor
. clipboard_provider
. set_contents ( value . into_owned ( ) , clipboard_type )
{
2021-08-20 06:42:17 +02:00
bail! ( " Couldn't set system clipboard content: {} " , e ) ;
2021-06-19 08:27:43 +02:00
}
editor . set_status ( " yanked main selection to system clipboard " . to_owned ( ) ) ;
2021-06-26 19:50:44 +02:00
Ok ( ( ) )
2021-06-19 08:27:43 +02:00
}
fn yank_main_selection_to_clipboard ( cx : & mut Context ) {
2021-12-03 04:48:07 +01:00
let _ = yank_main_selection_to_clipboard_impl ( cx . editor , ClipboardType ::Clipboard ) ;
2021-08-12 04:53:48 +02:00
}
fn yank_joined_to_primary_clipboard ( cx : & mut Context ) {
2021-11-06 15:52:26 +01:00
let line_ending = doc! ( cx . editor ) . line_ending ;
2021-12-03 04:51:55 +01:00
let _ =
yank_joined_to_clipboard_impl ( cx . editor , line_ending . as_str ( ) , ClipboardType ::Selection ) ;
2021-08-12 04:53:48 +02:00
}
fn yank_main_selection_to_primary_clipboard ( cx : & mut Context ) {
2021-12-03 04:48:07 +01:00
let _ = yank_main_selection_to_clipboard_impl ( cx . editor , ClipboardType ::Selection ) ;
2021-07-30 04:39:47 +02:00
exit_select_mode ( cx ) ;
2021-06-19 08:27:43 +02:00
}
2021-04-07 10:03:29 +02:00
#[ derive(Copy, Clone) ]
enum Paste {
Before ,
After ,
}
2021-06-15 05:26:05 +02:00
fn paste_impl (
values : & [ String ] ,
doc : & mut Document ,
view : & View ,
action : Paste ,
2021-12-14 09:49:29 +01:00
count : usize ,
2021-06-15 05:26:05 +02:00
) -> Option < Transaction > {
let repeat = std ::iter ::repeat (
values
. last ( )
2021-12-14 09:49:29 +01:00
. map ( | value | Tendril ::from ( value . repeat ( count ) ) )
2021-06-15 05:26:05 +02:00
. unwrap ( ) ,
) ;
2020-10-06 09:00:23 +02:00
2021-06-21 19:29:29 +02:00
// if any of values ends with a line ending, it's linewise paste
2021-06-16 17:22:55 +02:00
let linewise = values
. iter ( )
2021-06-21 19:29:29 +02:00
. any ( | value | get_line_ending_of_str ( value ) . is_some ( ) ) ;
2020-10-09 09:58:43 +02:00
2021-09-10 17:12:26 +02:00
// Only compiled once.
#[ allow(clippy::trivial_regex) ]
static REGEX : Lazy < Regex > = Lazy ::new ( | | Regex ::new ( r "\r\n|\r|\n" ) . unwrap ( ) ) ;
let mut values = values
. iter ( )
. map ( | value | REGEX . replace_all ( value , doc . line_ending . as_str ( ) ) )
2021-12-14 09:49:29 +01:00
. map ( | value | Tendril ::from ( value . as_ref ( ) . repeat ( count ) ) )
2021-09-10 17:12:26 +02:00
. chain ( repeat ) ;
2020-10-06 09:00:23 +02:00
2021-06-15 05:26:05 +02:00
let text = doc . text ( ) ;
2021-07-29 00:57:00 +02:00
let selection = doc . selection ( view . id ) ;
2021-01-21 08:55:46 +01:00
2021-07-29 00:57:00 +02:00
let transaction = Transaction ::change_by_selection ( text , selection , | range | {
2021-06-15 05:26:05 +02:00
let pos = match ( action , linewise ) {
// paste linewise before
( Paste ::Before , true ) = > text . line_to_char ( text . char_to_line ( range . from ( ) ) ) ,
// paste linewise after
2021-07-20 19:56:27 +02:00
( Paste ::After , true ) = > {
2021-07-20 21:40:58 +02:00
let line = range . line_range ( text . slice ( .. ) ) . 1 ;
text . line_to_char ( ( line + 1 ) . min ( text . len_lines ( ) ) )
2021-07-20 19:56:27 +02:00
}
2021-06-15 05:26:05 +02:00
// paste insert
( Paste ::Before , false ) = > range . from ( ) ,
// paste append
2021-07-01 18:51:24 +02:00
( Paste ::After , false ) = > range . to ( ) ,
2021-06-15 05:26:05 +02:00
} ;
2021-12-14 09:49:29 +01:00
( pos , pos , values . next ( ) )
2021-06-15 05:26:05 +02:00
} ) ;
Some ( transaction )
2021-04-07 10:03:29 +02:00
}
2021-08-12 04:53:48 +02:00
fn paste_clipboard_impl (
editor : & mut Editor ,
action : Paste ,
clipboard_type : ClipboardType ,
2021-12-14 09:49:29 +01:00
count : usize ,
2021-08-12 04:53:48 +02:00
) -> anyhow ::Result < ( ) > {
2021-06-19 08:27:43 +02:00
let ( view , doc ) = current! ( editor ) ;
match editor
. clipboard_provider
2021-08-12 04:53:48 +02:00
. get_contents ( clipboard_type )
2021-12-14 09:49:29 +01:00
. map ( | contents | paste_impl ( & [ contents ] , doc , view , action , count ) )
2021-06-19 08:27:43 +02:00
{
Ok ( Some ( transaction ) ) = > {
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
2021-06-26 19:50:44 +02:00
Ok ( ( ) )
}
Ok ( None ) = > Ok ( ( ) ) ,
2021-07-11 09:35:57 +02:00
Err ( e ) = > Err ( e . context ( " Couldn't get system clipboard contents " ) ) ,
2021-06-19 08:27:43 +02:00
}
}
fn paste_clipboard_after ( cx : & mut Context ) {
2021-12-14 09:49:29 +01:00
let _ = paste_clipboard_impl (
cx . editor ,
Paste ::After ,
ClipboardType ::Clipboard ,
cx . count ( ) ,
) ;
2021-06-19 08:27:43 +02:00
}
fn paste_clipboard_before ( cx : & mut Context ) {
2021-12-14 09:49:29 +01:00
let _ = paste_clipboard_impl (
cx . editor ,
Paste ::Before ,
ClipboardType ::Clipboard ,
cx . count ( ) ,
) ;
2021-08-12 04:53:48 +02:00
}
fn paste_primary_clipboard_after ( cx : & mut Context ) {
2021-12-14 09:49:29 +01:00
let _ = paste_clipboard_impl (
cx . editor ,
Paste ::After ,
ClipboardType ::Selection ,
cx . count ( ) ,
) ;
2021-08-12 04:53:48 +02:00
}
fn paste_primary_clipboard_before ( cx : & mut Context ) {
2021-12-14 09:49:29 +01:00
let _ = paste_clipboard_impl (
cx . editor ,
Paste ::Before ,
ClipboardType ::Selection ,
cx . count ( ) ,
) ;
2021-06-19 08:27:43 +02:00
}
2021-06-17 13:08:05 +02:00
fn replace_with_yanked ( cx : & mut Context ) {
2021-12-14 09:49:29 +01:00
let count = cx . count ( ) ;
2021-09-08 07:52:09 +02:00
let reg_name = cx . register . unwrap_or ( '"' ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
let registers = & mut cx . editor . registers ;
2021-06-07 20:13:40 +02:00
2021-06-15 05:26:05 +02:00
if let Some ( values ) = registers . read ( reg_name ) {
2021-10-21 02:44:53 +02:00
if ! values . is_empty ( ) {
let repeat = std ::iter ::repeat (
values
. last ( )
2021-12-14 09:49:29 +01:00
. map ( | value | Tendril ::from_slice ( & value . repeat ( count ) ) )
2021-10-21 02:44:53 +02:00
. unwrap ( ) ,
) ;
let mut values = values
. iter ( )
2021-12-14 09:49:29 +01:00
. map ( | value | Tendril ::from_slice ( & value . repeat ( count ) ) )
2021-10-21 02:44:53 +02:00
. chain ( repeat ) ;
2021-07-29 00:57:00 +02:00
let selection = doc . selection ( view . id ) ;
let transaction = Transaction ::change_by_selection ( doc . text ( ) , selection , | range | {
2021-07-01 18:51:24 +02:00
if ! range . is_empty ( ) {
2021-10-21 02:44:53 +02:00
( range . from ( ) , range . to ( ) , Some ( values . next ( ) . unwrap ( ) ) )
2021-07-01 18:51:24 +02:00
} else {
( range . from ( ) , range . to ( ) , None )
}
} ) ;
2021-06-07 20:13:40 +02:00
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
}
}
}
2021-08-12 04:53:48 +02:00
fn replace_selections_with_clipboard_impl (
editor : & mut Editor ,
clipboard_type : ClipboardType ,
2021-12-14 09:49:29 +01:00
count : usize ,
2021-08-12 04:53:48 +02:00
) -> anyhow ::Result < ( ) > {
2021-06-19 08:27:43 +02:00
let ( view , doc ) = current! ( editor ) ;
2021-08-12 04:53:48 +02:00
match editor . clipboard_provider . get_contents ( clipboard_type ) {
2021-06-19 08:27:43 +02:00
Ok ( contents ) = > {
2021-07-29 00:57:00 +02:00
let selection = doc . selection ( view . id ) ;
let transaction = Transaction ::change_by_selection ( doc . text ( ) , selection , | range | {
2021-12-14 09:49:29 +01:00
(
range . from ( ) ,
range . to ( ) ,
Some ( contents . repeat ( count ) . as_str ( ) . into ( ) ) ,
)
2021-07-01 18:51:24 +02:00
} ) ;
2021-06-19 08:27:43 +02:00
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
2021-07-11 09:35:57 +02:00
Ok ( ( ) )
2021-06-19 08:27:43 +02:00
}
2021-07-11 09:35:57 +02:00
Err ( e ) = > Err ( e . context ( " Couldn't get system clipboard contents " ) ) ,
2021-06-19 08:27:43 +02:00
}
}
fn replace_selections_with_clipboard ( cx : & mut Context ) {
2021-12-14 09:49:29 +01:00
let _ = replace_selections_with_clipboard_impl ( cx . editor , ClipboardType ::Clipboard , cx . count ( ) ) ;
2021-08-12 04:53:48 +02:00
}
fn replace_selections_with_primary_clipboard ( cx : & mut Context ) {
2021-12-14 09:49:29 +01:00
let _ = replace_selections_with_clipboard_impl ( cx . editor , ClipboardType ::Selection , cx . count ( ) ) ;
2021-06-19 08:27:43 +02:00
}
2021-06-17 13:08:05 +02:00
fn paste_after ( cx : & mut Context ) {
2021-12-14 09:49:29 +01:00
let count = cx . count ( ) ;
2021-09-08 07:52:09 +02:00
let reg_name = cx . register . unwrap_or ( '"' ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
let registers = & mut cx . editor . registers ;
2021-04-07 10:03:29 +02:00
2021-06-15 05:26:05 +02:00
if let Some ( transaction ) = registers
. read ( reg_name )
2021-12-14 09:49:29 +01:00
. and_then ( | values | paste_impl ( values , doc , view , Paste ::After , count ) )
2021-06-15 05:26:05 +02:00
{
2021-04-07 10:03:29 +02:00
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
}
}
2021-06-17 13:08:05 +02:00
fn paste_before ( cx : & mut Context ) {
2021-12-14 09:49:29 +01:00
let count = cx . count ( ) ;
2021-09-08 07:52:09 +02:00
let reg_name = cx . register . unwrap_or ( '"' ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
let registers = & mut cx . editor . registers ;
2020-10-06 09:00:23 +02:00
2021-06-15 05:26:05 +02:00
if let Some ( transaction ) = registers
. read ( reg_name )
2021-12-14 09:49:29 +01:00
. and_then ( | values | paste_impl ( values , doc , view , Paste ::Before , count ) )
2021-06-15 05:26:05 +02:00
{
2021-04-01 03:39:46 +02:00
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
2020-10-06 09:00:23 +02:00
}
}
2020-10-09 09:58:43 +02:00
2021-03-31 10:17:01 +02:00
fn get_lines ( doc : & Document , view_id : ViewId ) -> Vec < usize > {
2020-10-09 09:58:43 +02:00
let mut lines = Vec ::new ( ) ;
// Get all line numbers
2021-03-31 10:17:01 +02:00
for range in doc . selection ( view_id ) {
2021-07-22 19:50:12 +02:00
let ( start , end ) = range . line_range ( doc . text ( ) . slice ( .. ) ) ;
2020-10-09 09:58:43 +02:00
for line in start ..= end {
lines . push ( line )
}
}
lines . sort_unstable ( ) ; // sorting by usize so _unstable is preferred
lines . dedup ( ) ;
2020-10-13 16:08:28 +02:00
lines
}
2021-06-17 13:08:05 +02:00
fn indent ( cx : & mut Context ) {
2021-06-08 05:24:27 +02:00
let count = cx . count ( ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-04-01 03:39:46 +02:00
let lines = get_lines ( doc , view . id ) ;
2020-10-09 09:58:43 +02:00
// Indent by one level
2021-05-18 17:37:01 +02:00
let indent = Tendril ::from ( doc . indent_unit ( ) . repeat ( count ) ) ;
2020-10-09 09:58:43 +02:00
let transaction = Transaction ::change (
2021-03-18 05:28:27 +01:00
doc . text ( ) ,
2020-10-09 09:58:43 +02:00
lines . into_iter ( ) . map ( | line | {
2021-01-21 09:00:08 +01:00
let pos = doc . text ( ) . line_to_char ( line ) ;
2020-10-09 09:58:43 +02:00
( pos , pos , Some ( indent . clone ( ) ) )
} ) ,
) ;
2021-04-01 03:39:46 +02:00
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
2020-10-09 09:58:43 +02:00
}
2021-06-17 13:08:05 +02:00
fn unindent ( cx : & mut Context ) {
2021-06-08 05:24:27 +02:00
let count = cx . count ( ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-04-01 03:39:46 +02:00
let lines = get_lines ( doc , view . id ) ;
2020-10-13 16:08:28 +02:00
let mut changes = Vec ::with_capacity ( lines . len ( ) ) ;
2021-03-22 05:47:39 +01:00
let tab_width = doc . tab_width ( ) ;
2021-05-18 17:34:46 +02:00
let indent_width = count * tab_width ;
2020-10-13 16:08:28 +02:00
for line_idx in lines {
2021-01-21 09:00:08 +01:00
let line = doc . text ( ) . line ( line_idx ) ;
2020-10-13 16:08:28 +02:00
let mut width = 0 ;
2021-05-18 17:34:46 +02:00
let mut pos = 0 ;
2020-10-13 16:08:28 +02:00
for ch in line . chars ( ) {
match ch {
' ' = > width + = 1 ,
2021-03-22 05:47:39 +01:00
'\t' = > width = ( width / tab_width + 1 ) * tab_width ,
2020-10-13 16:08:28 +02:00
_ = > break ,
}
2021-05-18 17:34:46 +02:00
pos + = 1 ;
if width > = indent_width {
2020-10-13 16:08:28 +02:00
break ;
}
}
2021-05-18 17:34:46 +02:00
// now delete from start to first non-blank
if pos > 0 {
2021-01-21 09:00:08 +01:00
let start = doc . text ( ) . line_to_char ( line_idx ) ;
2021-05-18 17:34:46 +02:00
changes . push ( ( start , start + pos , None ) )
2020-10-13 16:08:28 +02:00
}
}
2021-03-18 05:28:27 +01:00
let transaction = Transaction ::change ( doc . text ( ) , changes . into_iter ( ) ) ;
2020-10-13 16:08:28 +02:00
2021-04-01 03:39:46 +02:00
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
2020-10-09 09:58:43 +02:00
}
2020-10-14 11:07:42 +02:00
2021-06-17 13:08:05 +02:00
fn format_selections ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-02-26 07:52:43 +01:00
// via lsp if available
// else via tree-sitter indentation calculations
2021-04-14 08:30:15 +02:00
let language_server = match doc . language_server ( ) {
Some ( language_server ) = > language_server ,
None = > return ,
} ;
2021-02-26 07:52:43 +01:00
let ranges : Vec < lsp ::Range > = doc
2021-04-01 03:39:46 +02:00
. selection ( view . id )
2021-02-26 07:52:43 +01:00
. iter ( )
2021-04-14 08:30:15 +02:00
. map ( | range | range_to_lsp_range ( doc . text ( ) , * range , language_server . offset_encoding ( ) ) )
2021-02-26 07:52:43 +01:00
. collect ( ) ;
2021-07-01 20:57:12 +02:00
// TODO: all of the TODO's and commented code inside the loop,
// to make this actually work.
for _range in ranges {
let _language_server = match doc . language_server ( ) {
2021-02-26 07:52:43 +01:00
Some ( language_server ) = > language_server ,
None = > return ,
} ;
// TODO: handle fails
// TODO: concurrent map
2021-05-08 11:17:13 +02:00
// TODO: need to block to get the formatting
2021-05-06 10:15:49 +02:00
// let edits = block_on(language_server.text_document_range_formatting(
// doc.identifier(),
// range,
// lsp::FormattingOptions::default(),
// ))
// .unwrap_or_default();
// let transaction = helix_lsp::util::generate_transaction_from_edits(
// doc.text(),
// edits,
// language_server.offset_encoding(),
// );
// doc.apply(&transaction, view.id);
2021-02-26 07:52:43 +01:00
}
2021-04-01 03:39:46 +02:00
doc . append_changes_to_history ( view . id ) ;
2021-02-26 07:52:43 +01:00
}
2021-06-17 13:08:05 +02:00
fn join_selections ( cx : & mut Context ) {
2021-06-11 14:57:07 +02:00
use movement ::skip_while ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-02-26 09:21:59 +01:00
let text = doc . text ( ) ;
let slice = doc . text ( ) . slice ( .. ) ;
let mut changes = Vec ::new ( ) ;
let fragment = Tendril ::from ( " " ) ;
2021-04-01 03:39:46 +02:00
for selection in doc . selection ( view . id ) {
2021-07-22 19:50:12 +02:00
let ( start , mut end ) = selection . line_range ( slice ) ;
2021-02-26 09:21:59 +01:00
if start = = end {
2021-07-22 19:50:12 +02:00
end = ( end + 1 ) . min ( text . len_lines ( ) - 1 ) ;
2021-02-26 09:21:59 +01:00
}
let lines = start .. end ;
changes . reserve ( lines . len ( ) ) ;
for line in lines {
2021-07-01 20:57:12 +02:00
let start = line_end_char_index ( & slice , line ) ;
2021-06-22 19:15:30 +02:00
let mut end = text . line_to_char ( line + 1 ) ;
2021-06-11 14:57:07 +02:00
end = skip_while ( slice , end , | ch | matches! ( ch , ' ' | '\t' ) ) . unwrap_or ( end ) ;
2021-02-26 09:21:59 +01:00
// need to skip from start, not end
let change = ( start , end , Some ( fragment . clone ( ) ) ) ;
changes . push ( change ) ;
}
}
changes . sort_unstable_by_key ( | ( from , _to , _text ) | * from ) ;
changes . dedup ( ) ;
// TODO: joining multiple empty lines should be replaced by a single space.
// need to merge change ranges that touch
2021-03-18 05:28:27 +01:00
let transaction = Transaction ::change ( doc . text ( ) , changes . into_iter ( ) ) ;
2021-02-26 09:21:59 +01:00
// TODO: select inserted spaces
// .with_selection(selection);
2021-04-01 03:39:46 +02:00
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
2021-02-26 09:21:59 +01:00
}
2021-11-12 01:34:08 +01:00
fn keep_or_remove_selections_impl ( cx : & mut Context , remove : bool ) {
// keep or remove selections matching regex
2021-09-08 07:52:09 +02:00
let reg = cx . register . unwrap_or ( '/' ) ;
2021-09-21 18:03:12 +02:00
let prompt = ui ::regex_prompt (
cx ,
2021-11-12 01:34:08 +01:00
if ! remove { " keep: " } else { " remove: " } . into ( ) ,
2021-09-21 18:03:12 +02:00
Some ( reg ) ,
2021-11-04 04:26:01 +01:00
| _input : & str | Vec ::new ( ) ,
2021-09-21 18:03:12 +02:00
move | view , doc , regex , event | {
if event ! = PromptEvent ::Update {
return ;
}
let text = doc . text ( ) . slice ( .. ) ;
2021-03-15 09:09:18 +01:00
2021-11-12 01:34:08 +01:00
if let Some ( selection ) =
selection ::keep_or_remove_matches ( text , doc . selection ( view . id ) , & regex , remove )
{
2021-09-21 18:03:12 +02:00
doc . set_selection ( view . id , selection ) ;
}
} ,
) ;
2021-03-15 09:09:18 +01:00
cx . push_layer ( Box ::new ( prompt ) ) ;
2021-03-01 10:19:08 +01:00
}
2021-11-12 01:34:08 +01:00
fn keep_selections ( cx : & mut Context ) {
keep_or_remove_selections_impl ( cx , false )
}
fn remove_selections ( cx : & mut Context ) {
keep_or_remove_selections_impl ( cx , true )
}
2021-06-17 13:08:05 +02:00
fn keep_primary_selection ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-09-21 17:51:49 +02:00
// TODO: handle count
2021-03-15 09:13:36 +01:00
2021-04-01 03:39:46 +02:00
let range = doc . selection ( view . id ) . primary ( ) ;
2021-08-12 09:00:42 +02:00
doc . set_selection ( view . id , Selection ::single ( range . anchor , range . head ) ) ;
2021-03-15 09:13:36 +01:00
}
2021-09-21 17:51:49 +02:00
fn remove_primary_selection ( cx : & mut Context ) {
let ( view , doc ) = current! ( cx . editor ) ;
// TODO: handle count
let selection = doc . selection ( view . id ) ;
if selection . len ( ) = = 1 {
cx . editor . set_error ( " no selections remaining " . to_owned ( ) ) ;
return ;
}
let index = selection . primary_index ( ) ;
let selection = selection . clone ( ) . remove ( index ) ;
doc . set_selection ( view . id , selection ) ;
}
2021-08-26 04:14:46 +02:00
pub fn completion ( cx : & mut Context ) {
2021-03-26 08:02:13 +01:00
// trigger on trigger char, or if user calls it
// (or on word char typing??)
// after it's triggered, if response marked is_incomplete, update on every subsequent keypress
//
// lsp calls are done via a callback: it sends a request and doesn't block.
// when we get the response similarly to notification, trigger a call to the completion popup
//
// language_server.completion(params, |cx: &mut Context, _meta, response| {
// // called at response time
// // compositor, lookup completion layer
// // downcast dyn Component to Completion component
// // emit response to completion (completion.complete/handle(response))
// })
2021-03-27 04:06:40 +01:00
//
// typing after prompt opens: usually start offset is tracked and everything between
// start_offset..cursor is replaced. For our purposes we could keep the start state (doc,
// selection) and revert to them before applying. This needs to properly reset changes/history
// though...
//
// company-mode does this by matching the prefix of the completion and removing it.
// ignore isIncomplete for now
// keep state while typing
// the behavior should be, filter the menu based on input
// if items returns empty at any point, remove the popup
// if backspace past initial offset point, remove the popup
//
// debounce requests!
//
// need an idle timeout thing.
// https://github.com/company-mode/company-mode/blob/master/company.el#L620-L622
//
// "The idle delay in seconds until completion starts automatically.
// The prefix still has to satisfy `company-minimum-prefix-length' before that
// happens. The value of nil means no idle completion."
2021-03-26 08:02:13 +01:00
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-01-21 08:55:46 +01:00
2021-03-18 06:40:22 +01:00
let language_server = match doc . language_server ( ) {
2021-02-22 03:42:47 +01:00
Some ( language_server ) = > language_server ,
None = > return ,
} ;
2021-04-14 08:30:15 +02:00
let offset_encoding = language_server . offset_encoding ( ) ;
2021-08-27 03:44:12 +02:00
let text = doc . text ( ) . slice ( .. ) ;
let cursor = doc . selection ( view . id ) . primary ( ) . cursor ( text ) ;
2021-04-14 08:30:15 +02:00
2021-07-19 18:25:10 +02:00
let pos = pos_to_lsp_pos ( doc . text ( ) , cursor , offset_encoding ) ;
2021-01-06 09:48:14 +01:00
2021-06-18 05:39:37 +02:00
let future = language_server . completion ( doc . identifier ( ) , pos , None ) ;
2021-03-26 08:02:13 +01:00
2021-07-19 18:25:10 +02:00
let trigger_offset = cursor ;
2021-03-27 04:06:40 +01:00
2021-08-27 03:44:12 +02:00
// TODO: trigger_offset should be the cursor offset but we also need a starting offset from where we want to apply
// completion filtering. For example logger.te| should filter the initial suggestion list with "te".
use helix_core ::chars ;
let mut iter = text . chars_at ( cursor ) ;
iter . reverse ( ) ;
let offset = iter . take_while ( | ch | chars ::char_is_word ( * ch ) ) . count ( ) ;
let start_offset = cursor . saturating_sub ( offset ) ;
2021-10-24 09:55:29 +02:00
let prefix = text . slice ( start_offset .. cursor ) . to_string ( ) ;
2021-08-27 03:44:12 +02:00
2021-03-26 08:02:13 +01:00
cx . callback (
2021-05-06 08:08:59 +02:00
future ,
2021-03-27 04:06:40 +01:00
move | editor : & mut Editor ,
compositor : & mut Compositor ,
response : Option < lsp ::CompletionResponse > | {
2021-06-30 10:42:23 +02:00
let ( _ , doc ) = current! ( editor ) ;
if doc . mode ( ) ! = Mode ::Insert {
// we're not in insert mode anymore
return ;
}
2021-10-24 09:55:29 +02:00
let mut items = match response {
2021-03-26 08:02:13 +01:00
Some ( lsp ::CompletionResponse ::Array ( items ) ) = > items ,
// TODO: do something with is_incomplete
Some ( lsp ::CompletionResponse ::List ( lsp ::CompletionList {
is_incomplete : _is_incomplete ,
items ,
} ) ) = > items ,
None = > Vec ::new ( ) ,
} ;
2021-10-24 09:55:29 +02:00
if ! prefix . is_empty ( ) {
items = items
. into_iter ( )
. filter ( | item | {
item . filter_text
. as_ref ( )
. unwrap_or ( & item . label )
. starts_with ( & prefix )
} )
. collect ( ) ;
}
2021-05-08 08:39:42 +02:00
if items . is_empty ( ) {
2021-10-10 05:32:06 +02:00
// editor.set_error("No completion available".to_string());
2021-05-08 08:39:42 +02:00
return ;
2021-03-26 08:02:13 +01:00
}
2021-05-08 08:39:42 +02:00
let size = compositor . size ( ) ;
2021-11-18 03:08:47 +01:00
let ui = compositor . find ::< ui ::EditorView > ( ) . unwrap ( ) ;
ui . set_completion (
editor ,
items ,
offset_encoding ,
start_offset ,
trigger_offset ,
size ,
) ;
2021-03-26 08:02:13 +01:00
} ,
) ;
2020-12-23 08:20:49 +01:00
}
2021-02-04 11:49:29 +01:00
2021-06-17 13:08:05 +02:00
fn hover ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-02-25 10:07:47 +01:00
2021-03-18 06:40:22 +01:00
let language_server = match doc . language_server ( ) {
2021-02-25 10:07:47 +01:00
Some ( language_server ) = > language_server ,
None = > return ,
} ;
// TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier
2021-04-14 08:30:15 +02:00
let pos = pos_to_lsp_pos (
doc . text ( ) ,
2021-07-26 17:40:30 +02:00
doc . selection ( view . id )
. primary ( )
. cursor ( doc . text ( ) . slice ( .. ) ) ,
2021-04-14 08:30:15 +02:00
language_server . offset_encoding ( ) ,
) ;
2021-02-25 10:07:47 +01:00
2021-06-18 05:39:37 +02:00
let future = language_server . text_document_hover ( doc . identifier ( ) , pos , None ) ;
2021-02-25 10:07:47 +01:00
2021-05-06 10:15:49 +02:00
cx . callback (
future ,
move | editor : & mut Editor , compositor : & mut Compositor , response : Option < lsp ::Hover > | {
if let Some ( hover ) = response {
// hover.contents / .range <- used for visualizing
2021-11-15 02:29:07 +01:00
fn marked_string_to_markdown ( contents : lsp ::MarkedString ) -> String {
match contents {
lsp ::MarkedString ::String ( contents ) = > contents ,
lsp ::MarkedString ::LanguageString ( string ) = > {
if string . language = = " markdown " {
string . value
} else {
format! ( " ``` {} \n {} \n ``` " , string . language , string . value )
}
}
2021-05-06 10:15:49 +02:00
}
2021-11-15 02:29:07 +01:00
}
let contents = match hover . contents {
lsp ::HoverContents ::Scalar ( contents ) = > marked_string_to_markdown ( contents ) ,
lsp ::HoverContents ::Array ( contents ) = > contents
. into_iter ( )
. map ( marked_string_to_markdown )
. collect ::< Vec < _ > > ( )
. join ( " \n \n " ) ,
2021-05-06 10:15:49 +02:00
lsp ::HoverContents ::Markup ( contents ) = > contents . value ,
} ;
2021-02-25 10:07:47 +01:00
2021-05-06 10:15:49 +02:00
// skip if contents empty
2021-06-19 13:27:32 +02:00
let contents = ui ::Markdown ::new ( contents , editor . syn_loader . clone ( ) ) ;
2021-12-08 08:11:18 +01:00
let popup = Popup ::new ( " documentation " , contents ) ;
if let Some ( doc_popup ) = compositor . find_id ( " documentation " ) {
* doc_popup = popup ;
} else {
compositor . push ( Box ::new ( popup ) ) ;
}
2021-05-06 10:15:49 +02:00
}
} ,
) ;
2021-02-25 10:07:47 +01:00
}
2021-02-19 05:59:24 +01:00
// comments
2021-06-17 13:08:05 +02:00
fn toggle_comments ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-07-18 18:33:38 +02:00
let token = doc
. language_config ( )
. and_then ( | lc | lc . comment_token . as_ref ( ) )
. map ( | tc | tc . as_ref ( ) ) ;
let transaction = comment ::toggle_line_comments ( doc . text ( ) , doc . selection ( view . id ) , token ) ;
2021-02-19 05:59:24 +01:00
2021-04-01 03:39:46 +02:00
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
2021-08-17 02:52:52 +02:00
exit_select_mode ( cx ) ;
2021-02-19 05:59:24 +01:00
}
2021-02-22 07:50:41 +01:00
2021-08-06 04:22:01 +02:00
fn rotate_selections ( cx : & mut Context , direction : Direction ) {
let count = cx . count ( ) ;
let ( view , doc ) = current! ( cx . editor ) ;
let mut selection = doc . selection ( view . id ) . clone ( ) ;
let index = selection . primary_index ( ) ;
let len = selection . len ( ) ;
selection . set_primary_index ( match direction {
Direction ::Forward = > ( index + count ) % len ,
Direction ::Backward = > ( index + ( len . saturating_sub ( count ) % len ) ) % len ,
} ) ;
doc . set_selection ( view . id , selection ) ;
}
fn rotate_selections_forward ( cx : & mut Context ) {
rotate_selections ( cx , Direction ::Forward )
}
fn rotate_selections_backward ( cx : & mut Context ) {
rotate_selections ( cx , Direction ::Backward )
}
2021-08-08 06:26:13 +02:00
fn rotate_selection_contents ( cx : & mut Context , direction : Direction ) {
let count = cx . count ;
let ( view , doc ) = current! ( cx . editor ) ;
let text = doc . text ( ) . slice ( .. ) ;
let selection = doc . selection ( view . id ) ;
let mut fragments : Vec < _ > = selection
. fragments ( text )
. map ( | fragment | Tendril ::from_slice ( & fragment ) )
. collect ( ) ;
let group = count
. map ( | count | count . get ( ) )
. unwrap_or ( fragments . len ( ) ) // default to rotating everything as one group
. min ( fragments . len ( ) ) ;
for chunk in fragments . chunks_mut ( group ) {
// TODO: also modify main index
match direction {
Direction ::Forward = > chunk . rotate_right ( 1 ) ,
Direction ::Backward = > chunk . rotate_left ( 1 ) ,
} ;
}
let transaction = Transaction ::change (
doc . text ( ) ,
selection
. ranges ( )
. iter ( )
. zip ( fragments )
. map ( | ( range , fragment ) | ( range . from ( ) , range . to ( ) , Some ( fragment ) ) ) ,
) ;
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
}
fn rotate_selection_contents_forward ( cx : & mut Context ) {
rotate_selection_contents ( cx , Direction ::Forward )
}
fn rotate_selection_contents_backward ( cx : & mut Context ) {
rotate_selection_contents ( cx , Direction ::Backward )
}
2021-02-22 07:50:41 +01:00
// tree sitter node selection
2021-06-17 13:08:05 +02:00
fn expand_selection ( cx : & mut Context ) {
2021-10-27 11:42:11 +02:00
let motion = | editor : & mut Editor | {
let ( view , doc ) = current! ( editor ) ;
2021-02-22 07:50:41 +01:00
2021-10-27 11:42:11 +02:00
if let Some ( syntax ) = doc . syntax ( ) {
let text = doc . text ( ) . slice ( .. ) ;
let selection = object ::expand_selection ( syntax , text , doc . selection ( view . id ) ) ;
doc . set_selection ( view . id , selection ) ;
}
} ;
2021-12-03 04:48:07 +01:00
motion ( cx . editor ) ;
2021-10-27 11:42:11 +02:00
cx . editor . last_motion = Some ( Motion ( Box ::new ( motion ) ) ) ;
2021-02-22 07:50:41 +01:00
}
2021-03-22 09:58:49 +01:00
2021-06-17 13:08:05 +02:00
fn match_brackets ( cx : & mut Context ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-22 09:58:49 +01:00
if let Some ( syntax ) = doc . syntax ( ) {
2021-10-29 03:08:53 +02:00
let text = doc . text ( ) . slice ( .. ) ;
let selection = doc . selection ( view . id ) . clone ( ) . transform ( | range | {
2021-11-20 15:17:25 +01:00
if let Some ( pos ) =
match_brackets ::find_matching_bracket_fuzzy ( syntax , doc . text ( ) , range . anchor )
{
2021-10-29 03:08:53 +02:00
range . put_cursor ( text , pos , doc . mode = = Mode ::Select )
} else {
range
}
} ) ;
doc . set_selection ( view . id , selection ) ;
2021-03-22 09:58:49 +01:00
}
}
2021-03-24 10:01:26 +01:00
//
2021-06-17 13:08:05 +02:00
fn jump_forward ( cx : & mut Context ) {
2021-06-08 05:24:27 +02:00
let count = cx . count ( ) ;
2021-11-06 15:52:26 +01:00
let view = view_mut! ( cx . editor ) ;
2021-03-24 10:01:26 +01:00
if let Some ( ( id , selection ) ) = view . jumps . forward ( count ) {
view . doc = * id ;
2021-04-24 04:46:46 +02:00
let selection = selection . clone ( ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ; // refetch doc
2021-04-24 04:46:46 +02:00
doc . set_selection ( view . id , selection ) ;
2021-05-08 08:36:27 +02:00
align_view ( doc , view , Align ::Center ) ;
2021-03-24 10:01:26 +01:00
} ;
}
2021-06-17 13:08:05 +02:00
fn jump_backward ( cx : & mut Context ) {
2021-06-08 05:24:27 +02:00
let count = cx . count ( ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-24 10:01:26 +01:00
2021-06-08 18:40:38 +02:00
if let Some ( ( id , selection ) ) = view . jumps . backward ( view . id , doc , count ) {
2021-06-12 14:21:06 +02:00
// manually set the alternate_file as we cannot use the Editor::switch function here.
if view . doc ! = * id {
view . last_accessed_doc = Some ( view . doc )
}
2021-03-24 10:01:26 +01:00
view . doc = * id ;
2021-05-08 11:25:19 +02:00
let selection = selection . clone ( ) ;
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ; // refetch doc
2021-05-08 11:25:19 +02:00
doc . set_selection ( view . id , selection ) ;
2021-05-08 08:36:27 +02:00
align_view ( doc , view , Align ::Center ) ;
2021-03-24 10:01:26 +01:00
} ;
}
2021-03-30 11:38:26 +02:00
2021-12-10 03:46:24 +01:00
fn save_selection ( cx : & mut Context ) {
push_jump ( cx . editor ) ;
cx . editor
. set_status ( " Selection saved to jumplist " . to_owned ( ) ) ;
}
2021-06-17 13:08:05 +02:00
fn rotate_view ( cx : & mut Context ) {
2021-06-05 09:45:24 +02:00
cx . editor . focus_next ( )
}
2021-10-23 13:06:40 +02:00
fn jump_view_right ( cx : & mut Context ) {
cx . editor . focus_right ( )
}
fn jump_view_left ( cx : & mut Context ) {
cx . editor . focus_left ( )
}
fn jump_view_up ( cx : & mut Context ) {
cx . editor . focus_up ( )
}
fn jump_view_down ( cx : & mut Context ) {
cx . editor . focus_down ( )
}
2021-06-05 09:45:24 +02:00
// split helper, clear it later
fn split ( cx : & mut Context , action : Action ) {
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-05-08 11:25:19 +02:00
let id = doc . id ( ) ;
let selection = doc . selection ( view . id ) . clone ( ) ;
2021-08-19 05:52:07 +02:00
let offset = view . offset ;
2021-03-31 11:00:53 +02:00
2021-06-05 09:45:24 +02:00
cx . editor . switch ( id , action ) ;
2021-05-08 11:25:19 +02:00
// match the selection in the previous view
2021-06-18 00:09:10 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-08-19 05:52:07 +02:00
view . offset = offset ;
2021-05-08 11:25:19 +02:00
doc . set_selection ( view . id , selection ) ;
2021-03-31 11:00:53 +02:00
}
2021-06-17 13:08:05 +02:00
fn hsplit ( cx : & mut Context ) {
2021-06-05 09:45:24 +02:00
split ( cx , Action ::HorizontalSplit ) ;
}
2021-06-17 13:08:05 +02:00
fn vsplit ( cx : & mut Context ) {
2021-06-05 09:45:24 +02:00
split ( cx , Action ::VerticalSplit ) ;
}
2021-06-17 13:08:05 +02:00
fn wclose ( cx : & mut Context ) {
2021-09-17 07:42:14 +02:00
if cx . editor . tree . views ( ) . count ( ) = = 1 {
if let Err ( err ) = cmd ::buffers_remaining_impl ( cx . editor ) {
cx . editor . set_error ( err . to_string ( ) ) ;
return ;
}
}
2021-06-18 00:09:10 +02:00
let view_id = view! ( cx . editor ) . id ;
2021-06-05 09:45:24 +02:00
// close current split
helix-term/commands: implement buffer-close (bc, bclose) (#1035)
* helix-view/view: impl method to remove document from jumps
* helix-view/editor: impl close_document
* helix-view/editor: remove close_buffer argument from `close`
According to archseer, this was never implemented or used properly. Now
that we have a proper "buffer close" function, we can get rid of this.
* helix-term/commands: implement buffer-close (bc, bclose)
This behaves the same as Kakoune's `delete-buffer` / `db` command:
* With 3 files opened by the user with `:o ab`, `:o cd`, and `:o ef`:
* `buffer-close` once closes `ef` and switches to `cd`
* `buffer-close` again closes `cd` and switches to `ab`
* `buffer-close` again closes `ab` and switches to a scratch buffer
* With 3 files opened from the command line with `hx -- ab cd ef`:
* `buffer-close` once closes `ab` and switches to `cd`
* `buffer-close` again closes `cd` and switches to `ef`
* `buffer-close` again closes `ef` and switches to a scratch buffer
* With 1 file opened (`ab`):
* `buffer-close` once closes `ab` and switches to a scratch buffer
* `buffer-close` again closes the scratch buffer and switches to a new
scratch buffer
* helix-term/commands: implement buffer-close! (bclose!, bc!)
Namely, if you have a document open in multiple splits, all the splits
will be closed at the same time, leaving only splits without that
document focused (or a scratch buffer if they were all focused on that
buffer).
* helix-view/tree: reset focus if Tree is empty
2021-11-15 16:30:45 +01:00
cx . editor . close ( view_id ) ;
2021-06-05 09:45:24 +02:00
}
2021-11-11 03:32:23 +01:00
fn wonly ( cx : & mut Context ) {
let views = cx
. editor
. tree
. views ( )
. map ( | ( v , focus ) | ( v . id , focus ) )
. collect ::< Vec < _ > > ( ) ;
for ( view_id , focus ) in views {
if ! focus {
helix-term/commands: implement buffer-close (bc, bclose) (#1035)
* helix-view/view: impl method to remove document from jumps
* helix-view/editor: impl close_document
* helix-view/editor: remove close_buffer argument from `close`
According to archseer, this was never implemented or used properly. Now
that we have a proper "buffer close" function, we can get rid of this.
* helix-term/commands: implement buffer-close (bc, bclose)
This behaves the same as Kakoune's `delete-buffer` / `db` command:
* With 3 files opened by the user with `:o ab`, `:o cd`, and `:o ef`:
* `buffer-close` once closes `ef` and switches to `cd`
* `buffer-close` again closes `cd` and switches to `ab`
* `buffer-close` again closes `ab` and switches to a scratch buffer
* With 3 files opened from the command line with `hx -- ab cd ef`:
* `buffer-close` once closes `ab` and switches to `cd`
* `buffer-close` again closes `cd` and switches to `ef`
* `buffer-close` again closes `ef` and switches to a scratch buffer
* With 1 file opened (`ab`):
* `buffer-close` once closes `ab` and switches to a scratch buffer
* `buffer-close` again closes the scratch buffer and switches to a new
scratch buffer
* helix-term/commands: implement buffer-close! (bclose!, bc!)
Namely, if you have a document open in multiple splits, all the splits
will be closed at the same time, leaving only splits without that
document focused (or a scratch buffer if they were all focused on that
buffer).
* helix-view/tree: reset focus if Tree is empty
2021-11-15 16:30:45 +01:00
cx . editor . close ( view_id ) ;
2021-11-11 03:32:23 +01:00
}
}
}
2021-06-17 13:08:05 +02:00
fn select_register ( cx : & mut Context ) {
2021-06-05 04:21:31 +02:00
cx . on_next_key ( move | cx , event | {
2021-07-28 18:03:05 +02:00
if let Some ( ch ) = event . char ( ) {
2021-09-08 07:52:09 +02:00
cx . editor . selected_register = Some ( ch ) ;
2021-06-05 04:21:31 +02:00
}
} )
2021-11-15 16:31:20 +01:00
}
fn insert_register ( cx : & mut Context ) {
cx . on_next_key ( move | cx , event | {
if let Some ( ch ) = event . char ( ) {
cx . editor . selected_register = Some ( ch ) ;
paste_before ( cx ) ;
}
} )
2021-06-05 04:21:31 +02:00
}
2021-07-08 03:58:11 +02:00
fn align_view_top ( cx : & mut Context ) {
let ( view , doc ) = current! ( cx . editor ) ;
align_view ( doc , view , Align ::Top ) ;
}
2021-04-14 10:15:11 +02:00
2021-07-08 03:58:11 +02:00
fn align_view_center ( cx : & mut Context ) {
let ( view , doc ) = current! ( cx . editor ) ;
align_view ( doc , view , Align ::Center ) ;
}
2021-04-14 10:15:11 +02:00
2021-07-08 03:58:11 +02:00
fn align_view_bottom ( cx : & mut Context ) {
let ( view , doc ) = current! ( cx . editor ) ;
align_view ( doc , view , Align ::Bottom ) ;
}
fn align_view_middle ( cx : & mut Context ) {
let ( view , doc ) = current! ( cx . editor ) ;
2021-08-20 06:42:47 +02:00
let text = doc . text ( ) . slice ( .. ) ;
let pos = doc . selection ( view . id ) . primary ( ) . cursor ( text ) ;
let pos = coords_at_pos ( text , pos ) ;
2021-07-08 03:58:11 +02:00
2021-08-19 06:19:15 +02:00
view . offset . col = pos
. col
. saturating_sub ( ( view . inner_area ( ) . width as usize ) / 2 ) ;
2021-07-08 03:58:11 +02:00
}
fn scroll_up ( cx : & mut Context ) {
scroll ( cx , cx . count ( ) , Direction ::Backward ) ;
}
fn scroll_down ( cx : & mut Context ) {
scroll ( cx , cx . count ( ) , Direction ::Forward ) ;
2021-04-14 10:15:11 +02:00
}
2021-06-06 11:59:32 +02:00
2021-07-06 16:18:30 +02:00
fn select_textobject_around ( cx : & mut Context ) {
select_textobject ( cx , textobject ::TextObject ::Around ) ;
2021-06-06 11:59:32 +02:00
}
2021-06-19 16:59:19 +02:00
2021-07-06 16:18:30 +02:00
fn select_textobject_inner ( cx : & mut Context ) {
select_textobject ( cx , textobject ::TextObject ::Inside ) ;
2021-06-19 19:25:50 +02:00
}
2021-07-03 03:07:49 +02:00
fn select_textobject ( cx : & mut Context , objtype : textobject ::TextObject ) {
let count = cx . count ( ) ;
cx . on_next_key ( move | cx , event | {
2021-07-28 18:03:05 +02:00
if let Some ( ch ) = event . char ( ) {
2021-10-24 15:47:10 +02:00
let textobject = move | editor : & mut Editor | {
let ( view , doc ) = current! ( editor ) ;
let text = doc . text ( ) . slice ( .. ) ;
let textobject_treesitter = | obj_name : & str , range : Range | -> Range {
let ( lang_config , syntax ) = match doc . language_config ( ) . zip ( doc . syntax ( ) ) {
Some ( t ) = > t ,
None = > return range ,
} ;
textobject ::textobject_treesitter (
text ,
range ,
objtype ,
obj_name ,
syntax . tree ( ) . root_node ( ) ,
lang_config ,
count ,
)
2021-10-23 04:41:19 +02:00
} ;
2021-10-24 15:47:10 +02:00
let selection = doc . selection ( view . id ) . clone ( ) . transform ( | range | {
match ch {
2021-11-08 01:54:39 +01:00
'w' = > textobject ::textobject_word ( text , range , objtype , count , false ) ,
'W' = > textobject ::textobject_word ( text , range , objtype , count , true ) ,
2021-10-24 15:47:10 +02:00
'c' = > textobject_treesitter ( " class " , range ) ,
'f' = > textobject_treesitter ( " function " , range ) ,
'p' = > textobject_treesitter ( " parameter " , range ) ,
2021-11-11 03:33:31 +01:00
'm' = > {
let ch = text . char ( range . cursor ( text ) ) ;
if ! ch . is_ascii_alphanumeric ( ) {
textobject ::textobject_surround ( text , range , objtype , ch , count )
} else {
range
}
}
2021-10-24 15:47:10 +02:00
// TODO: cancel new ranges if inconsistent surround matches across lines
ch if ! ch . is_ascii_alphanumeric ( ) = > {
textobject ::textobject_surround ( text , range , objtype , ch , count )
}
_ = > range ,
2021-07-03 03:07:49 +02:00
}
2021-10-24 15:47:10 +02:00
} ) ;
doc . set_selection ( view . id , selection ) ;
} ;
2021-12-03 04:48:07 +01:00
textobject ( cx . editor ) ;
2021-10-24 15:47:10 +02:00
cx . editor . last_motion = Some ( Motion ( Box ::new ( textobject ) ) ) ;
2021-07-03 03:07:49 +02:00
}
} )
}
2021-06-19 19:25:50 +02:00
fn surround_add ( cx : & mut Context ) {
cx . on_next_key ( move | cx , event | {
2021-07-28 18:03:05 +02:00
if let Some ( ch ) = event . char ( ) {
2021-06-19 19:25:50 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
let selection = doc . selection ( view . id ) ;
let ( open , close ) = surround ::get_pair ( ch ) ;
2021-11-06 15:52:49 +01:00
let mut changes = Vec ::with_capacity ( selection . len ( ) * 2 ) ;
2021-07-01 20:57:12 +02:00
for range in selection . iter ( ) {
2021-07-01 18:51:24 +02:00
changes . push ( ( range . from ( ) , range . from ( ) , Some ( Tendril ::from_char ( open ) ) ) ) ;
changes . push ( ( range . to ( ) , range . to ( ) , Some ( Tendril ::from_char ( close ) ) ) ) ;
2021-06-19 19:25:50 +02:00
}
let transaction = Transaction ::change ( doc . text ( ) , changes . into_iter ( ) ) ;
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
}
} )
}
fn surround_replace ( cx : & mut Context ) {
let count = cx . count ( ) ;
cx . on_next_key ( move | cx , event | {
2021-07-28 18:03:05 +02:00
if let Some ( from ) = event . char ( ) {
2021-06-19 19:25:50 +02:00
cx . on_next_key ( move | cx , event | {
2021-07-28 18:03:05 +02:00
if let Some ( to ) = event . char ( ) {
2021-06-19 19:25:50 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
let text = doc . text ( ) . slice ( .. ) ;
let selection = doc . selection ( view . id ) ;
let change_pos = match surround ::get_surround_pos ( text , selection , from , count )
{
Some ( c ) = > c ,
None = > return ,
} ;
let ( open , close ) = surround ::get_pair ( to ) ;
let transaction = Transaction ::change (
doc . text ( ) ,
change_pos . iter ( ) . enumerate ( ) . map ( | ( i , & pos ) | {
2021-08-11 02:17:59 +02:00
(
pos ,
pos + 1 ,
Some ( Tendril ::from_char ( if i % 2 = = 0 { open } else { close } ) ) ,
)
2021-06-19 19:25:50 +02:00
} ) ,
) ;
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
}
} ) ;
}
} )
}
fn surround_delete ( cx : & mut Context ) {
let count = cx . count ( ) ;
cx . on_next_key ( move | cx , event | {
2021-07-28 18:03:05 +02:00
if let Some ( ch ) = event . char ( ) {
2021-06-19 19:25:50 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
let text = doc . text ( ) . slice ( .. ) ;
let selection = doc . selection ( view . id ) ;
let change_pos = match surround ::get_surround_pos ( text , selection , ch , count ) {
Some ( c ) = > c ,
None = > return ,
} ;
let transaction =
Transaction ::change ( doc . text ( ) , change_pos . into_iter ( ) . map ( | p | ( p , p + 1 , None ) ) ) ;
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
}
} )
}
2021-07-17 16:47:08 +02:00
2021-08-31 11:13:16 +02:00
#[ derive(Eq, PartialEq) ]
enum ShellBehavior {
Replace ,
Ignore ,
Insert ,
Append ,
}
fn shell_pipe ( cx : & mut Context ) {
2021-08-31 11:29:24 +02:00
shell ( cx , " pipe: " . into ( ) , ShellBehavior ::Replace ) ;
2021-08-31 11:13:16 +02:00
}
fn shell_pipe_to ( cx : & mut Context ) {
2021-08-31 11:29:24 +02:00
shell ( cx , " pipe-to: " . into ( ) , ShellBehavior ::Ignore ) ;
2021-08-31 11:13:16 +02:00
}
fn shell_insert_output ( cx : & mut Context ) {
2021-08-31 11:29:24 +02:00
shell ( cx , " insert-output: " . into ( ) , ShellBehavior ::Insert ) ;
2021-08-31 11:13:16 +02:00
}
fn shell_append_output ( cx : & mut Context ) {
2021-08-31 11:29:24 +02:00
shell ( cx , " append-output: " . into ( ) , ShellBehavior ::Append ) ;
2021-08-31 11:13:16 +02:00
}
fn shell_keep_pipe ( cx : & mut Context ) {
2021-09-01 04:01:19 +02:00
let prompt = Prompt ::new (
" keep-pipe: " . into ( ) ,
Some ( '|' ) ,
| _input : & str | Vec ::new ( ) ,
move | cx : & mut compositor ::Context , input : & str , event : PromptEvent | {
let shell = & cx . editor . config . shell ;
if event ! = PromptEvent ::Validate {
return ;
}
if input . is_empty ( ) {
return ;
}
let ( view , doc ) = current! ( cx . editor ) ;
let selection = doc . selection ( view . id ) ;
let mut ranges = SmallVec ::with_capacity ( selection . len ( ) ) ;
let old_index = selection . primary_index ( ) ;
let mut index : Option < usize > = None ;
let text = doc . text ( ) . slice ( .. ) ;
for ( i , range ) in selection . ranges ( ) . iter ( ) . enumerate ( ) {
let fragment = range . fragment ( text ) ;
let ( _output , success ) = match shell_impl ( shell , input , Some ( fragment . as_bytes ( ) ) ) {
Ok ( result ) = > result ,
Err ( err ) = > {
cx . editor . set_error ( err . to_string ( ) ) ;
return ;
}
} ;
// if the process exits successfully, keep the selection
if success {
ranges . push ( * range ) ;
if i > = old_index & & index . is_none ( ) {
index = Some ( ranges . len ( ) - 1 ) ;
}
}
}
if ranges . is_empty ( ) {
cx . editor . set_error ( " No selections remaining " . to_string ( ) ) ;
return ;
}
let index = index . unwrap_or_else ( | | ranges . len ( ) - 1 ) ;
doc . set_selection ( view . id , Selection ::new ( ranges , index ) ) ;
} ,
) ;
cx . push_layer ( Box ::new ( prompt ) ) ;
2021-08-31 11:13:16 +02:00
}
2021-09-01 03:46:35 +02:00
fn shell_impl (
shell : & [ String ] ,
cmd : & str ,
input : Option < & [ u8 ] > ,
) -> anyhow ::Result < ( Tendril , bool ) > {
2021-08-31 11:13:16 +02:00
use std ::io ::Write ;
use std ::process ::{ Command , Stdio } ;
2021-11-30 04:59:19 +01:00
ensure! ( ! shell . is_empty ( ) , " No shell set " ) ;
2021-09-01 04:01:19 +02:00
2021-09-01 03:46:35 +02:00
let mut process = match Command ::new ( & shell [ 0 ] )
. args ( & shell [ 1 .. ] )
. arg ( cmd )
. stdin ( Stdio ::piped ( ) )
. stdout ( Stdio ::piped ( ) )
. stderr ( Stdio ::piped ( ) )
. spawn ( )
{
Ok ( process ) = > process ,
Err ( e ) = > {
log ::error! ( " Failed to start shell: {} " , e ) ;
return Err ( e . into ( ) ) ;
}
} ;
if let Some ( input ) = input {
let mut stdin = process . stdin . take ( ) . unwrap ( ) ;
stdin . write_all ( input ) ? ;
}
let output = process . wait_with_output ( ) ? ;
2021-09-01 04:01:19 +02:00
if ! output . stderr . is_empty ( ) {
log ::error! ( " Shell error: {} " , String ::from_utf8_lossy ( & output . stderr ) ) ;
2021-09-01 03:46:35 +02:00
}
let tendril = Tendril ::try_from_byte_slice ( & output . stdout )
. map_err ( | _ | anyhow! ( " Process did not output valid UTF-8 " ) ) ? ;
Ok ( ( tendril , output . status . success ( ) ) )
}
fn shell ( cx : & mut Context , prompt : Cow < 'static , str > , behavior : ShellBehavior ) {
2021-08-31 11:13:16 +02:00
let pipe = match behavior {
2021-09-01 04:01:19 +02:00
ShellBehavior ::Replace | ShellBehavior ::Ignore = > true ,
2021-08-31 11:13:16 +02:00
ShellBehavior ::Insert | ShellBehavior ::Append = > false ,
} ;
let prompt = Prompt ::new (
2021-08-31 11:29:24 +02:00
prompt ,
2021-08-31 11:13:16 +02:00
Some ( '|' ) ,
| _input : & str | Vec ::new ( ) ,
move | cx : & mut compositor ::Context , input : & str , event : PromptEvent | {
2021-09-01 03:46:35 +02:00
let shell = & cx . editor . config . shell ;
2021-08-31 11:24:24 +02:00
if event ! = PromptEvent ::Validate {
return ;
}
2021-09-01 04:01:19 +02:00
if input . is_empty ( ) {
return ;
}
2021-08-31 11:24:24 +02:00
let ( view , doc ) = current! ( cx . editor ) ;
let selection = doc . selection ( view . id ) ;
2021-08-31 11:17:22 +02:00
2021-08-31 11:24:24 +02:00
let mut changes = Vec ::with_capacity ( selection . len ( ) ) ;
2021-09-01 03:46:35 +02:00
let text = doc . text ( ) . slice ( .. ) ;
2021-08-31 11:24:24 +02:00
for range in selection . ranges ( ) {
2021-09-01 03:46:35 +02:00
let fragment = range . fragment ( text ) ;
let ( output , success ) =
match shell_impl ( shell , input , pipe . then ( | | fragment . as_bytes ( ) ) ) {
Ok ( result ) = > result ,
Err ( err ) = > {
cx . editor . set_error ( err . to_string ( ) ) ;
2021-08-31 11:24:24 +02:00
return ;
}
} ;
2021-09-01 03:46:35 +02:00
2021-09-01 04:01:19 +02:00
if ! success {
cx . editor . set_error ( " Command failed " . to_string ( ) ) ;
return ;
2021-08-31 11:17:22 +02:00
}
2021-09-01 04:01:19 +02:00
let ( from , to ) = match behavior {
ShellBehavior ::Replace = > ( range . from ( ) , range . to ( ) ) ,
ShellBehavior ::Insert = > ( range . from ( ) , range . from ( ) ) ,
ShellBehavior ::Append = > ( range . to ( ) , range . to ( ) ) ,
_ = > ( range . from ( ) , range . from ( ) ) ,
} ;
changes . push ( ( from , to , Some ( output ) ) ) ;
2021-08-31 11:24:24 +02:00
}
2021-08-31 11:17:22 +02:00
2021-08-31 11:24:24 +02:00
if behavior ! = ShellBehavior ::Ignore {
2021-08-31 11:29:24 +02:00
let transaction = Transaction ::change ( doc . text ( ) , changes . into_iter ( ) ) ;
2021-08-31 11:24:24 +02:00
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
2021-08-31 11:13:16 +02:00
}
2021-11-18 10:46:27 +01:00
// after replace cursor may be out of bounds, do this to
// make sure cursor is in view and update scroll as well
view . ensure_cursor_in_view ( doc , cx . editor . config . scrolloff ) ;
2021-08-31 11:13:16 +02:00
} ,
) ;
cx . push_layer ( Box ::new ( prompt ) ) ;
}
2021-07-17 16:47:08 +02:00
fn suspend ( _cx : & mut Context ) {
#[ cfg(not(windows)) ]
signal_hook ::low_level ::raise ( signal_hook ::consts ::signal ::SIGTSTP ) . unwrap ( ) ;
}
2021-09-01 17:55:16 +02:00
fn add_newline_above ( cx : & mut Context ) {
add_newline_impl ( cx , Open ::Above ) ;
}
fn add_newline_below ( cx : & mut Context ) {
add_newline_impl ( cx , Open ::Below )
}
fn add_newline_impl ( cx : & mut Context , open : Open ) {
let count = cx . count ( ) ;
let ( view , doc ) = current! ( cx . editor ) ;
let selection = doc . selection ( view . id ) ;
let text = doc . text ( ) ;
let slice = text . slice ( .. ) ;
let changes = selection . into_iter ( ) . map ( | range | {
let ( start , end ) = range . line_range ( slice ) ;
let line = match open {
Open ::Above = > start ,
Open ::Below = > end + 1 ,
} ;
let pos = text . line_to_char ( line ) ;
(
pos ,
pos ,
Some ( doc . line_ending . as_str ( ) . repeat ( count ) . into ( ) ) ,
)
} ) ;
let transaction = Transaction ::change ( text , changes ) ;
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
}
2021-11-08 16:17:54 +01:00
fn rename_symbol ( cx : & mut Context ) {
let prompt = Prompt ::new (
2021-11-12 17:34:49 +01:00
" rename-to: " . into ( ) ,
2021-11-08 16:17:54 +01:00
None ,
| _input : & str | Vec ::new ( ) ,
move | cx : & mut compositor ::Context , input : & str , event : PromptEvent | {
if event ! = PromptEvent ::Validate {
return ;
}
log ::debug! ( " renaming to: {:?} " , input ) ;
let ( view , doc ) = current! ( cx . editor ) ;
let language_server = match doc . language_server ( ) {
Some ( language_server ) = > language_server ,
None = > return ,
} ;
let offset_encoding = language_server . offset_encoding ( ) ;
let pos = pos_to_lsp_pos (
doc . text ( ) ,
doc . selection ( view . id )
. primary ( )
. cursor ( doc . text ( ) . slice ( .. ) ) ,
offset_encoding ,
) ;
let task = language_server . rename_symbol ( doc . identifier ( ) , pos , input . to_string ( ) ) ;
let edits = block_on ( task ) . unwrap_or_default ( ) ;
log ::debug! ( " Edits from LSP: {:?} " , edits ) ;
2021-12-03 04:48:07 +01:00
apply_workspace_edit ( cx . editor , offset_encoding , & edits ) ;
2021-11-08 16:17:54 +01:00
} ,
) ;
cx . push_layer ( Box ::new ( prompt ) ) ;
}
2021-11-15 16:32:58 +01:00
/// Increment object under cursor by count.
fn increment ( cx : & mut Context ) {
increment_impl ( cx , cx . count ( ) as i64 ) ;
}
/// Decrement object under cursor by count.
fn decrement ( cx : & mut Context ) {
increment_impl ( cx , - ( cx . count ( ) as i64 ) ) ;
}
/// Decrement object under cursor by `amount`.
fn increment_impl ( cx : & mut Context , amount : i64 ) {
let ( view , doc ) = current! ( cx . editor ) ;
let selection = doc . selection ( view . id ) ;
2021-12-04 19:38:08 +01:00
let text = doc . text ( ) . slice ( .. ) ;
2021-11-15 16:32:58 +01:00
2021-12-04 19:38:08 +01:00
let changes : Vec < _ > = selection
2021-11-30 02:22:21 +01:00
. ranges ( )
. iter ( )
. filter_map ( | range | {
2021-12-04 19:38:08 +01:00
let incrementor : Box < dyn Increment > =
if let Some ( incrementor ) = DateTimeIncrementor ::from_range ( text , * range ) {
Box ::new ( incrementor )
} else if let Some ( incrementor ) = NumberIncrementor ::from_range ( text , * range ) {
Box ::new ( incrementor )
} else {
return None ;
} ;
2021-11-30 02:22:21 +01:00
let ( range , new_text ) = incrementor . increment ( amount ) ;
Some ( ( range . from ( ) , range . to ( ) , Some ( new_text ) ) )
} )
2021-12-04 19:38:08 +01:00
. collect ( ) ;
2021-11-21 18:38:41 +01:00
2021-11-30 02:22:21 +01:00
// Overlapping changes in a transaction will panic, so we need to find and remove them.
// For example, if there are cursors on each of the year, month, and day of `2021-11-29`,
// incrementing will give overlapping changes, with each change incrementing a different part of
// the date. Since these conflict with each other we remove these changes from the transaction
// so nothing happens.
let mut overlapping_indexes = HashSet ::new ( ) ;
for ( i , changes ) in changes . windows ( 2 ) . enumerate ( ) {
if changes [ 0 ] . 1 > changes [ 1 ] . 0 {
overlapping_indexes . insert ( i ) ;
overlapping_indexes . insert ( i + 1 ) ;
}
}
let changes = changes . into_iter ( ) . enumerate ( ) . filter_map ( | ( i , change ) | {
if overlapping_indexes . contains ( & i ) {
None
} else {
Some ( change )
}
2021-11-15 16:32:58 +01:00
} ) ;
if changes . clone ( ) . count ( ) > 0 {
let transaction = Transaction ::change ( doc . text ( ) , changes ) ;
let transaction = transaction . with_selection ( selection . clone ( ) ) ;
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
}
}
2021-12-12 13:16:48 +01:00
fn record_macro ( cx : & mut Context ) {
if let Some ( ( reg , mut keys ) ) = cx . editor . macro_recording . take ( ) {
// Remove the keypress which ends the recording
keys . pop ( ) ;
let s = keys
. into_iter ( )
2021-12-12 14:51:57 +01:00
. map ( | key | key . to_string ( ) )
2021-12-12 13:16:48 +01:00
. collect ::< Vec < _ > > ( )
. join ( " " ) ;
cx . editor . registers . get_mut ( reg ) . write ( vec! [ s ] ) ;
cx . editor
. set_status ( format! ( " Recorded to register {} " , reg ) ) ;
} else {
let reg = cx . register . take ( ) . unwrap_or ( '@' ) ;
cx . editor . macro_recording = Some ( ( reg , Vec ::new ( ) ) ) ;
cx . editor
. set_status ( format! ( " Recording to register {} " , reg ) ) ;
}
}
2021-12-12 14:51:57 +01:00
fn replay_macro ( cx : & mut Context ) {
2021-12-12 13:16:48 +01:00
let reg = cx . register . unwrap_or ( '@' ) ;
2021-12-12 14:54:14 +01:00
// TODO: macro keys should be parsed one by one and not space delimited (see kak)
2021-12-12 14:32:55 +01:00
let keys : Vec < KeyEvent > = if let Some ( [ keys ] ) = cx . editor . registers . read ( reg ) {
match keys . split_whitespace ( ) . map ( str ::parse ) . collect ( ) {
Ok ( keys ) = > keys ,
Err ( err ) = > {
cx . editor . set_error ( format! ( " Invalid macro: {} " , err ) ) ;
return ;
}
2021-12-12 13:16:48 +01:00
}
2021-12-12 14:32:55 +01:00
} else {
cx . editor . set_error ( format! ( " Register [ {} ] empty " , reg ) ) ;
return ;
2021-12-12 13:16:48 +01:00
} ;
let count = cx . count ( ) ;
cx . callback = Some ( Box ::new (
move | compositor : & mut Compositor , cx : & mut compositor ::Context | {
for _ in 0 .. count {
for & key in keys . iter ( ) {
compositor . handle_event ( crossterm ::event ::Event ::Key ( key . into ( ) ) , cx ) ;
}
}
} ,
) ) ;
}