2020-09-19 18:16:00 +04:00
use helix_core ::{
2021-06-08 11:11:45 +04:00
comment , coords_at_pos , find_first_non_whitespace_char , find_root , graphemes , indent ,
2021-06-21 22:08:05 +04:00
line_ending ::{
get_line_ending , get_line_ending_of_str , line_end_char_index , str_is_line_ending ,
} ,
2021-06-08 10:25:55 +04:00
match_brackets ,
2021-03-18 10:07:02 +04:00
movement ::{ self , Direction } ,
object , pos_at_coords ,
2021-02-22 10:14:02 +04:00
regex ::{ self , Regex } ,
2021-06-15 07:26:05 +04:00
register ::{ self , Register , Registers } ,
2021-06-21 22:08:05 +04:00
search , selection , Change , ChangeSet , LineEnding , Position , Range , Rope , RopeGraphemes ,
RopeSlice , Selection , SmallVec , Tendril , Transaction , DEFAULT_LINE_ENDING ,
2020-09-19 18:16:00 +04:00
} ;
2020-12-10 13:13:42 +04:00
2021-04-01 06:37:18 +04:00
use helix_view ::{
2021-06-14 05:09:22 +04:00
document ::{ IndentStyle , Mode } ,
2021-04-01 06:37:18 +04:00
view ::{ View , PADDING } ,
Document , DocumentId , Editor , ViewId ,
} ;
2021-06-17 15:08:05 +04:00
use anyhow ::anyhow ;
2021-04-01 06:37:18 +04:00
use helix_lsp ::{
lsp ,
2021-06-12 16:45:21 +04:00
util ::{ lsp_pos_to_pos , lsp_range_to_range , pos_to_lsp_pos , range_to_lsp_range } ,
2021-06-20 23:31:45 +04:00
LspProgressMap , OffsetEncoding ,
2021-04-01 06:37:18 +04:00
} ;
2021-06-17 15:08:05 +04:00
use insert ::* ;
2021-06-11 16:57:07 +04:00
use movement ::Movement ;
2020-09-19 18:16:00 +04:00
2021-03-22 07:40:07 +04:00
use crate ::{
2021-03-26 11:02:13 +04:00
compositor ::{ Callback , Component , Compositor } ,
2021-03-27 07:06:40 +04:00
ui ::{ self , Completion , Picker , Popup , Prompt , PromptEvent } ,
2021-03-22 07:40:07 +04:00
} ;
2020-12-10 13:13:42 +04:00
2021-04-01 06:37:18 +04:00
use crate ::application ::{ LspCallbackWrapper , LspCallbacks } ;
use futures_util ::FutureExt ;
2021-06-17 15:08:05 +04:00
use std ::{ fmt , future ::Future , path ::Display , str ::FromStr } ;
2021-04-01 06:37:18 +04:00
2021-06-11 16:57:07 +04:00
use std ::{
borrow ::Cow ,
path ::{ Path , PathBuf } ,
} ;
2021-03-07 22:41:49 +04:00
2021-06-19 18:59:19 +04:00
use crossterm ::event ::{ KeyCode , KeyEvent } ;
2021-04-01 06:37:18 +04:00
use once_cell ::sync ::Lazy ;
2021-03-26 11:02:13 +04:00
2021-01-21 11:55:46 +04:00
pub struct Context < ' a > {
2021-06-15 07:26:05 +04:00
pub selected_register : helix_view ::RegisterSelection ,
2021-06-15 08:29:03 +04:00
pub count : Option < std ::num ::NonZeroUsize > ,
2021-01-21 11:55:46 +04:00
pub editor : & ' a mut Editor ,
2020-12-11 13:25:09 +04:00
pub callback : Option < crate ::compositor ::Callback > ,
2021-03-11 05:44:38 +04:00
pub on_next_key_callback : Option < Box < dyn FnOnce ( & mut Context , KeyEvent ) > > ,
2021-03-26 11:02:13 +04:00
pub callbacks : & ' a mut LspCallbacks ,
2020-10-30 09:09:59 +04:00
}
2021-01-21 11:55:46 +04:00
impl < ' a > Context < ' a > {
2021-02-21 14:04:31 +04:00
/// Push a new component onto the compositor.
2021-03-26 11:02:13 +04:00
pub fn push_layer ( & mut self , mut component : Box < dyn Component > ) {
2021-05-09 13:02:31 +04:00
self . callback = Some ( Box ::new ( | compositor : & mut Compositor | {
compositor . push ( component )
} ) ) ;
2021-02-21 14:04:31 +04:00
}
2021-03-11 05:44:38 +04: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 11:02:13 +04: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 ) ? ;
let call : LspCallbackWrapper =
Box ::new ( move | editor : & mut Editor , compositor : & mut Compositor | {
callback ( editor , compositor , response )
} ) ;
Ok ( call )
} ) ;
self . callbacks . push ( callback ) ;
}
2021-06-08 07:24:27 +04:00
2021-06-17 10:19:02 +04:00
/// Returns 1 if no explicit count was provided
2021-06-08 07:24:27 +04:00
#[ inline ]
pub fn count ( & self ) -> usize {
2021-06-15 08:29:03 +04:00
self . count . map_or ( 1 , | v | v . get ( ) )
2021-06-08 07:24:27 +04:00
}
2021-01-21 11:55:46 +04:00
}
2021-05-08 10:36:27 +04:00
enum Align {
Top ,
Center ,
Bottom ,
}
fn align_view ( doc : & Document , view : & mut View , align : Align ) {
let pos = doc . selection ( view . id ) . cursor ( ) ;
let line = doc . text ( ) . char_to_line ( pos ) ;
let relative = match align {
Align ::Center = > view . area . height as usize / 2 ,
Align ::Top = > 0 ,
Align ::Bottom = > view . area . height as usize ,
} ;
view . first_line = line . saturating_sub ( relative ) ;
}
2021-06-17 15:08:05 +04:00
/// A command is composed of a static name, and a function that takes the current state plus a count,
/// and does a side-effect on the state (usually by creating and applying a transaction).
#[ derive(Copy, Clone) ]
pub struct Command ( & 'static str , fn ( cx : & mut Context ) ) ;
macro_rules ! commands {
( $( $name :ident ) , * ) = > {
$(
#[ allow(non_upper_case_globals) ]
pub const $name : Self = Self ( stringify! ( $name ) , $name ) ;
) *
pub const COMMAND_LIST : & 'static [ Self ] = & [
$( Self ::$name , ) *
] ;
}
}
2020-06-07 19:15:39 +04:00
2021-06-17 15:08:05 +04:00
impl Command {
pub fn execute ( & self , cx : & mut Context ) {
( self . 1 ) ( cx ) ;
}
pub fn name ( & self ) -> & 'static str {
self . 0
}
commands! (
move_char_left ,
move_char_right ,
move_line_up ,
move_line_down ,
move_line_end ,
move_line_start ,
move_first_nonwhitespace ,
move_next_word_start ,
move_prev_word_start ,
move_next_word_end ,
move_file_start ,
move_file_end ,
extend_next_word_start ,
extend_prev_word_start ,
extend_next_word_end ,
find_till_char ,
find_next_char ,
extend_till_char ,
extend_next_char ,
till_prev_char ,
find_prev_char ,
extend_till_prev_char ,
extend_prev_char ,
extend_first_nonwhitespace ,
replace ,
page_up ,
page_down ,
half_page_up ,
half_page_down ,
extend_char_left ,
extend_char_right ,
extend_line_up ,
extend_line_down ,
extend_line_end ,
extend_line_start ,
select_all ,
select_regex ,
split_selection ,
split_selection_on_newline ,
search ,
search_next ,
extend_search_next ,
search_selection ,
extend_line ,
delete_selection ,
change_selection ,
collapse_selection ,
flip_selections ,
insert_mode ,
append_mode ,
command_mode ,
file_picker ,
buffer_picker ,
symbol_picker ,
prepend_to_line ,
append_to_line ,
open_below ,
open_above ,
normal_mode ,
goto_mode ,
select_mode ,
exit_select_mode ,
goto_definition ,
goto_type_definition ,
goto_implementation ,
goto_reference ,
goto_first_diag ,
goto_last_diag ,
goto_next_diag ,
goto_prev_diag ,
signature_help ,
insert_tab ,
insert_newline ,
delete_char_backward ,
delete_char_forward ,
delete_word_backward ,
undo ,
redo ,
yank ,
2021-06-19 10:27:43 +04:00
yank_joined_to_clipboard ,
yank_main_selection_to_clipboard ,
2021-06-17 15:08:05 +04:00
replace_with_yanked ,
2021-06-19 10:27:43 +04:00
replace_selections_with_clipboard ,
2021-06-17 15:08:05 +04:00
paste_after ,
paste_before ,
2021-06-19 10:27:43 +04:00
paste_clipboard_after ,
paste_clipboard_before ,
2021-06-17 15:08:05 +04:00
indent ,
unindent ,
format_selections ,
join_selections ,
keep_selections ,
keep_primary_selection ,
save ,
completion ,
hover ,
toggle_comments ,
expand_selection ,
match_brackets ,
jump_forward ,
jump_backward ,
window_mode ,
rotate_view ,
hsplit ,
vsplit ,
wclose ,
select_register ,
space_mode ,
view_mode ,
left_bracket_mode ,
right_bracket_mode
) ;
}
fn move_char_left ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-18 10:07:02 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-04-01 05:39:46 +04:00
let selection = doc . selection ( view . id ) . transform ( | range | {
2021-06-11 16:57:07 +04:00
movement ::move_horizontally ( text , range , Direction ::Backward , count , Movement ::Move )
2021-03-18 10:07:02 +04:00
} ) ;
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2020-06-07 19:15:39 +04:00
}
2021-06-17 15:08:05 +04:00
fn move_char_right ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-18 10:07:02 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-04-01 05:39:46 +04:00
let selection = doc . selection ( view . id ) . transform ( | range | {
2021-06-11 16:57:07 +04:00
movement ::move_horizontally ( text , range , Direction ::Forward , count , Movement ::Move )
2021-03-18 10:07:02 +04:00
} ) ;
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2020-06-07 19:15:39 +04:00
}
2021-06-17 15:08:05 +04:00
fn move_line_up ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-18 10:07:02 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-04-01 05:39:46 +04:00
let selection = doc . selection ( view . id ) . transform ( | range | {
2021-06-11 16:57:07 +04:00
movement ::move_vertically ( text , range , Direction ::Backward , count , Movement ::Move )
2021-03-18 10:07:02 +04:00
} ) ;
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2020-06-07 19:15:39 +04:00
}
2021-06-17 15:08:05 +04:00
fn move_line_down ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-18 10:07:02 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-04-01 05:39:46 +04:00
let selection = doc . selection ( view . id ) . transform ( | range | {
2021-06-11 16:57:07 +04:00
movement ::move_vertically ( text , range , Direction ::Forward , count , Movement ::Move )
2021-03-18 10:07:02 +04:00
} ) ;
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2020-06-07 19:15:39 +04:00
}
2020-09-05 17:01:05 +04:00
2021-06-17 15:08:05 +04:00
fn move_line_end ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2020-09-25 18:04:58 +04:00
2021-05-18 13:27:52 +04:00
let selection = doc . selection ( view . id ) . transform ( | range | {
let text = doc . text ( ) ;
let line = text . char_to_line ( range . head ) ;
2020-09-25 18:04:58 +04:00
2021-06-21 02:09:10 +04:00
let pos = line_end_char_index ( & text . slice ( .. ) , line ) ;
2021-06-19 16:03:14 +04:00
2021-05-18 13:27:52 +04:00
Range ::new ( pos , pos )
} ) ;
2020-09-25 18:04:58 +04:00
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2020-09-25 18:04:58 +04:00
}
2021-06-17 15:08:05 +04:00
fn move_line_start ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2020-09-25 18:04:58 +04:00
2021-05-18 13:27:52 +04:00
let selection = doc . selection ( view . id ) . transform ( | range | {
let text = doc . text ( ) ;
let line = text . char_to_line ( range . head ) ;
2020-09-25 18:04:58 +04:00
2021-05-18 13:27:52 +04:00
// adjust to start of the line
let pos = text . line_to_char ( line ) ;
Range ::new ( pos , pos )
} ) ;
2020-09-25 18:04:58 +04:00
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2020-09-25 18:04:58 +04:00
}
2021-06-17 15:08:05 +04:00
fn move_first_nonwhitespace ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-06-08 10:25:55 +04:00
let selection = doc . selection ( view . id ) . transform ( | range | {
let text = doc . text ( ) ;
let line_idx = text . char_to_line ( range . head ) ;
2021-06-08 11:11:45 +04:00
if let Some ( pos ) = find_first_non_whitespace_char ( text . line ( line_idx ) ) {
2021-06-08 10:25:55 +04:00
let pos = pos + text . line_to_char ( line_idx ) ;
Range ::new ( pos , pos )
} else {
range
}
} ) ;
doc . set_selection ( view . id , selection ) ;
}
2021-03-04 09:12:19 +04:00
// TODO: move vs extend could take an extra type Extend/Move that would
// Range::new(if Move { pos } if Extend { range.anchor }, pos)
// since these all really do the same thing
2021-06-17 15:08:05 +04:00
fn move_next_word_start ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-04 09:07:35 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2020-09-24 14:16:35 +04:00
2021-06-11 16:57:07 +04:00
let selection = doc
. selection ( view . id )
. transform ( | range | movement ::move_next_word_start ( text , range , count ) ) ;
2021-03-04 09:07:35 +04:00
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2020-09-24 14:16:35 +04:00
}
2021-06-17 15:08:05 +04:00
fn move_prev_word_start ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-04 09:07:35 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2020-09-24 14:16:35 +04:00
2021-06-11 16:57:07 +04:00
let selection = doc
. selection ( view . id )
. transform ( | range | movement ::move_prev_word_start ( text , range , count ) ) ;
2021-03-04 09:07:35 +04:00
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2020-09-24 14:16:35 +04:00
}
2021-06-17 15:08:05 +04:00
fn move_next_word_end ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-04 09:07:35 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2020-09-24 14:16:35 +04:00
2021-04-05 11:35:04 +04:00
let selection = doc
. selection ( view . id )
2021-06-11 16:57:07 +04:00
. transform ( | range | movement ::move_next_word_end ( text , range , count ) ) ;
2021-03-04 09:07:35 +04:00
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2020-09-24 14:16:35 +04:00
}
2021-06-17 15:08:05 +04:00
fn move_file_start ( cx : & mut Context ) {
2021-05-06 12:15:49 +04:00
push_jump ( cx . editor ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , Selection ::point ( 0 ) ) ;
2020-10-05 01:47:37 +04:00
}
2021-06-17 15:08:05 +04:00
fn move_file_end ( cx : & mut Context ) {
2021-05-06 12:15:49 +04:00
push_jump ( cx . editor ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-02-21 14:04:31 +04:00
let text = doc . text ( ) ;
2020-10-07 03:41:09 +04:00
let last_line = text . line_to_char ( text . len_lines ( ) . saturating_sub ( 2 ) ) ;
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , Selection ::point ( last_line ) ) ;
2020-10-05 01:47:37 +04:00
}
2021-06-17 15:08:05 +04:00
fn extend_next_word_start ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-04 09:07:35 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-04-01 05:39:46 +04:00
let selection = doc . selection ( view . id ) . transform ( | mut range | {
2021-06-11 16:57:07 +04:00
let word = movement ::move_next_word_start ( text , range , count ) ;
2021-04-05 11:35:04 +04:00
let pos = word . head ;
2021-03-04 09:07:35 +04:00
Range ::new ( range . anchor , pos )
2021-02-24 12:29:28 +04:00
} ) ;
2021-02-12 11:49:24 +04:00
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2021-01-22 11:31:49 +04:00
}
2021-06-17 15:08:05 +04:00
fn extend_prev_word_start ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-04 09:07:35 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-04-01 05:39:46 +04:00
let selection = doc . selection ( view . id ) . transform ( | mut range | {
2021-06-11 16:57:07 +04:00
let word = movement ::move_prev_word_start ( text , range , count ) ;
2021-04-05 11:35:04 +04:00
let pos = word . head ;
2021-03-04 09:07:35 +04:00
Range ::new ( range . anchor , pos )
2021-02-24 12:29:28 +04:00
} ) ;
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2021-02-12 11:49:24 +04:00
}
2021-06-17 15:08:05 +04:00
fn extend_next_word_end ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-04 09:07:35 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-04-01 05:39:46 +04:00
let selection = doc . selection ( view . id ) . transform ( | mut range | {
2021-06-11 16:57:07 +04:00
let word = movement ::move_next_word_end ( text , range , count ) ;
2021-04-05 11:35:04 +04:00
let pos = word . head ;
2021-03-04 09:07:35 +04:00
Range ::new ( range . anchor , pos )
2021-02-24 12:29:28 +04:00
} ) ;
2021-02-12 11:49:24 +04:00
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2021-01-22 11:31:49 +04:00
}
2021-03-11 11:14:52 +04:00
#[ inline ]
2021-06-15 08:29:03 +04:00
fn find_char_impl < F > ( cx : & mut Context , search_fn : F , inclusive : bool , extend : bool )
2021-03-11 11:14:52 +04:00
where
// TODO: make an options struct for and abstract this Fn into a searcher type
// use the definition for w/b/e too
2021-06-05 18:25:45 +04:00
F : Fn ( RopeSlice , char , usize , usize , bool ) -> Option < usize > + 'static ,
2021-03-11 11:14:52 +04:00
{
2021-03-11 05:44:38 +04: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 07:24:27 +04:00
let count = cx . count ( ) ;
2021-03-11 05:44:38 +04:00
// need to wait for next key
2021-06-21 02:09:10 +04:00
// TODO: should this be done by grapheme rather than char? For example,
2021-06-21 22:59:03 +04:00
// we can't properly handle the line-ending CRLF case here in terms of char.
2021-03-11 05:44:38 +04:00
cx . on_next_key ( move | cx , event | {
2021-06-11 11:30:27 +04:00
let ch = match event {
KeyEvent {
code : KeyCode ::Enter ,
..
2021-06-21 22:59:03 +04: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.
{
current! ( cx . editor )
. 1
. line_ending
. as_str ( )
. chars ( )
. next ( )
. unwrap ( )
}
2021-06-11 11:30:27 +04:00
KeyEvent {
code : KeyCode ::Char ( ch ) ,
..
} = > ch ,
_ = > return ,
} ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-06-11 11:30:27 +04:00
let text = doc . text ( ) . slice ( .. ) ;
let selection = doc . selection ( view . id ) . transform ( | mut range | {
search_fn ( text , ch , range . head , count , inclusive ) . map_or ( range , | pos | {
if extend {
Range ::new ( range . anchor , pos )
} else {
// select
Range ::new ( range . head , pos )
}
// or (pos, pos) to move to found val
} )
} ) ;
doc . set_selection ( view . id , selection ) ;
2021-03-11 05:44:38 +04:00
} )
}
2021-06-17 15:08:05 +04:00
fn find_till_char ( cx : & mut Context ) {
2021-06-15 08:29:03 +04:00
find_char_impl (
2021-03-11 11:14:52 +04:00
cx ,
search ::find_nth_next ,
false , /* inclusive */
false , /* extend */
)
}
2021-06-17 15:08:05 +04:00
fn find_next_char ( cx : & mut Context ) {
2021-06-15 08:29:03 +04:00
find_char_impl (
2021-03-11 11:14:52 +04:00
cx ,
search ::find_nth_next ,
true , /* inclusive */
false , /* extend */
)
}
2021-06-17 15:08:05 +04:00
fn extend_till_char ( cx : & mut Context ) {
2021-06-15 08:29:03 +04:00
find_char_impl (
2021-03-11 11:14:52 +04:00
cx ,
search ::find_nth_next ,
false , /* inclusive */
true , /* extend */
)
}
2021-06-17 15:08:05 +04:00
fn extend_next_char ( cx : & mut Context ) {
2021-06-15 08:29:03 +04:00
find_char_impl (
2021-03-11 11:14:52 +04:00
cx ,
search ::find_nth_next ,
true , /* inclusive */
true , /* extend */
)
}
2021-06-17 15:08:05 +04:00
fn till_prev_char ( cx : & mut Context ) {
2021-06-15 08:29:03 +04:00
find_char_impl (
2021-03-11 11:14:52 +04:00
cx ,
search ::find_nth_prev ,
false , /* inclusive */
false , /* extend */
)
}
2021-06-17 15:08:05 +04:00
fn find_prev_char ( cx : & mut Context ) {
2021-06-15 08:29:03 +04:00
find_char_impl (
2021-03-11 11:14:52 +04:00
cx ,
search ::find_nth_prev ,
true , /* inclusive */
false , /* extend */
)
}
2021-06-17 15:08:05 +04:00
fn extend_till_prev_char ( cx : & mut Context ) {
2021-06-15 08:29:03 +04:00
find_char_impl (
2021-03-11 11:14:52 +04:00
cx ,
search ::find_nth_prev ,
false , /* inclusive */
true , /* extend */
)
}
2021-06-17 15:08:05 +04:00
fn extend_prev_char ( cx : & mut Context ) {
2021-06-15 08:29:03 +04:00
find_char_impl (
2021-03-11 11:14:52 +04:00
cx ,
search ::find_nth_prev ,
true , /* inclusive */
true , /* extend */
)
}
2021-06-17 15:08:05 +04:00
fn extend_first_nonwhitespace ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-06-08 10:25:55 +04:00
let selection = doc . selection ( view . id ) . transform ( | range | {
let text = doc . text ( ) ;
let line_idx = text . char_to_line ( range . head ) ;
2021-06-08 11:11:45 +04:00
if let Some ( pos ) = find_first_non_whitespace_char ( text . line ( line_idx ) ) {
2021-06-08 10:25:55 +04:00
let pos = pos + text . line_to_char ( line_idx ) ;
Range ::new ( range . anchor , pos )
} else {
range
}
} ) ;
doc . set_selection ( view . id , selection ) ;
}
2021-06-17 15:08:05 +04:00
fn replace ( cx : & mut Context ) {
2021-06-21 22:08:05 +04:00
let mut buf = [ 0 u8 ; 4 ] ; // To hold utf8 encoded char.
2021-03-19 13:01:08 +04:00
// need to wait for next key
cx . on_next_key ( move | cx , event | {
2021-06-21 22:08:05 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-06-08 10:44:26 +04:00
let ch = match event {
KeyEvent {
code : KeyCode ::Char ( ch ) ,
..
2021-06-21 22:08:05 +04:00
} = > Some ( & ch . encode_utf8 ( & mut buf [ .. ] ) [ .. ] ) ,
2021-06-08 10:44:26 +04:00
KeyEvent {
code : KeyCode ::Enter ,
..
2021-06-21 22:08:05 +04:00
} = > Some ( doc . line_ending . as_str ( ) ) ,
2021-06-08 10:44:26 +04:00
_ = > None ,
} ;
if let Some ( ch ) = ch {
2021-03-19 13:01:08 +04:00
let transaction =
2021-04-01 05:39:46 +04:00
Transaction ::change_by_selection ( doc . text ( ) , doc . selection ( view . id ) , | range | {
2021-06-07 21:03:44 +04:00
let max_to = doc . text ( ) . len_chars ( ) . saturating_sub ( 1 ) ;
let to = std ::cmp ::min ( max_to , range . to ( ) + 1 ) ;
2021-06-21 22:08:05 +04:00
let text : String = RopeGraphemes ::new ( doc . text ( ) . slice ( range . from ( ) .. to ) )
. map ( | g | {
let cow : Cow < str > = g . into ( ) ;
if str_is_line_ending ( & cow ) {
cow
} else {
ch . into ( )
}
} )
2021-06-07 23:28:04 +04:00
. collect ( ) ;
2021-06-08 08:42:35 +04:00
( range . from ( ) , to , Some ( text . into ( ) ) )
2021-03-19 13:01:08 +04:00
} ) ;
2021-04-01 05:39:46 +04:00
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
2021-03-19 13:01:08 +04:00
}
} )
}
2021-03-23 12:47:40 +04:00
fn scroll ( cx : & mut Context , offset : usize , direction : Direction ) {
2021-03-04 11:13:26 +04:00
use Direction ::* ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-31 12:17:01 +04:00
let cursor = coords_at_pos ( doc . text ( ) . slice ( .. ) , doc . selection ( view . id ) . cursor ( ) ) ;
2021-03-16 13:27:57 +04:00
let doc_last_line = doc . text ( ) . len_lines ( ) - 1 ;
2021-03-04 11:13:26 +04:00
2021-03-23 12:47:40 +04:00
let last_line = view . last_line ( doc ) ;
2021-03-04 11:13:26 +04:00
if direction = = Backward & & view . first_line = = 0
| | direction = = Forward & & last_line = = doc_last_line
{
2020-10-07 09:15:32 +04:00
return ;
}
2021-04-14 09:27:47 +04:00
let scrolloff = PADDING . min ( view . area . height as usize / 2 ) ; // TODO: user pref
2021-03-04 11:13:26 +04:00
// cursor visual offset
2021-04-14 09:27:47 +04:00
// TODO: only if dragging via mouse?
// let cursor_off = cursor.row - view.first_line;
2020-10-05 19:58:16 +04:00
2021-03-04 11:13:26 +04:00
view . first_line = match direction {
Forward = > view . first_line + offset ,
Backward = > view . first_line . saturating_sub ( offset ) ,
2020-10-07 08:58:13 +04:00
}
2021-03-04 11:13:26 +04:00
. min ( doc_last_line ) ;
2021-04-14 09:27:47 +04:00
// recalculate last line
let last_line = view . last_line ( doc ) ;
2021-03-04 11:13:26 +04:00
// clamp into viewport
2021-06-02 08:19:21 +04:00
let line = cursor
. row
2021-06-04 06:36:28 +04:00
. max ( view . first_line + scrolloff )
. min ( last_line . saturating_sub ( scrolloff ) ) ;
2021-03-04 11:13:26 +04:00
2021-03-23 12:47:40 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-03-04 11:13:26 +04:00
let pos = pos_at_coords ( text , Position ::new ( line , cursor . col ) ) ; // this func will properly truncate to line end
2021-03-23 12:47:40 +04:00
2021-04-14 09:27:47 +04:00
// TODO: only manipulate main selection
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , Selection ::point ( pos ) ) ;
2020-10-05 19:18:29 +04:00
}
2021-06-17 15:08:05 +04:00
fn page_up ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let view = view! ( cx . editor ) ;
2021-03-23 12:47:40 +04:00
let offset = view . area . height as usize ;
scroll ( cx , offset , Direction ::Backward ) ;
2021-03-04 11:13:26 +04:00
}
2020-10-06 12:32:30 +04:00
2021-06-17 15:08:05 +04:00
fn page_down ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let view = view! ( cx . editor ) ;
2021-03-23 12:47:40 +04:00
let offset = view . area . height as usize ;
scroll ( cx , offset , Direction ::Forward ) ;
2020-10-05 19:18:29 +04:00
}
2021-06-17 15:08:05 +04:00
fn half_page_up ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let view = view! ( cx . editor ) ;
2021-03-23 12:47:40 +04:00
let offset = view . area . height as usize / 2 ;
scroll ( cx , offset , Direction ::Backward ) ;
2020-10-05 19:18:29 +04:00
}
2021-06-17 15:08:05 +04:00
fn half_page_down ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let view = view! ( cx . editor ) ;
2021-03-23 12:47:40 +04:00
let offset = view . area . height as usize / 2 ;
scroll ( cx , offset , Direction ::Forward ) ;
2020-10-05 19:18:29 +04:00
}
2020-09-07 12:08:28 +04:00
2021-06-17 15:08:05 +04:00
fn extend_char_left ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-18 10:07:02 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-04-01 05:39:46 +04:00
let selection = doc . selection ( view . id ) . transform ( | range | {
2021-06-11 16:57:07 +04:00
movement ::move_horizontally ( text , range , Direction ::Backward , count , Movement ::Extend )
2021-03-18 10:07:02 +04:00
} ) ;
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2020-09-28 20:00:35 +04:00
}
2021-06-17 15:08:05 +04:00
fn extend_char_right ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-18 10:07:02 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-04-01 05:39:46 +04:00
let selection = doc . selection ( view . id ) . transform ( | range | {
2021-06-11 16:57:07 +04:00
movement ::move_horizontally ( text , range , Direction ::Forward , count , Movement ::Extend )
2021-03-18 10:07:02 +04:00
} ) ;
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2020-09-28 20:00:35 +04:00
}
2021-06-17 15:08:05 +04:00
fn extend_line_up ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-18 10:07:02 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-04-01 05:39:46 +04:00
let selection = doc . selection ( view . id ) . transform ( | range | {
2021-06-11 16:57:07 +04:00
movement ::move_vertically ( text , range , Direction ::Backward , count , Movement ::Extend )
2021-03-18 10:07:02 +04:00
} ) ;
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2020-09-28 20:00:35 +04:00
}
2021-06-17 15:08:05 +04:00
fn extend_line_down ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-18 10:07:02 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-04-01 05:39:46 +04:00
let selection = doc . selection ( view . id ) . transform ( | range | {
2021-06-11 16:57:07 +04:00
movement ::move_vertically ( text , range , Direction ::Forward , count , Movement ::Extend )
2021-03-18 10:07:02 +04:00
} ) ;
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2020-09-28 20:00:35 +04:00
}
2021-06-17 15:08:05 +04:00
fn extend_line_end ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-06-05 04:25:46 +04:00
let selection = doc . selection ( view . id ) . transform ( | range | {
let text = doc . text ( ) ;
let line = text . char_to_line ( range . head ) ;
2021-06-21 02:09:10 +04:00
let pos = line_end_char_index ( & text . slice ( .. ) , line ) ;
2021-06-19 16:03:14 +04:00
2021-06-05 04:25:46 +04:00
Range ::new ( range . anchor , pos )
} ) ;
doc . set_selection ( view . id , selection ) ;
}
2021-06-17 15:08:05 +04:00
fn extend_line_start ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-06-05 04:25:46 +04:00
let selection = doc . selection ( view . id ) . transform ( | range | {
let text = doc . text ( ) ;
let line = text . char_to_line ( range . head ) ;
// adjust to start of the line
let pos = text . line_to_char ( line ) ;
Range ::new ( range . anchor , pos )
} ) ;
doc . set_selection ( view . id , selection ) ;
}
2021-06-17 15:08:05 +04:00
fn select_all ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-02-09 10:39:26 +04:00
2021-03-16 13:27:57 +04:00
let end = doc . text ( ) . len_chars ( ) . saturating_sub ( 1 ) ;
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , Selection ::single ( 0 , end ) )
2021-02-09 10:39:26 +04:00
}
2021-06-17 15:08:05 +04:00
fn select_regex ( cx : & mut Context ) {
2021-06-15 07:26:05 +04:00
let prompt = ui ::regex_prompt ( cx , " select: " . to_string ( ) , move | view , doc , _ , regex | {
2021-02-18 13:34:22 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-04-14 10:21:49 +04:00
if let Some ( selection ) = selection ::select_on_matches ( text , doc . selection ( view . id ) , & regex )
2021-03-31 12:17:01 +04:00
{
2021-04-14 10:21:49 +04:00
doc . set_selection ( view . id , selection ) ;
2021-03-03 12:55:56 +04:00
}
2021-01-22 12:13:14 +04:00
} ) ;
2021-02-21 14:04:31 +04:00
cx . push_layer ( Box ::new ( prompt ) ) ;
2021-01-22 12:13:14 +04:00
}
2021-06-17 15:08:05 +04:00
fn split_selection ( cx : & mut Context ) {
2021-06-15 07:26:05 +04:00
let prompt = ui ::regex_prompt ( cx , " split: " . to_string ( ) , move | view , doc , _ , regex | {
2021-02-18 13:34:22 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-04-14 10:21:49 +04:00
let selection = selection ::split_on_matches ( text , doc . selection ( view . id ) , & regex ) ;
doc . set_selection ( view . id , selection ) ;
2021-01-22 12:13:14 +04:00
} ) ;
2020-12-14 09:12:54 +04:00
2021-02-21 14:04:31 +04:00
cx . push_layer ( Box ::new ( prompt ) ) ;
2020-12-14 09:12:54 +04:00
}
2021-06-17 15:08:05 +04:00
fn split_selection_on_newline ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-02-18 13:34:22 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2020-09-28 20:01:27 +04:00
// only compile the regex once
2020-10-01 07:23:07 +04:00
#[ allow(clippy::trivial_regex) ]
2021-06-21 22:22:07 +04: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 05:39:46 +04:00
let selection = selection ::split_on_matches ( text , doc . selection ( view . id ) , & REGEX ) ;
doc . set_selection ( view . id , selection ) ;
2020-09-28 20:01:27 +04:00
}
2021-02-12 13:10:05 +04:00
// search: searches for the first occurence in file, provides a prompt
// search_next: reuses the last search regex and searches for the next match. The next match becomes the main selection.
// -> we always search from after the cursor.head
// TODO: be able to use selection as search query (*/alt *)
// I'd probably collect all the matches right now and store the current index. The cache needs
// wiping if input happens.
2021-06-15 08:29:03 +04:00
fn search_impl ( doc : & mut Document , view : & mut View , contents : & str , regex : & Regex , extend : bool ) {
2021-02-12 13:10:05 +04:00
let text = doc . text ( ) ;
2021-04-14 10:21:49 +04:00
let selection = doc . selection ( view . id ) ;
2021-06-08 08:20:15 +04:00
let start = text . char_to_byte ( selection . cursor ( ) ) ;
2021-02-12 13:10:05 +04:00
2021-04-08 10:58:20 +04:00
// use find_at to find the next match after the cursor, loop around the end
2021-06-08 08:20:15 +04:00
// Careful, `Regex` uses `bytes` as offsets, not character indices!
2021-04-08 10:58:20 +04:00
let mat = regex
. find_at ( contents , start )
. or_else ( | | regex . find ( contents ) ) ;
2021-05-08 10:36:27 +04:00
// TODO: message on wraparound
2021-04-08 10:58:20 +04:00
if let Some ( mat ) = mat {
2021-02-12 13:10:05 +04:00
let start = text . byte_to_char ( mat . start ( ) ) ;
let end = text . byte_to_char ( mat . end ( ) ) ;
2021-04-09 19:21:13 +04:00
2021-05-16 13:58:43 +04:00
if end = = 0 {
// skip empty matches that don't make sense
return ;
}
2021-06-08 08:20:15 +04:00
let head = end - 1 ;
2021-04-14 10:21:49 +04:00
2021-04-09 19:21:13 +04:00
let selection = if extend {
2021-04-14 10:21:49 +04:00
selection . clone ( ) . push ( Range ::new ( start , head ) )
2021-04-09 19:21:13 +04:00
} else {
2021-04-14 10:21:49 +04:00
Selection ::single ( start , head )
2021-04-09 19:21:13 +04:00
} ;
2021-02-12 13:10:05 +04:00
// TODO: (first_match, regex) stuff in register?
2021-04-14 10:21:49 +04:00
doc . set_selection ( view . id , selection ) ;
2021-05-08 10:36:27 +04:00
align_view ( doc , view , Align ::Center ) ;
2021-02-12 13:10:05 +04:00
} ;
}
// TODO: use one function for search vs extend
2021-06-17 15:08:05 +04:00
fn search ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-02-12 13:10:05 +04: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-05-15 05:31:45 +04:00
let view_id = view . id ;
2021-06-15 07:26:05 +04:00
let prompt = ui ::regex_prompt (
cx ,
" search: " . to_string ( ) ,
move | view , doc , registers , regex | {
search_impl ( doc , view , & contents , & regex , false ) ;
// TODO: only store on enter (accept), not update
registers . write ( '\\' , vec! [ regex . as_str ( ) . to_string ( ) ] ) ;
} ,
) ;
2021-02-12 13:10:05 +04:00
2021-02-21 14:04:31 +04:00
cx . push_layer ( Box ::new ( prompt ) ) ;
2021-02-12 13:10:05 +04:00
}
2021-06-17 15:08:05 +04:00
fn search_next_impl ( cx : & mut Context , extend : bool ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
let registers = & mut cx . editor . registers ;
2021-06-15 07:26:05 +04:00
if let Some ( query ) = registers . read ( '\\' ) {
2021-02-12 13:10:05 +04:00
let query = query . first ( ) . unwrap ( ) ;
let contents = doc . text ( ) . slice ( .. ) . to_string ( ) ;
2021-05-07 10:03:47 +04:00
let regex = Regex ::new ( query ) . unwrap ( ) ;
2021-06-15 08:29:03 +04:00
search_impl ( doc , view , & contents , & regex , extend ) ;
2021-02-12 13:10:05 +04:00
}
}
2021-06-17 15:08:05 +04:00
fn search_next ( cx : & mut Context ) {
2021-06-15 08:29:03 +04:00
search_next_impl ( cx , false ) ;
2021-04-09 19:21:13 +04:00
}
2021-06-17 15:08:05 +04:00
fn extend_search_next ( cx : & mut Context ) {
2021-06-15 08:29:03 +04:00
search_next_impl ( cx , true ) ;
2021-04-09 19:21:13 +04:00
}
2021-06-17 15:08:05 +04:00
fn search_selection ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-02-22 10:14:02 +04:00
let contents = doc . text ( ) . slice ( .. ) ;
2021-04-01 05:39:46 +04:00
let query = doc . selection ( view . id ) . primary ( ) . fragment ( contents ) ;
2021-02-22 10:14:02 +04:00
let regex = regex ::escape ( & query ) ;
2021-06-15 07:26:05 +04:00
cx . editor . registers . write ( '\\' , vec! [ regex ] ) ;
2021-02-22 10:14:02 +04:00
search_next ( cx ) ;
}
2021-02-12 13:10:05 +04:00
// TODO: N -> search_prev
// need to loop around buffer also and show a message
// same for no matches
//
2021-06-17 15:08:05 +04:00
fn extend_line ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-01 09:41:02 +04:00
2021-04-01 05:39:46 +04:00
let pos = doc . selection ( view . id ) . primary ( ) ;
2021-03-01 09:41:02 +04:00
let text = doc . text ( ) ;
let line_start = text . char_to_line ( pos . anchor ) ;
let mut line = text . char_to_line ( pos . head ) ;
let line_end = text . line_to_char ( line + 1 ) . saturating_sub ( 1 ) ;
if line_start < = pos . anchor & & pos . head = = line_end & & line ! = text . len_lines ( ) {
line + = 1 ;
}
let start = text . line_to_char ( line_start ) ;
let end = text . line_to_char ( line + 1 ) . saturating_sub ( 1 ) ;
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , Selection ::single ( start , end ) ) ;
2021-03-01 09:41:02 +04:00
}
2020-10-07 13:31:04 +04:00
2020-12-21 08:42:47 +04:00
// heuristic: append changes to history after each command, unless we're in insert mode
2021-06-15 07:26:05 +04:00
fn delete_selection_impl ( reg : & mut Register , doc : & mut Document , view_id : ViewId ) {
2021-04-07 10:40:15 +04:00
// first yank the selection
let values : Vec < String > = doc
. selection ( view_id )
. fragments ( doc . text ( ) . slice ( .. ) )
. map ( Cow ::into_owned )
. collect ( ) ;
2021-06-15 07:26:05 +04:00
reg . write ( values ) ;
2021-04-07 10:40:15 +04:00
// then delete
2021-03-31 12:17:01 +04:00
let transaction =
Transaction ::change_by_selection ( doc . text ( ) , doc . selection ( view_id ) , | range | {
2021-06-20 03:24:36 +04:00
let alltext = doc . text ( ) ;
let line = alltext . char_to_line ( range . head ) ;
let max_to = doc . text ( ) . len_chars ( ) . saturating_sub (
get_line_ending ( & alltext . line ( line ) )
. map ( | le | le . len_chars ( ) )
. unwrap_or ( 0 ) ,
) ;
2021-06-03 18:10:10 +04:00
let to = std ::cmp ::min ( max_to , range . to ( ) + 1 ) ;
2021-06-03 17:44:16 +04:00
( range . from ( ) , to , None )
2021-03-31 12:17:01 +04:00
} ) ;
doc . apply ( & transaction , view_id ) ;
2020-12-21 08:42:47 +04:00
}
2021-06-17 15:08:05 +04:00
fn delete_selection ( cx : & mut Context ) {
2021-06-15 07:26:05 +04:00
let reg_name = cx . selected_register . name ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
let registers = & mut cx . editor . registers ;
2021-06-15 07:26:05 +04:00
let reg = registers . get_or_insert ( reg_name ) ;
2021-06-15 08:29:03 +04:00
delete_selection_impl ( reg , doc , view . id ) ;
2020-10-07 13:31:16 +04:00
2021-04-01 05:39:46 +04:00
doc . append_changes_to_history ( view . id ) ;
2021-06-04 05:30:18 +04:00
// exit select mode, if currently in select mode
exit_select_mode ( cx ) ;
2020-09-28 20:00:35 +04:00
}
2021-06-17 15:08:05 +04:00
fn change_selection ( cx : & mut Context ) {
2021-06-15 07:26:05 +04:00
let reg_name = cx . selected_register . name ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
let registers = & mut cx . editor . registers ;
2021-06-15 07:26:05 +04:00
let reg = registers . get_or_insert ( reg_name ) ;
2021-06-15 08:29:03 +04:00
delete_selection_impl ( reg , doc , view . id ) ;
2021-03-30 13:19:27 +04:00
enter_insert_mode ( doc ) ;
2020-09-28 20:00:35 +04:00
}
2021-06-17 15:08:05 +04:00
fn collapse_selection ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-02-21 14:04:31 +04:00
let selection = doc
2021-04-01 05:39:46 +04:00
. selection ( view . id )
2020-10-23 07:06:33 +04:00
. transform ( | range | Range ::new ( range . head , range . head ) ) ;
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2020-10-01 13:44:46 +04:00
}
2021-06-17 15:08:05 +04:00
fn flip_selections ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-02-21 14:04:31 +04:00
let selection = doc
2021-04-01 05:39:46 +04:00
. selection ( view . id )
2020-10-23 07:06:33 +04:00
. transform ( | range | Range ::new ( range . head , range . anchor ) ) ;
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2020-10-09 11:58:43 +04:00
}
2021-02-21 14:04:31 +04:00
fn enter_insert_mode ( doc : & mut Document ) {
doc . mode = Mode ::Insert ;
2020-10-06 09:44:18 +04:00
}
2020-12-21 08:42:47 +04:00
2020-09-07 12:08:28 +04:00
// inserts at the start of each selection
2021-06-17 15:08:05 +04:00
fn insert_mode ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-23 12:47:40 +04:00
enter_insert_mode ( doc ) ;
2020-09-07 12:08:28 +04:00
2021-02-21 14:04:31 +04:00
let selection = doc
2021-04-01 05:39:46 +04:00
. selection ( view . id )
2020-10-23 07:06:33 +04:00
. transform ( | range | Range ::new ( range . to ( ) , range . from ( ) ) ) ;
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2020-09-07 12:08:28 +04:00
}
// inserts at the end of each selection
2021-06-17 15:08:05 +04:00
fn append_mode ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-23 12:47:40 +04:00
enter_insert_mode ( doc ) ;
2021-01-21 12:00:08 +04:00
doc . restore_cursor = true ;
2020-09-07 12:08:28 +04:00
2021-02-18 13:34:22 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-04-01 05:39:46 +04:00
let selection = doc . selection ( view . id ) . transform ( | range | {
2020-09-13 06:32:37 +04:00
Range ::new (
range . from ( ) ,
2021-02-25 11:52:32 +04:00
graphemes ::next_grapheme_boundary ( text , range . to ( ) ) , // to() + next char
2020-09-13 06:32:37 +04:00
)
2020-10-23 07:06:33 +04:00
} ) ;
2021-05-15 05:46:59 +04:00
let end = text . len_chars ( ) ;
if selection . iter ( ) . any ( | range | range . head = = end ) {
let transaction = Transaction ::change (
doc . text ( ) ,
2021-06-21 20:52:21 +04:00
std ::array ::IntoIter ::new ( [ ( end , end , Some ( doc . line_ending . as_str ( ) . into ( ) ) ) ] ) ,
2021-05-15 05:46:59 +04:00
) ;
doc . apply ( & transaction , view . id ) ;
}
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2020-09-05 17:01:05 +04:00
}
2021-05-07 12:08:07 +04:00
mod cmd {
use super ::* ;
use std ::collections ::HashMap ;
use helix_view ::editor ::Action ;
2021-05-07 12:19:45 +04:00
use ui ::completers ::{ self , Completer } ;
2021-05-07 12:08:07 +04:00
#[ derive(Clone) ]
2021-06-17 15:08:05 +04:00
pub struct TypableCommand {
2021-05-07 12:08:07 +04:00
pub name : & 'static str ,
pub alias : Option < & 'static str > ,
pub doc : & 'static str ,
// params, flags, helper, completer
pub fun : fn ( & mut Editor , & [ & str ] , PromptEvent ) ,
2021-05-07 12:19:45 +04:00
pub completer : Option < Completer > ,
2021-05-07 12:08:07 +04:00
}
fn quit ( editor : & mut Editor , args : & [ & str ] , event : PromptEvent ) {
// last view and we have unsaved changes
2021-06-15 08:29:03 +04:00
if editor . tree . views ( ) . count ( ) = = 1 & & buffers_remaining_impl ( editor ) {
2021-06-13 13:29:22 +04:00
return ;
2021-05-07 12:08:07 +04:00
}
2021-06-18 02:09:10 +04:00
editor . close ( view! ( editor ) . id , /* close_buffer */ false ) ;
2021-05-07 12:08:07 +04:00
}
fn force_quit ( editor : & mut Editor , args : & [ & str ] , event : PromptEvent ) {
2021-06-18 02:09:10 +04:00
editor . close ( view! ( editor ) . id , /* close_buffer */ false ) ;
2021-05-07 12:08:07 +04:00
}
fn open ( editor : & mut Editor , args : & [ & str ] , event : PromptEvent ) {
2021-06-01 06:59:59 +04:00
match args . get ( 0 ) {
Some ( path ) = > {
// TODO: handle error
editor . open ( path . into ( ) , Action ::Replace ) ;
}
None = > {
editor . set_error ( " wrong argument count " . to_string ( ) ) ;
}
} ;
2021-05-07 12:08:07 +04:00
}
2021-06-15 08:29:03 +04:00
fn write_impl < P : AsRef < Path > > (
2021-06-13 09:31:25 +04:00
view : & View ,
doc : & mut Document ,
path : Option < P > ,
) -> Result < ( ) , anyhow ::Error > {
use anyhow ::anyhow ;
if let Some ( path ) = path {
if let Err ( err ) = doc . set_path ( path . as_ref ( ) ) {
return Err ( anyhow! ( " invalid filepath: {} " , err ) ) ;
2021-06-01 09:47:21 +04:00
} ;
}
2021-05-07 12:08:07 +04:00
if doc . path ( ) . is_none ( ) {
2021-06-13 13:54:00 +04:00
return Err ( anyhow! ( " cannot write a buffer without a filename " ) ) ;
2021-05-07 12:08:07 +04:00
}
2021-06-12 05:20:37 +04:00
let autofmt = doc
. language_config ( )
. map ( | config | config . auto_format )
. unwrap_or_default ( ) ;
if autofmt {
doc . format ( view . id ) ; // TODO: merge into save
}
2021-06-14 20:01:58 +04:00
helix_lsp ::block_on ( doc . save ( ) ) ;
2021-06-13 09:31:25 +04:00
Ok ( ( ) )
}
fn write ( editor : & mut Editor , args : & [ & str ] , event : PromptEvent ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( editor ) ;
2021-06-15 08:29:03 +04:00
if let Err ( e ) = write_impl ( view , doc , args . first ( ) ) {
2021-06-13 09:31:25 +04:00
editor . set_error ( e . to_string ( ) ) ;
} ;
2021-05-07 12:08:07 +04:00
}
2021-05-12 12:24:55 +04:00
2021-05-07 12:08:07 +04:00
fn new_file ( editor : & mut Editor , args : & [ & str ] , event : PromptEvent ) {
editor . new_file ( Action ::Replace ) ;
}
2021-05-12 12:24:55 +04:00
fn format ( editor : & mut Editor , args : & [ & str ] , event : PromptEvent ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( editor ) ;
2021-05-12 12:24:55 +04:00
2021-05-29 19:00:15 +04:00
doc . format ( view . id )
2021-05-12 12:24:55 +04:00
}
2021-06-14 05:09:22 +04:00
fn set_indent_style ( editor : & mut Editor , args : & [ & str ] , event : PromptEvent ) {
use IndentStyle ::* ;
2021-06-15 06:43:24 +04:00
// If no argument, report current indent style.
if args . is_empty ( ) {
2021-06-18 02:09:10 +04:00
let style = current! ( editor ) . 1. indent_style ;
2021-06-15 06:43:24 +04:00
editor . set_status ( match style {
Tabs = > " tabs " . into ( ) ,
Spaces ( 1 ) = > " 1 space " . into ( ) ,
Spaces ( n ) if ( 2 ..= 8 ) . contains ( & n ) = > format! ( " {} spaces " , n ) ,
_ = > " error " . into ( ) , // Shouldn't happen.
} ) ;
return ;
}
// Attempt to parse argument as an indent style.
2021-06-14 05:09:22 +04:00
let style = match args . get ( 0 ) {
Some ( arg ) if " tabs " . starts_with ( & arg . to_lowercase ( ) ) = > Some ( Tabs ) ,
2021-06-15 00:22:25 +04:00
Some ( & " 0 " ) = > Some ( Tabs ) ,
Some ( arg ) = > arg
. parse ::< u8 > ( )
. ok ( )
. filter ( | n | ( 1 ..= 8 ) . contains ( n ) )
. map ( Spaces ) ,
2021-06-14 05:09:22 +04:00
_ = > None ,
} ;
if let Some ( s ) = style {
2021-06-18 02:09:10 +04:00
let doc = doc_mut! ( editor ) ;
2021-06-14 05:09:22 +04:00
doc . indent_style = s ;
2021-06-15 06:43:24 +04:00
} else {
// Invalid argument.
editor . set_error ( format! ( " invalid indent style ' {} ' " , args [ 0 ] , ) ) ;
2021-06-14 05:09:22 +04:00
}
}
2021-06-21 23:36:01 +04:00
/// Sets or reports the current document's line ending setting.
fn set_line_ending ( editor : & mut Editor , args : & [ & str ] , event : PromptEvent ) {
use LineEnding ::* ;
// If no argument, report current line ending setting.
if args . is_empty ( ) {
let line_ending = current! ( editor ) . 1. line_ending ;
editor . set_status ( match line_ending {
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 ( ) ,
} ) ;
return ;
}
// Attempt to parse argument as a line ending.
let line_ending = match args . get ( 0 ) {
// We check for CR first because it shares a common prefix with CRLF.
Some ( arg ) if " cr " . starts_with ( & arg . to_lowercase ( ) ) = > Some ( CR ) ,
Some ( arg ) if " crlf " . starts_with ( & arg . to_lowercase ( ) ) = > Some ( Crlf ) ,
Some ( arg ) if " lf " . starts_with ( & arg . to_lowercase ( ) ) = > Some ( LF ) ,
Some ( arg ) if " ff " . starts_with ( & arg . to_lowercase ( ) ) = > Some ( FF ) ,
Some ( arg ) if " nel " . starts_with ( & arg . to_lowercase ( ) ) = > Some ( Nel ) ,
_ = > None ,
} ;
if let Some ( le ) = line_ending {
doc_mut! ( editor ) . line_ending = le ;
} else {
// Invalid argument.
editor . set_error ( format! ( " invalid line ending ' {} ' " , args [ 0 ] , ) ) ;
}
}
2021-06-11 17:06:13 +04:00
fn earlier ( editor : & mut Editor , args : & [ & str ] , event : PromptEvent ) {
let uk = match args . join ( " " ) . parse ::< helix_core ::history ::UndoKind > ( ) {
Ok ( uk ) = > uk ,
Err ( msg ) = > {
editor . set_error ( msg ) ;
return ;
}
} ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( editor ) ;
2021-06-11 17:06:13 +04:00
doc . earlier ( view . id , uk )
}
fn later ( editor : & mut Editor , args : & [ & str ] , event : PromptEvent ) {
let uk = match args . join ( " " ) . parse ::< helix_core ::history ::UndoKind > ( ) {
Ok ( uk ) = > uk ,
Err ( msg ) = > {
editor . set_error ( msg ) ;
return ;
}
} ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( editor ) ;
2021-06-11 17:06:13 +04:00
doc . later ( view . id , uk )
}
2021-06-13 09:31:25 +04:00
fn write_quit ( editor : & mut Editor , args : & [ & str ] , event : PromptEvent ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( editor ) ;
2021-06-15 08:29:03 +04:00
if let Err ( e ) = write_impl ( view , doc , args . first ( ) ) {
2021-06-13 13:54:00 +04:00
editor . set_error ( e . to_string ( ) ) ;
return ;
} ;
quit ( editor , & [ ] , event )
2021-06-13 09:31:25 +04:00
}
fn force_write_quit ( editor : & mut Editor , args : & [ & str ] , event : PromptEvent ) {
2021-06-13 13:00:13 +04:00
write ( editor , args , event ) ;
2021-06-13 09:31:25 +04:00
force_quit ( editor , & [ ] , event ) ;
}
2021-06-13 13:14:53 +04:00
/// Returns `true` if there are modified buffers remaining and sets editor error,
/// otherwise returns `false`
2021-06-15 08:29:03 +04:00
fn buffers_remaining_impl ( editor : & mut Editor ) -> bool {
2021-06-13 13:14:53 +04:00
let modified : Vec < _ > = editor
2021-06-13 13:00:55 +04:00
. documents ( )
. filter ( | doc | doc . is_modified ( ) )
. map ( | doc | {
doc . relative_path ( )
2021-06-18 10:19:34 +04:00
. map ( | path | path . to_string_lossy ( ) . to_string ( ) )
. unwrap_or_else ( | | " [scratch] " . into ( ) )
2021-06-13 13:00:55 +04:00
} )
2021-06-13 13:14:53 +04:00
. collect ( ) ;
if ! modified . is_empty ( ) {
let err = format! (
" {} unsaved buffer(s) remaining: {:?} " ,
modified . len ( ) ,
modified
) ;
editor . set_error ( err ) ;
true
} else {
false
}
2021-06-13 13:00:55 +04:00
}
2021-06-15 08:29:03 +04:00
fn write_all_impl (
editor : & mut Editor ,
args : & [ & str ] ,
event : PromptEvent ,
quit : bool ,
force : bool ,
) {
2021-06-13 13:00:55 +04:00
let mut errors = String ::new ( ) ;
2021-06-13 09:31:25 +04:00
2021-06-13 13:00:55 +04:00
// save all documents
for ( id , mut doc ) in & mut editor . documents {
if doc . path ( ) . is_none ( ) {
errors . push_str ( " cannot write a buffer without a filename \n " ) ;
continue ;
}
2021-06-14 20:01:58 +04:00
helix_lsp ::block_on ( doc . save ( ) ) ;
2021-06-13 13:00:55 +04:00
}
editor . set_error ( errors ) ;
if quit {
2021-06-15 08:29:03 +04:00
if ! force & & buffers_remaining_impl ( editor ) {
2021-06-13 13:29:22 +04:00
return ;
2021-06-13 09:31:25 +04:00
}
2021-06-13 13:00:55 +04:00
// close all views
let views : Vec < _ > = editor . tree . views ( ) . map ( | ( view , _ ) | view . id ) . collect ( ) ;
for view_id in views {
editor . close ( view_id , false ) ;
}
2021-06-13 09:31:25 +04:00
}
}
fn write_all ( editor : & mut Editor , args : & [ & str ] , event : PromptEvent ) {
2021-06-15 08:29:03 +04:00
write_all_impl ( editor , args , event , false , false )
2021-06-13 09:31:25 +04:00
}
fn write_all_quit ( editor : & mut Editor , args : & [ & str ] , event : PromptEvent ) {
2021-06-15 08:29:03 +04:00
write_all_impl ( editor , args , event , true , false )
2021-06-13 09:31:25 +04:00
}
fn force_write_all_quit ( editor : & mut Editor , args : & [ & str ] , event : PromptEvent ) {
2021-06-15 08:29:03 +04:00
write_all_impl ( editor , args , event , true , true )
2021-06-13 09:31:25 +04:00
}
2021-06-15 08:29:03 +04:00
fn quit_all_impl ( editor : & mut Editor , args : & [ & str ] , event : PromptEvent , force : bool ) {
if ! force & & buffers_remaining_impl ( editor ) {
2021-06-13 13:29:22 +04:00
return ;
2021-06-13 13:06:06 +04:00
}
// close all views
let views : Vec < _ > = editor . tree . views ( ) . map ( | ( view , _ ) | view . id ) . collect ( ) ;
for view_id in views {
editor . close ( view_id , false ) ;
}
}
fn quit_all ( editor : & mut Editor , args : & [ & str ] , event : PromptEvent ) {
2021-06-15 08:29:03 +04:00
quit_all_impl ( editor , args , event , false )
2021-06-13 13:06:06 +04:00
}
fn force_quit_all ( editor : & mut Editor , args : & [ & str ] , event : PromptEvent ) {
2021-06-15 08:29:03 +04:00
quit_all_impl ( editor , args , event , true )
2021-06-13 13:06:06 +04:00
}
2021-06-19 15:27:32 +04:00
fn theme ( editor : & mut Editor , args : & [ & str ] , event : PromptEvent ) {
let theme = if let Some ( theme ) = args . first ( ) {
theme
} else {
editor . set_error ( " theme name not provided " . into ( ) ) ;
return ;
} ;
editor . set_theme_from_name ( theme ) ;
}
2021-06-19 10:27:43 +04:00
fn yank_main_selection_to_clipboard ( editor : & mut Editor , _ : & [ & str ] , _ : PromptEvent ) {
yank_main_selection_to_clipboard_impl ( editor ) ;
2021-06-15 01:37:17 +04:00
}
2021-06-19 10:27:43 +04:00
fn yank_joined_to_clipboard ( editor : & mut Editor , args : & [ & str ] , _ : PromptEvent ) {
2021-06-21 22:59:03 +04:00
let ( _ , doc ) = current! ( editor ) ;
2021-06-21 23:02:44 +04:00
let separator = args
. first ( )
. copied ( )
. unwrap_or_else ( | | doc . line_ending . as_str ( ) ) ;
2021-06-19 10:27:43 +04:00
yank_joined_to_clipboard_impl ( editor , separator ) ;
2021-06-15 01:37:17 +04:00
}
fn paste_clipboard_after ( editor : & mut Editor , _ : & [ & str ] , _ : PromptEvent ) {
paste_clipboard_impl ( editor , Paste ::After ) ;
}
2021-06-19 10:27:43 +04:00
fn paste_clipboard_before ( editor : & mut Editor , _ : & [ & str ] , _ : PromptEvent ) {
2021-06-15 01:37:17 +04:00
paste_clipboard_impl ( editor , Paste ::After ) ;
}
2021-06-19 10:27:43 +04:00
fn replace_selections_with_clipboard ( editor : & mut Editor , _ : & [ & str ] , _ : PromptEvent ) {
2021-06-15 01:37:17 +04:00
let ( view , doc ) = current! ( editor ) ;
match editor . clipboard_provider . get_contents ( ) {
Ok ( contents ) = > {
let transaction =
Transaction ::change_by_selection ( doc . text ( ) , doc . selection ( view . id ) , | range | {
let max_to = doc . text ( ) . len_chars ( ) . saturating_sub ( 1 ) ;
let to = std ::cmp ::min ( max_to , range . to ( ) + 1 ) ;
( range . from ( ) , to , Some ( contents . as_str ( ) . into ( ) ) )
} ) ;
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
}
Err ( e ) = > log ::error! ( " Couldn't get system clipboard contents: {:?} " , e ) ,
}
}
fn show_clipboard_provider ( editor : & mut Editor , _ : & [ & str ] , _ : PromptEvent ) {
editor . set_status ( editor . clipboard_provider . name ( ) . into ( ) ) ;
}
2021-06-17 15:08:05 +04:00
pub const TYPABLE_COMMAND_LIST : & [ TypableCommand ] = & [
TypableCommand {
2021-05-07 12:08:07 +04:00
name : " quit " ,
alias : Some ( " q " ) ,
doc : " Close the current view. " ,
fun : quit ,
2021-05-07 12:19:45 +04:00
completer : None ,
2021-05-07 12:08:07 +04:00
} ,
2021-06-17 15:08:05 +04:00
TypableCommand {
2021-05-07 12:08:07 +04:00
name : " quit! " ,
alias : Some ( " q! " ) ,
doc : " Close the current view. " ,
fun : force_quit ,
2021-05-07 12:19:45 +04:00
completer : None ,
2021-05-07 12:08:07 +04:00
} ,
2021-06-17 15:08:05 +04:00
TypableCommand {
2021-05-07 12:08:07 +04:00
name : " open " ,
alias : Some ( " o " ) ,
doc : " Open a file from disk into the current view. " ,
fun : open ,
2021-05-07 12:19:45 +04:00
completer : Some ( completers ::filename ) ,
2021-05-07 12:08:07 +04:00
} ,
2021-06-17 15:08:05 +04:00
TypableCommand {
2021-05-07 12:08:07 +04:00
name : " write " ,
alias : Some ( " w " ) ,
2021-06-01 09:47:21 +04:00
doc : " Write changes to disk. Accepts an optional path (:write some/path.txt) " ,
2021-05-07 12:08:07 +04:00
fun : write ,
2021-05-07 12:19:45 +04:00
completer : Some ( completers ::filename ) ,
2021-05-07 12:08:07 +04:00
} ,
2021-06-17 15:08:05 +04:00
TypableCommand {
2021-05-07 12:08:07 +04:00
name : " new " ,
alias : Some ( " n " ) ,
doc : " Create a new scratch buffer. " ,
fun : new_file ,
2021-05-07 12:19:45 +04:00
completer : Some ( completers ::filename ) ,
2021-05-07 12:08:07 +04:00
} ,
2021-06-17 15:08:05 +04:00
TypableCommand {
2021-05-12 12:24:55 +04:00
name : " format " ,
alias : Some ( " fmt " ) ,
doc : " Format the file using a formatter. " ,
fun : format ,
completer : None ,
} ,
2021-06-17 15:08:05 +04:00
TypableCommand {
2021-06-15 00:22:25 +04:00
name : " indent-style " ,
2021-06-14 05:09:22 +04:00
alias : None ,
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 23:36:01 +04:00
TypableCommand {
name : " line-ending " ,
alias : None ,
doc : " Set the document's default line ending. Options: crlf, lf, cr, ff, nel. " ,
fun : set_line_ending ,
completer : None ,
} ,
2021-06-17 15:08:05 +04:00
TypableCommand {
2021-06-11 17:06:13 +04:00
name : " earlier " ,
alias : Some ( " ear " ) ,
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 15:08:05 +04:00
TypableCommand {
2021-06-11 17:06:13 +04:00
name : " later " ,
alias : Some ( " lat " ) ,
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 15:08:05 +04:00
TypableCommand {
2021-06-13 09:31:25 +04:00
name : " write-quit " ,
alias : Some ( " wq " ) ,
doc : " Writes changes to disk and closes the current view. Accepts an optional path (:wq some/path.txt) " ,
fun : write_quit ,
completer : Some ( completers ::filename ) ,
} ,
2021-06-17 15:08:05 +04:00
TypableCommand {
2021-06-13 09:31:25 +04:00
name : " write-quit! " ,
alias : Some ( " wq! " ) ,
doc : " Writes changes to disk and closes the current view forcefully. Accepts an optional path (:wq! some/path.txt) " ,
fun : force_write_quit ,
completer : Some ( completers ::filename ) ,
} ,
2021-06-17 15:08:05 +04:00
TypableCommand {
2021-06-13 09:31:25 +04:00
name : " write-all " ,
alias : Some ( " wa " ) ,
doc : " Writes changes from all views to disk. " ,
fun : write_all ,
completer : None ,
} ,
2021-06-17 15:08:05 +04:00
TypableCommand {
2021-06-13 13:06:06 +04:00
name : " write-quit-all " ,
alias : Some ( " wqa " ) ,
2021-06-13 09:31:25 +04:00
doc : " Writes changes from all views to disk and close all views. " ,
fun : write_all_quit ,
completer : None ,
} ,
2021-06-17 15:08:05 +04:00
TypableCommand {
2021-06-13 13:06:06 +04:00
name : " write-quit-all! " ,
alias : Some ( " wqa! " ) ,
doc : " Writes changes from all views to disk and close all views forcefully (ignoring unsaved changes). " ,
2021-06-13 09:31:25 +04:00
fun : force_write_all_quit ,
completer : None ,
} ,
2021-06-17 15:08:05 +04:00
TypableCommand {
2021-06-13 13:06:06 +04:00
name : " quit-all " ,
alias : Some ( " qa " ) ,
doc : " Close all views. " ,
fun : quit_all ,
completer : None ,
} ,
2021-06-17 15:08:05 +04:00
TypableCommand {
2021-06-13 13:06:06 +04:00
name : " quit-all! " ,
alias : Some ( " qa! " ) ,
doc : " Close all views forcefully (ignoring unsaved changes). " ,
fun : force_quit_all ,
completer : None ,
} ,
2021-06-19 15:27:32 +04:00
TypableCommand {
name : " theme " ,
alias : None ,
doc : " Change the theme of current view. Requires theme name as argument (:theme <name>) " ,
fun : theme ,
completer : Some ( completers ::theme ) ,
} ,
2021-06-15 01:37:17 +04:00
TypableCommand {
name : " clipboard-yank " ,
alias : None ,
doc : " Yank main selection into system clipboard. " ,
fun : yank_main_selection_to_clipboard ,
completer : None ,
} ,
TypableCommand {
name : " clipboard-yank-join " ,
alias : None ,
2021-06-19 10:27:43 +04: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-15 01:37:17 +04:00
fun : yank_joined_to_clipboard ,
completer : None ,
} ,
TypableCommand {
name : " clipboard-paste-after " ,
alias : None ,
doc : " Paste system clipboard after selections. " ,
fun : paste_clipboard_after ,
completer : None ,
} ,
TypableCommand {
name : " clipboard-paste-before " ,
alias : None ,
doc : " Paste system clipboard before selections. " ,
fun : paste_clipboard_before ,
completer : None ,
} ,
TypableCommand {
name : " clipboard-paste-replace " ,
alias : None ,
doc : " Replace selections with content of system clipboard. " ,
fun : replace_selections_with_clipboard ,
completer : None ,
} ,
TypableCommand {
name : " show-clipboard-provider " ,
alias : None ,
doc : " Show clipboard provider name in status bar. " ,
fun : show_clipboard_provider ,
completer : None ,
} ,
2021-05-07 12:08:07 +04:00
] ;
2021-06-17 15:08:05 +04:00
pub static COMMANDS : Lazy < HashMap < & 'static str , & 'static TypableCommand > > = Lazy ::new ( | | {
2021-05-07 12:08:07 +04:00
let mut map = HashMap ::new ( ) ;
2021-06-17 15:08:05 +04:00
for cmd in TYPABLE_COMMAND_LIST {
2021-05-07 12:13:26 +04:00
map . insert ( cmd . name , cmd ) ;
2021-05-07 12:08:07 +04:00
if let Some ( alias ) = cmd . alias {
2021-05-07 12:13:26 +04:00
map . insert ( alias , cmd ) ;
2021-05-07 12:08:07 +04:00
}
}
map
} ) ;
}
2021-03-01 13:02:31 +04:00
2021-06-17 15:08:05 +04:00
fn command_mode ( cx : & mut Context ) {
2021-05-07 12:19:45 +04:00
// TODO: completion items should have a info section that would get displayed in
// a popup above the prompt when items are tabbed over
2021-05-08 12:33:06 +04:00
let mut prompt = Prompt ::new (
2021-02-21 14:04:31 +04:00
" : " . to_owned ( ) ,
2021-03-01 13:02:31 +04:00
| input : & str | {
2021-06-08 08:20:15 +04:00
// we use .this over split_whitespace() because we care about empty segments
2021-03-01 13:02:31 +04:00
let parts = input . split ( ' ' ) . collect ::< Vec < & str > > ( ) ;
2021-05-07 12:19:45 +04: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 13:02:31 +04:00
if parts . len ( ) < = 1 {
2021-03-21 09:13:49 +04:00
use std ::{ borrow ::Cow , ops ::Range } ;
let end = 0 .. ;
2021-06-17 15:08:05 +04:00
cmd ::TYPABLE_COMMAND_LIST
2021-03-01 13:02:31 +04:00
. iter ( )
2021-05-07 12:08:07 +04:00
. filter ( | command | command . name . contains ( input ) )
. map ( | command | ( end . clone ( ) , Cow ::Borrowed ( command . name ) ) )
2021-03-01 13:02:31 +04:00
. collect ( )
} else {
let part = parts . last ( ) . unwrap ( ) ;
2021-06-17 15:08:05 +04:00
if let Some ( cmd ::TypableCommand {
2021-05-07 12:19:45 +04:00
completer : Some ( completer ) ,
..
} ) = cmd ::COMMANDS . get ( parts [ 0 ] )
{
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 13:02:31 +04:00
}
2021-02-21 14:04:31 +04:00
} , // completion
move | editor : & mut Editor , input : & str , event : PromptEvent | {
2021-05-07 09:19:58 +04:00
use helix_view ::editor ::Action ;
2021-02-21 14:04:31 +04:00
if event ! = PromptEvent ::Validate {
return ;
}
2021-02-16 13:23:44 +04:00
2021-06-08 08:20:15 +04:00
let parts = input . split_whitespace ( ) . collect ::< Vec < & str > > ( ) ;
2021-06-02 05:48:21 +04:00
if parts . is_empty ( ) {
return ;
}
2021-02-21 14:04:31 +04:00
2021-05-07 12:08:07 +04:00
if let Some ( cmd ) = cmd ::COMMANDS . get ( parts [ 0 ] ) {
( cmd . fun ) ( editor , & parts [ 1 .. ] , event ) ;
} else {
editor . set_error ( format! ( " no such command: ' {} ' " , parts [ 0 ] ) ) ;
} ;
2020-12-21 11:23:05 +04:00
} ,
2021-02-21 14:04:31 +04:00
) ;
2021-05-08 12:33:06 +04:00
prompt . doc_fn = Box ::new ( | input : & str | {
let part = input . split ( ' ' ) . next ( ) . unwrap_or_default ( ) ;
2021-06-17 15:08:05 +04:00
if let Some ( cmd ::TypableCommand { doc , .. } ) = cmd ::COMMANDS . get ( part ) {
2021-05-08 12:33:06 +04:00
return Some ( doc ) ;
}
None
} ) ;
2021-02-21 14:04:31 +04:00
cx . push_layer ( Box ::new ( prompt ) ) ;
2020-11-03 13:57:12 +04:00
}
2021-03-29 12:04:12 +04:00
2021-06-17 15:08:05 +04:00
fn file_picker ( cx : & mut Context ) {
2021-03-29 12:04:12 +04:00
let root = find_root ( None ) . unwrap_or_else ( | | PathBuf ::from ( " ./ " ) ) ;
let picker = ui ::file_picker ( root ) ;
2021-02-21 14:04:31 +04:00
cx . push_layer ( Box ::new ( picker ) ) ;
2020-12-21 11:23:05 +04:00
}
2021-06-17 15:08:05 +04:00
fn buffer_picker ( cx : & mut Context ) {
2021-03-24 11:26:53 +04:00
use std ::path ::{ Path , PathBuf } ;
2021-06-18 02:09:10 +04:00
let current = view! ( cx . editor ) . doc ;
2021-03-24 11:26:53 +04:00
let picker = Picker ::new (
cx . editor
. documents
. iter ( )
2021-06-18 10:19:34 +04:00
. map ( | ( id , doc ) | ( id , doc . relative_path ( ) ) )
2021-03-24 11:26:53 +04:00
. collect ( ) ,
move | ( id , path ) : & ( DocumentId , Option < PathBuf > ) | {
// format_fn
2021-03-26 06:02:32 +04:00
match path . as_ref ( ) . and_then ( | path | path . to_str ( ) ) {
2021-03-24 11:26:53 +04:00
Some ( path ) = > {
if * id = = current {
2021-03-26 06:02:32 +04:00
format! ( " {} (*) " , path ) . into ( )
2021-03-24 11:26:53 +04:00
} else {
2021-03-26 06:02:32 +04:00
path . into ( )
2021-03-24 11:26:53 +04:00
}
}
2021-05-07 09:36:06 +04:00
None = > " [scratch buffer] " . into ( ) ,
2021-03-24 11:26:53 +04:00
}
} ,
2021-05-07 09:36:06 +04:00
| editor : & mut Editor , ( id , _path ) : & ( DocumentId , Option < PathBuf > ) , _action | {
use helix_view ::editor ::Action ;
2021-05-07 09:40:11 +04:00
editor . switch ( * id , Action ::Replace ) ;
2021-03-24 11:26:53 +04:00
} ,
) ;
cx . push_layer ( Box ::new ( picker ) ) ;
2020-12-17 13:08:16 +04:00
}
2020-11-03 13:57:12 +04:00
2021-06-17 15:08:05 +04:00
fn symbol_picker ( cx : & mut Context ) {
2021-06-12 16:45:21 +04: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-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-06-12 16:45:21 +04: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 02:09:10 +04:00
let ( _view , doc ) = current! ( editor ) ;
2021-06-12 16:45:21 +04:00
let mut flat_symbols = Vec ::new ( ) ;
for symbol in symbols {
nested_to_flat ( & mut flat_symbols , & doc . identifier ( ) , symbol )
}
flat_symbols
}
} ;
let picker = Picker ::new (
symbols ,
| symbol | ( & symbol . name ) . into ( ) ,
move | editor : & mut Editor , symbol , _action | {
push_jump ( editor ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( editor ) ;
2021-06-12 16:45:21 +04:00
if let Some ( range ) =
lsp_range_to_range ( doc . text ( ) , symbol . location . range , offset_encoding )
{
doc . set_selection ( view . id , Selection ::single ( range . to ( ) , range . from ( ) ) ) ;
align_view ( doc , view , Align ::Center ) ;
}
} ,
) ;
compositor . push ( Box ::new ( picker ) )
}
} ,
)
}
2021-06-10 15:10:05 +04:00
// I inserts at the first nonwhitespace character of each line with a selection
2021-06-17 15:08:05 +04:00
fn prepend_to_line ( cx : & mut Context ) {
2021-06-10 15:10:05 +04:00
move_first_nonwhitespace ( cx ) ;
2021-06-18 02:09:10 +04:00
let doc = doc_mut! ( cx . editor ) ;
2021-03-23 12:47:40 +04:00
enter_insert_mode ( doc ) ;
2020-09-13 15:04:16 +04:00
}
2020-09-07 12:08:28 +04:00
// A inserts at the end of each line with a selection
2021-06-17 15:08:05 +04:00
fn append_to_line ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-23 12:47:40 +04:00
enter_insert_mode ( doc ) ;
2020-09-13 15:04:16 +04:00
2021-04-01 05:39:46 +04:00
let selection = doc . selection ( view . id ) . transform ( | range | {
2021-05-18 13:17:14 +04:00
let text = doc . text ( ) ;
let line = text . char_to_line ( range . head ) ;
2021-06-21 02:09:10 +04:00
let pos = line_end_char_index ( & text . slice ( .. ) , line ) ;
2021-03-30 13:27:45 +04:00
Range ::new ( pos , pos )
} ) ;
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2020-09-13 15:04:16 +04:00
}
2020-09-13 14:51:42 +04:00
2021-04-01 06:36:59 +04:00
enum Open {
Below ,
Above ,
}
fn open ( cx : & mut Context , open : Open ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-23 12:47:40 +04:00
enter_insert_mode ( doc ) ;
2021-01-21 11:55:46 +04:00
2021-04-01 06:01:11 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-06-16 20:11:54 +04:00
let contents = doc . text ( ) ;
2021-05-18 13:27:52 +04:00
let selection = doc . selection ( view . id ) ;
2020-09-13 14:51:42 +04:00
2021-05-18 13:27:52 +04:00
let mut ranges = SmallVec ::with_capacity ( selection . len ( ) ) ;
2021-06-16 20:11:54 +04:00
let mut offs = 0 ;
2021-05-18 13:27:52 +04:00
2021-06-16 20:11:54 +04:00
let mut transaction = Transaction ::change_by_selection ( contents , selection , | range | {
let line = text . char_to_line ( range . head ) ;
2020-09-13 14:51:42 +04:00
2021-06-16 20:11:54 +04: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 06:36:59 +04:00
2021-06-17 10:19:02 +04:00
// insert newlines after this index for both Above and Below variants
let linend_index = doc . text ( ) . line_to_char ( line ) . saturating_sub ( 1 ) ;
2021-03-18 08:45:57 +04:00
2021-06-16 20:11:54 +04: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-17 10:19:02 +04:00
linend_index ,
2021-06-16 20:11:54 +04:00
true ,
) ;
let indent = doc . indent_unit ( ) . repeat ( indent_level ) ;
2021-06-18 12:50:25 +04:00
let indent_len = indent . len ( ) ;
let mut text = String ::with_capacity ( 1 + indent_len ) ;
2021-06-21 22:59:03 +04:00
text . push_str ( doc . line_ending . as_str ( ) ) ;
2021-06-16 20:11:54 +04:00
text . push_str ( & indent ) ;
let text = text . repeat ( count ) ;
2021-06-17 10:19:02 +04:00
// calculate new selection ranges
2021-06-16 20:11:54 +04:00
let pos = if line = = 0 {
0 // Required since text will have a min len of 1 (\n)
} else {
2021-06-17 10:19:02 +04:00
offs + linend_index + 1
2021-06-16 20:11:54 +04:00
} ;
2021-06-17 10:19:02 +04:00
for i in 0 .. count {
2021-06-18 12:50:25 +04: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 10:19:02 +04:00
}
2021-06-16 20:11:54 +04:00
offs + = text . chars ( ) . count ( ) ;
2021-06-17 10:19:02 +04:00
( linend_index , linend_index , Some ( text . into ( ) ) )
2021-06-16 20:11:54 +04:00
} ) ;
2020-09-13 14:51:42 +04:00
2021-06-16 20:11:54 +04:00
transaction = transaction . with_selection ( Selection ::new ( ranges , selection . primary_index ( ) ) ) ;
2020-09-13 14:51:42 +04:00
2021-04-01 05:39:46 +04:00
doc . apply ( & transaction , view . id ) ;
2020-09-13 14:51:42 +04:00
}
2021-04-01 06:36:59 +04:00
// o inserts a new line after each line with a selection
2021-06-17 15:08:05 +04:00
fn open_below ( cx : & mut Context ) {
2021-04-01 06:36:59 +04:00
open ( cx , Open ::Below )
}
2020-09-13 14:51:42 +04:00
// O inserts a new line before each line with a selection
2021-06-17 15:08:05 +04:00
fn open_above ( cx : & mut Context ) {
2021-04-01 06:36:59 +04:00
open ( cx , Open ::Above )
2021-03-19 19:16:34 +04:00
}
2020-09-07 12:08:28 +04:00
2021-06-17 15:08:05 +04:00
fn normal_mode ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2020-10-07 13:31:16 +04:00
2021-02-21 14:04:31 +04:00
doc . mode = Mode ::Normal ;
2020-10-07 13:31:16 +04:00
2021-04-01 05:39:46 +04:00
doc . append_changes_to_history ( view . id ) ;
2021-01-21 11:55:46 +04:00
2020-10-01 13:44:12 +04:00
// if leaving append mode, move cursor back by 1
2021-01-21 12:00:08 +04:00
if doc . restore_cursor {
2021-02-18 13:34:22 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-04-01 05:39:46 +04:00
let selection = doc . selection ( view . id ) . transform ( | range | {
2020-10-01 13:44:12 +04:00
Range ::new (
range . from ( ) ,
graphemes ::prev_grapheme_boundary ( text , range . to ( ) ) ,
)
} ) ;
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2020-10-01 13:44:12 +04:00
2021-01-21 12:00:08 +04:00
doc . restore_cursor = false ;
2020-10-01 13:44:12 +04:00
}
2020-09-05 17:01:05 +04:00
}
2021-03-29 11:32:42 +04:00
// Store a jump on the jumplist.
2021-05-06 12:15:49 +04:00
fn push_jump ( editor : & mut Editor ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( editor ) ;
2021-06-08 20:40:38 +04:00
let jump = ( doc . id ( ) , doc . selection ( view . id ) . clone ( ) ) ;
2021-04-01 05:39:46 +04:00
view . jumps . push ( jump ) ;
2021-03-29 11:32:42 +04:00
}
2021-06-12 16:21:06 +04:00
fn switch_to_last_accessed_file ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let alternate_file = view! ( cx . editor ) . last_accessed_doc ;
2021-06-12 16:21:06 +04:00
if let Some ( alt ) = alternate_file {
cx . editor . switch ( alt , Action ::Replace ) ;
} else {
2021-06-12 19:59:04 +04:00
cx . editor . set_error ( " no last accessed buffer " . to_owned ( ) )
2021-06-12 16:21:06 +04:00
}
}
2021-06-17 15:08:05 +04:00
fn goto_mode ( cx : & mut Context ) {
2021-06-15 08:29:03 +04:00
if let Some ( count ) = cx . count {
2021-05-06 12:15:49 +04:00
push_jump ( cx . editor ) ;
2021-03-29 11:32:42 +04:00
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-06-08 07:24:27 +04:00
let line_idx = std ::cmp ::min ( count . get ( ) - 1 , doc . text ( ) . len_lines ( ) . saturating_sub ( 2 ) ) ;
2021-06-03 18:43:28 +04:00
let pos = doc . text ( ) . line_to_char ( line_idx ) ;
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , Selection ::point ( pos ) ) ;
2021-03-29 10:29:03 +04:00
return ;
}
2021-03-29 10:04:29 +04:00
cx . on_next_key ( move | cx , event | {
if let KeyEvent {
code : KeyCode ::Char ( ch ) ,
..
} = event
{
// TODO: temporarily show GOTO in the mode list
2021-06-18 02:09:10 +04:00
let doc = doc_mut! ( cx . editor ) ;
match ( doc . mode , ch ) {
2021-06-07 18:32:44 +04:00
( _ , 'g' ) = > move_file_start ( cx ) ,
( _ , 'e' ) = > move_file_end ( cx ) ,
2021-06-12 16:21:06 +04:00
( _ , 'a' ) = > switch_to_last_accessed_file ( cx ) ,
2021-06-07 18:32:44 +04:00
( Mode ::Normal , 'h' ) = > move_line_start ( cx ) ,
( Mode ::Normal , 'l' ) = > move_line_end ( cx ) ,
( Mode ::Select , 'h' ) = > extend_line_start ( cx ) ,
( Mode ::Select , 'l' ) = > extend_line_end ( cx ) ,
( _ , 'd' ) = > goto_definition ( cx ) ,
( _ , 'y' ) = > goto_type_definition ( cx ) ,
( _ , 'r' ) = > goto_reference ( cx ) ,
( _ , 'i' ) = > goto_implementation ( cx ) ,
2021-06-08 10:25:55 +04:00
( Mode ::Normal , 's' ) = > move_first_nonwhitespace ( cx ) ,
( Mode ::Select , 's' ) = > extend_first_nonwhitespace ( cx ) ,
2021-06-07 18:32:44 +04:00
( _ , 't' ) | ( _ , 'm' ) | ( _ , 'b' ) = > {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-06-04 10:47:29 +04:00
let pos = doc . selection ( view . id ) . cursor ( ) ;
let line = doc . text ( ) . char_to_line ( pos ) ;
let scrolloff = PADDING . min ( view . area . height as usize / 2 ) ; // TODO: user pref
let last_line = view . last_line ( doc ) ;
let line = match ch {
't' = > ( view . first_line + scrolloff ) ,
'm' = > ( view . first_line + ( view . area . height as usize / 2 ) ) ,
'b' = > last_line . saturating_sub ( scrolloff ) ,
_ = > unreachable! ( ) ,
}
. min ( last_line . saturating_sub ( scrolloff ) ) ;
let pos = doc . text ( ) . line_to_char ( line ) ;
doc . set_selection ( view . id , Selection ::point ( pos ) ) ;
}
2021-03-29 10:04:29 +04:00
_ = > ( ) ,
}
}
} )
2020-10-05 01:47:37 +04:00
}
2021-06-17 15:08:05 +04:00
fn select_mode ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
doc_mut! ( cx . editor ) . mode = Mode ::Select ;
2021-03-01 09:31:34 +04:00
}
2021-06-17 15:08:05 +04:00
fn exit_select_mode ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
doc_mut! ( cx . editor ) . mode = Mode ::Normal ;
2021-03-01 09:31:34 +04:00
}
2021-06-15 08:29:03 +04:00
fn goto_impl (
2021-05-06 12:15:49 +04:00
editor : & mut Editor ,
compositor : & mut Compositor ,
locations : Vec < lsp ::Location > ,
offset_encoding : OffsetEncoding ,
) {
2021-03-24 09:28:26 +04:00
use helix_view ::editor ::Action ;
2021-03-29 10:44:03 +04:00
2021-05-06 12:15:49 +04:00
push_jump ( editor ) ;
2021-03-29 11:32:42 +04:00
2021-04-14 10:30:15 +04:00
fn jump_to (
editor : & mut Editor ,
location : & lsp ::Location ,
offset_encoding : OffsetEncoding ,
action : Action ,
) {
2021-03-29 10:44:03 +04:00
let id = editor
. open ( PathBuf ::from ( location . uri . path ( ) ) , action )
. expect ( " editor.open failed " ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( editor ) ;
2021-03-29 10:44:03 +04:00
let definition_pos = location . range . start ;
2021-04-14 10:30:15 +04:00
// TODO: convert inside server
2021-06-12 11:04:30 +04: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 05:39:46 +04:00
doc . set_selection ( view . id , Selection ::point ( new_pos ) ) ;
2021-05-08 10:36:27 +04:00
align_view ( doc , view , Align ::Center ) ;
2021-03-29 10:44:03 +04:00
}
2021-02-28 03:39:13 +04:00
2021-03-11 08:15:25 +04:00
match locations . as_slice ( ) {
2021-02-28 03:39:13 +04:00
[ location ] = > {
2021-05-06 12:15:49 +04:00
jump_to ( editor , location , offset_encoding , Action ::Replace ) ;
2021-02-28 03:39:13 +04:00
}
2021-05-08 10:39:42 +04:00
[ ] = > {
editor . set_error ( " No definition found. " . to_string ( ) ) ;
}
2021-03-11 08:15:25 +04:00
_locations = > {
2021-02-28 03:39:13 +04:00
let mut picker = ui ::Picker ::new (
2021-03-11 08:15:25 +04:00
locations ,
2021-03-29 10:44:03 +04:00
| location | {
let file = location . uri . as_str ( ) ;
let line = location . range . start . line ;
2021-02-28 03:39:13 +04:00
format! ( " {} : {} " , file , line ) . into ( )
} ,
2021-04-14 10:30:15 +04:00
move | editor : & mut Editor , location , action | {
jump_to ( editor , location , offset_encoding , action )
} ,
2021-02-28 03:39:13 +04:00
) ;
2021-05-06 12:15:49 +04:00
compositor . push ( Box ::new ( picker ) ) ;
2021-02-28 03:39:13 +04:00
}
}
2021-02-22 02:43:28 +04:00
}
2021-02-22 02:22:38 +04:00
2021-06-17 15:08:05 +04:00
fn goto_definition ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-18 09:40:22 +04:00
let language_server = match doc . language_server ( ) {
2021-03-07 22:41:49 +04:00
Some ( language_server ) = > language_server ,
None = > return ,
} ;
2021-04-14 10:30:15 +04:00
let offset_encoding = language_server . offset_encoding ( ) ;
let pos = pos_to_lsp_pos ( doc . text ( ) , doc . selection ( view . id ) . cursor ( ) , offset_encoding ) ;
2021-03-07 22:41:49 +04:00
// TODO: handle fails
2021-06-18 07:39:37 +04:00
let future = language_server . goto_definition ( doc . identifier ( ) , pos , None ) ;
2021-05-06 12:15:49 +04: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 08:29:03 +04:00
goto_impl ( editor , compositor , items , offset_encoding ) ;
2021-05-06 12:15:49 +04:00
} ,
) ;
2021-03-07 22:41:49 +04:00
}
2021-06-17 15:08:05 +04:00
fn goto_type_definition ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-18 09:40:22 +04:00
let language_server = match doc . language_server ( ) {
2021-03-07 22:41:49 +04:00
Some ( language_server ) = > language_server ,
None = > return ,
} ;
2021-04-14 10:30:15 +04:00
let offset_encoding = language_server . offset_encoding ( ) ;
let pos = pos_to_lsp_pos ( doc . text ( ) , doc . selection ( view . id ) . cursor ( ) , offset_encoding ) ;
2021-03-07 22:41:49 +04:00
// TODO: handle fails
2021-06-18 07:39:37 +04:00
let future = language_server . goto_type_definition ( doc . identifier ( ) , pos , None ) ;
2021-05-06 12:15:49 +04: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 08:29:03 +04:00
goto_impl ( editor , compositor , items , offset_encoding ) ;
2021-05-06 12:15:49 +04:00
} ,
) ;
2021-03-07 22:41:49 +04:00
}
2021-06-17 15:08:05 +04:00
fn goto_implementation ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-18 09:40:22 +04:00
let language_server = match doc . language_server ( ) {
2021-03-07 22:41:49 +04:00
Some ( language_server ) = > language_server ,
None = > return ,
} ;
2021-04-14 10:30:15 +04:00
let offset_encoding = language_server . offset_encoding ( ) ;
let pos = pos_to_lsp_pos ( doc . text ( ) , doc . selection ( view . id ) . cursor ( ) , offset_encoding ) ;
2021-03-07 22:41:49 +04:00
// TODO: handle fails
2021-06-18 07:39:37 +04:00
let future = language_server . goto_implementation ( doc . identifier ( ) , pos , None ) ;
2021-05-06 12:15:49 +04: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 08:29:03 +04:00
goto_impl ( editor , compositor , items , offset_encoding ) ;
2021-05-06 12:15:49 +04:00
} ,
) ;
2021-03-07 22:41:49 +04:00
}
2021-06-17 15:08:05 +04:00
fn goto_reference ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-18 09:40:22 +04:00
let language_server = match doc . language_server ( ) {
2021-03-07 22:41:49 +04:00
Some ( language_server ) = > language_server ,
None = > return ,
} ;
2021-04-14 10:30:15 +04:00
let offset_encoding = language_server . offset_encoding ( ) ;
let pos = pos_to_lsp_pos ( doc . text ( ) , doc . selection ( view . id ) . cursor ( ) , offset_encoding ) ;
2021-03-07 22:41:49 +04:00
// TODO: handle fails
2021-06-18 07:39:37 +04:00
let future = language_server . goto_reference ( doc . identifier ( ) , pos , None ) ;
2021-05-06 12:15:49 +04:00
cx . callback (
future ,
move | editor : & mut Editor ,
compositor : & mut Compositor ,
items : Option < Vec < lsp ::Location > > | {
2021-06-15 08:29:03 +04:00
goto_impl (
2021-05-06 12:15:49 +04:00
editor ,
compositor ,
items . unwrap_or_default ( ) ,
offset_encoding ,
) ;
} ,
) ;
2021-03-07 22:41:49 +04:00
}
2021-06-06 13:59:32 +04:00
fn goto_pos ( editor : & mut Editor , pos : usize ) {
push_jump ( editor ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( editor ) ;
2021-06-06 13:59:32 +04:00
doc . set_selection ( view . id , Selection ::point ( pos ) ) ;
align_view ( doc , view , Align ::Center ) ;
}
2021-06-17 15:08:05 +04:00
fn goto_first_diag ( cx : & mut Context ) {
2021-06-06 13:59:32 +04:00
let editor = & mut cx . editor ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( editor ) ;
2021-06-06 13:59:32 +04:00
let cursor_pos = doc . selection ( view . id ) . cursor ( ) ;
let diag = if let Some ( diag ) = doc . diagnostics ( ) . first ( ) {
diag . range . start
} else {
return ;
} ;
goto_pos ( editor , diag ) ;
}
2021-06-17 15:08:05 +04:00
fn goto_last_diag ( cx : & mut Context ) {
2021-06-06 13:59:32 +04:00
let editor = & mut cx . editor ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( editor ) ;
2021-06-06 13:59:32 +04:00
let cursor_pos = doc . selection ( view . id ) . cursor ( ) ;
let diag = if let Some ( diag ) = doc . diagnostics ( ) . last ( ) {
diag . range . start
} else {
return ;
} ;
goto_pos ( editor , diag ) ;
}
2021-06-17 15:08:05 +04:00
fn goto_next_diag ( cx : & mut Context ) {
2021-06-06 13:59:32 +04:00
let editor = & mut cx . editor ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( editor ) ;
2021-06-06 13:59:32 +04:00
let cursor_pos = doc . selection ( view . id ) . cursor ( ) ;
let diag = if let Some ( diag ) = doc
. diagnostics ( )
. iter ( )
. map ( | diag | diag . range . start )
. find ( | & pos | pos > cursor_pos )
{
diag
} else if let Some ( diag ) = doc . diagnostics ( ) . first ( ) {
diag . range . start
} else {
return ;
} ;
goto_pos ( editor , diag ) ;
}
2021-06-17 15:08:05 +04:00
fn goto_prev_diag ( cx : & mut Context ) {
2021-06-06 13:59:32 +04:00
let editor = & mut cx . editor ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( editor ) ;
2021-06-06 13:59:32 +04:00
let cursor_pos = doc . selection ( view . id ) . cursor ( ) ;
let diag = if let Some ( diag ) = doc
. diagnostics ( )
. iter ( )
. rev ( )
. map ( | diag | diag . range . start )
. find ( | & pos | pos < cursor_pos )
{
diag
} else if let Some ( diag ) = doc . diagnostics ( ) . last ( ) {
diag . range . start
} else {
return ;
} ;
goto_pos ( editor , diag ) ;
}
2021-06-17 15:08:05 +04:00
fn signature_help ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-24 13:17:00 +04:00
let language_server = match doc . language_server ( ) {
Some ( language_server ) = > language_server ,
None = > return ,
} ;
2021-04-14 10:30:15 +04:00
let pos = pos_to_lsp_pos (
doc . text ( ) ,
doc . selection ( view . id ) . cursor ( ) ,
language_server . offset_encoding ( ) ,
) ;
2021-03-24 13:17:00 +04:00
// TODO: handle fails
2021-06-18 07:39:37 +04:00
let future = language_server . text_document_signature_help ( doc . identifier ( ) , pos , None ) ;
2021-03-24 13:17:00 +04:00
2021-05-06 12:15:49 +04:00
cx . callback (
future ,
move | editor : & mut Editor ,
compositor : & mut Compositor ,
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 13:17:00 +04:00
}
2020-10-14 07:09:55 +04: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 07:18:48 +04:00
pub type Hook = fn ( & Rope , & Selection , char ) -> Option < Transaction > ;
2021-03-24 13:17:00 +04:00
pub type PostHook = fn ( & mut Context , char ) ;
2021-03-22 07:18:48 +04:00
2021-03-24 13:17:00 +04:00
fn completion ( cx : & mut Context , ch : char ) {
// if ch matches completion char, trigger completion
2021-06-18 02:09:10 +04:00
let doc = doc_mut! ( cx . editor ) ;
2021-03-24 13:17:00 +04:00
let language_server = match doc . language_server ( ) {
Some ( language_server ) = > language_server ,
None = > return ,
} ;
let capabilities = language_server . capabilities ( ) ;
if let lsp ::ServerCapabilities {
completion_provider :
Some ( lsp ::CompletionOptions {
trigger_characters : Some ( triggers ) ,
..
} ) ,
..
} = capabilities
{
// TODO: what if trigger is multiple chars long
let is_trigger = triggers . iter ( ) . any ( | trigger | trigger . contains ( ch ) ) ;
if is_trigger {
super ::completion ( cx ) ;
}
}
}
fn signature_help ( cx : & mut Context , ch : char ) {
// if ch matches signature_help char, trigger
2021-06-18 02:09:10 +04:00
let doc = doc_mut! ( cx . editor ) ;
2021-03-24 13:17:00 +04: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 12:56:02 +04:00
2021-05-05 11:25:17 +04: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 13:17:00 +04:00
}
2021-05-03 12:56:02 +04:00
// The default insert hook: simply insert the character
2021-05-05 11:25:17 +04:00
#[ allow(clippy::unnecessary_wraps) ] // need to use Option<> because of the Hook signature
2021-05-03 12:56:02 +04:00
fn insert ( doc : & Rope , selection : & Selection , ch : char ) -> Option < Transaction > {
let t = Tendril ::from_char ( ch ) ;
let transaction = Transaction ::insert ( doc , selection , t ) ;
Some ( transaction )
}
use helix_core ::auto_pairs ;
const HOOKS : & [ Hook ] = & [ auto_pairs ::hook , insert ] ;
2021-03-24 13:17:00 +04:00
const POST_HOOKS : & [ PostHook ] = & [ completion , signature_help ] ;
2020-10-30 09:09:59 +04:00
pub fn insert_char ( cx : & mut Context , c : char ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-22 07:18:48 +04:00
2021-05-03 12:56:02 +04:00
let text = doc . text ( ) ;
let selection = doc . selection ( view . id ) ;
2021-03-22 07:18:48 +04:00
// run through insert hooks, stopping on the first one that returns Some(t)
for hook in HOOKS {
2021-05-03 12:56:02 +04:00
if let Some ( transaction ) = hook ( text , selection , c ) {
2021-04-01 05:39:46 +04:00
doc . apply ( & transaction , view . id ) ;
2021-05-03 12:56:02 +04:00
break ;
2021-03-22 07:18:48 +04:00
}
}
2021-03-24 13:17:00 +04: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.
for hook in POST_HOOKS {
hook ( cx , c ) ;
}
2020-10-14 07:09:55 +04:00
}
2020-09-13 06:32:37 +04:00
2020-10-30 09:09:59 +04:00
pub fn insert_tab ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-22 08:47:39 +04: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-04-01 05:39:46 +04:00
let transaction = Transaction ::insert ( doc . text ( ) , doc . selection ( view . id ) , indent ) ;
doc . apply ( & transaction , view . id ) ;
2020-10-14 07:09:55 +04:00
}
2020-10-01 23:16:24 +04:00
2020-10-30 09:09:59 +04:00
pub fn insert_newline ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-18 08:45:57 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-05-06 17:59:59 +04:00
let contents = doc . text ( ) ;
let selection = doc . selection ( view . id ) ;
let mut ranges = SmallVec ::with_capacity ( selection . len ( ) ) ;
2021-05-15 05:26:41 +04:00
// TODO: this is annoying, but we need to do it to properly calculate pos after edits
let mut offs = 0 ;
2021-05-06 17:59:59 +04:00
let mut transaction = Transaction ::change_by_selection ( contents , selection , | range | {
let pos = range . head ;
let prev = if pos = = 0 {
' '
} else {
contents . char ( pos - 1 )
} ;
let curr = contents . char ( pos ) ;
// TODO: offset range.head by 1? when calculating?
2021-05-14 14:21:46 +04:00
let indent_level = indent ::suggested_indent_for_pos (
doc . language_config ( ) ,
doc . syntax ( ) ,
text ,
pos . saturating_sub ( 1 ) ,
true ,
) ;
2021-05-06 17:59:59 +04:00
let indent = doc . indent_unit ( ) . repeat ( indent_level ) ;
let mut text = String ::with_capacity ( 1 + indent . len ( ) ) ;
2021-06-21 20:52:21 +04:00
text . push_str ( doc . line_ending . as_str ( ) ) ;
2021-05-06 17:59:59 +04:00
text . push_str ( & indent ) ;
2021-06-07 04:26:49 +04:00
let head = pos + offs + text . chars ( ) . count ( ) ;
2021-05-06 17:59:59 +04:00
2021-05-27 19:00:51 +04: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 17:59:59 +04:00
ranges . push ( Range ::new (
2021-05-15 05:26:41 +04:00
if range . is_empty ( ) {
head
} else {
range . anchor + offs
} ,
2021-05-06 17:59:59 +04: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 20:52:21 +04:00
text . push_str ( doc . line_ending . as_str ( ) ) ;
2021-03-31 12:17:01 +04:00
text . push_str ( & indent ) ;
2021-05-06 17:59:59 +04:00
}
2021-05-15 05:26:41 +04:00
2021-06-07 04:26:49 +04:00
offs + = text . chars ( ) . count ( ) ;
2021-05-15 05:26:41 +04:00
( pos , pos , Some ( text . into ( ) ) )
2021-05-06 17:59:59 +04:00
} ) ;
transaction = transaction . with_selection ( Selection ::new ( ranges , selection . primary_index ( ) ) ) ;
//
2021-04-01 05:39:46 +04:00
doc . apply ( & transaction , view . id ) ;
2020-10-14 07:09:55 +04:00
}
2020-10-01 03:15:42 +04:00
2020-10-14 07:09:55 +04:00
// TODO: handle indent-aware delete
2020-10-30 09:09:59 +04:00
pub fn delete_char_backward ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-02-18 13:34:22 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-03-31 12:17:01 +04:00
let transaction =
2021-04-01 05:39:46 +04:00
Transaction ::change_by_selection ( doc . text ( ) , doc . selection ( view . id ) , | range | {
2021-03-31 12:17:01 +04:00
(
graphemes ::nth_prev_grapheme_boundary ( text , range . head , count ) ,
range . head ,
None ,
)
} ) ;
2021-04-01 05:39:46 +04:00
doc . apply ( & transaction , view . id ) ;
2020-10-14 07:09:55 +04:00
}
2020-09-13 06:32:37 +04:00
2020-10-30 09:09:59 +04:00
pub fn delete_char_forward ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-02-18 13:34:22 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-03-31 12:17:01 +04:00
let transaction =
2021-04-01 05:39:46 +04:00
Transaction ::change_by_selection ( doc . text ( ) , doc . selection ( view . id ) , | range | {
2021-03-31 12:17:01 +04:00
(
range . head ,
graphemes ::nth_next_grapheme_boundary ( text , range . head , count ) ,
None ,
)
} ) ;
2021-04-01 05:39:46 +04:00
doc . apply ( & transaction , view . id ) ;
2020-10-14 07:09:55 +04:00
}
2021-06-05 14:15:50 +04:00
pub fn delete_word_backward ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-06-05 14:15:50 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-06-11 16:57:07 +04:00
let selection = doc
. selection ( view . id )
. transform ( | range | movement ::move_prev_word_start ( text , range , count ) ) ;
doc . set_selection ( view . id , selection ) ;
delete_selection ( cx )
2021-06-05 14:15:50 +04:00
}
2020-09-13 06:32:37 +04:00
}
2020-10-04 12:15:43 +04:00
// Undo / Redo
2020-12-21 08:58:54 +04:00
// TODO: each command could simply return a Option<transaction>, then the higher level handles
// storing it?
2020-10-04 12:15:43 +04:00
2021-06-17 15:08:05 +04:00
fn undo ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
let view_id = view . id ;
doc . undo ( view_id ) ;
2020-10-04 12:15:43 +04:00
}
2021-06-17 15:08:05 +04:00
fn redo ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
let view_id = view . id ;
doc . redo ( view_id ) ;
2020-10-04 12:15:43 +04:00
}
2020-10-06 11:00:23 +04:00
// Yank / Paste
2021-06-17 15:08:05 +04:00
fn yank ( cx : & mut Context ) {
2020-10-06 11:00:23 +04:00
// TODO: should selections be made end inclusive?
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-29 11:47:02 +04:00
let values : Vec < String > = doc
2021-04-01 05:39:46 +04:00
. selection ( view . id )
2021-02-18 13:34:22 +04:00
. fragments ( doc . text ( ) . slice ( .. ) )
2021-03-31 18:42:16 +04:00
. map ( Cow ::into_owned )
2020-10-06 11:00:23 +04:00
. collect ( ) ;
2021-06-05 06:21:31 +04:00
let msg = format! (
" yanked {} selection(s) to register {} " ,
values . len ( ) ,
2021-06-15 07:26:05 +04:00
cx . selected_register . name ( )
2021-06-05 06:21:31 +04:00
) ;
2021-03-29 11:47:02 +04:00
2021-06-15 07:26:05 +04:00
cx . editor
. registers
. write ( cx . selected_register . name ( ) , values ) ;
2021-03-29 11:47:02 +04:00
2021-05-07 09:19:58 +04:00
cx . editor . set_status ( msg )
2020-10-06 11:00:23 +04:00
}
2021-06-19 10:27:43 +04:00
fn yank_joined_to_clipboard_impl ( editor : & mut Editor , separator : & str ) {
let ( view , doc ) = current! ( editor ) ;
let values : Vec < String > = doc
. selection ( view . id )
. fragments ( doc . text ( ) . slice ( .. ) )
. map ( Cow ::into_owned )
. collect ( ) ;
let msg = format! (
" joined and yanked {} selection(s) to system clipboard " ,
values . len ( ) ,
) ;
let joined = values . join ( separator ) ;
if let Err ( e ) = editor . clipboard_provider . set_contents ( joined ) {
log ::error! ( " Couldn't set system clipboard content: {:?} " , e ) ;
}
editor . set_status ( msg ) ;
}
fn yank_joined_to_clipboard ( cx : & mut Context ) {
2021-06-21 22:59:03 +04:00
let line_ending = current! ( cx . editor ) . 1. line_ending ;
yank_joined_to_clipboard_impl ( & mut cx . editor , line_ending . as_str ( ) ) ;
2021-06-19 10:27:43 +04:00
}
fn yank_main_selection_to_clipboard_impl ( editor : & mut Editor ) {
let ( view , doc ) = current! ( editor ) ;
let value = doc
. selection ( view . id )
. primary ( )
. fragment ( doc . text ( ) . slice ( .. ) ) ;
if let Err ( e ) = editor . clipboard_provider . set_contents ( value . into_owned ( ) ) {
log ::error! ( " Couldn't set system clipboard content: {:?} " , e ) ;
}
editor . set_status ( " yanked main selection to system clipboard " . to_owned ( ) ) ;
}
fn yank_main_selection_to_clipboard ( cx : & mut Context ) {
yank_main_selection_to_clipboard_impl ( & mut cx . editor ) ;
}
2021-04-07 12:03:29 +04:00
#[ derive(Copy, Clone) ]
enum Paste {
Before ,
After ,
}
2021-06-15 07:26:05 +04:00
fn paste_impl (
values : & [ String ] ,
doc : & mut Document ,
view : & View ,
action : Paste ,
) -> Option < Transaction > {
let repeat = std ::iter ::repeat (
values
. last ( )
. map ( | value | Tendril ::from_slice ( value ) )
. unwrap ( ) ,
) ;
2020-10-06 11:00:23 +04:00
2021-06-21 21:29:29 +04:00
// if any of values ends with a line ending, it's linewise paste
2021-06-16 19:22:55 +04:00
let linewise = values
. iter ( )
2021-06-21 21:29:29 +04:00
. any ( | value | get_line_ending_of_str ( value ) . is_some ( ) ) ;
2020-10-09 11:58:43 +04:00
2021-06-15 07:26:05 +04:00
let mut values = values . iter ( ) . cloned ( ) . map ( Tendril ::from ) . chain ( repeat ) ;
2020-10-06 11:00:23 +04:00
2021-06-15 07:26:05 +04:00
let text = doc . text ( ) ;
2021-01-21 11:55:46 +04:00
2021-06-15 07:26:05 +04:00
let transaction = Transaction ::change_by_selection ( text , doc . selection ( view . id ) , | range | {
let pos = match ( action , linewise ) {
// paste linewise before
( Paste ::Before , true ) = > text . line_to_char ( text . char_to_line ( range . from ( ) ) ) ,
// paste linewise after
( Paste ::After , true ) = > text . line_to_char ( text . char_to_line ( range . to ( ) ) + 1 ) ,
// paste insert
( Paste ::Before , false ) = > range . from ( ) ,
// paste append
( Paste ::After , false ) = > range . to ( ) + 1 ,
} ;
( pos , pos , Some ( values . next ( ) . unwrap ( ) ) )
} ) ;
Some ( transaction )
2021-04-07 12:03:29 +04:00
}
2021-06-19 10:27:43 +04:00
fn paste_clipboard_impl ( editor : & mut Editor , action : Paste ) {
let ( view , doc ) = current! ( editor ) ;
match editor
. clipboard_provider
. get_contents ( )
. map ( | contents | paste_impl ( & [ contents ] , doc , view , action ) )
{
Ok ( Some ( transaction ) ) = > {
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
}
Ok ( None ) = > { }
Err ( e ) = > log ::error! ( " Couldn't get system clipboard contents: {:?} " , e ) ,
}
}
fn paste_clipboard_after ( cx : & mut Context ) {
paste_clipboard_impl ( & mut cx . editor , Paste ::After ) ;
}
fn paste_clipboard_before ( cx : & mut Context ) {
paste_clipboard_impl ( & mut cx . editor , Paste ::Before ) ;
}
2021-06-17 15:08:05 +04:00
fn replace_with_yanked ( cx : & mut Context ) {
2021-06-15 07:26:05 +04:00
let reg_name = cx . selected_register . name ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
let registers = & mut cx . editor . registers ;
2021-06-07 22:13:40 +04:00
2021-06-15 07:26:05 +04:00
if let Some ( values ) = registers . read ( reg_name ) {
2021-06-07 22:13:40 +04:00
if let Some ( yank ) = values . first ( ) {
let transaction =
Transaction ::change_by_selection ( doc . text ( ) , doc . selection ( view . id ) , | range | {
let max_to = doc . text ( ) . len_chars ( ) . saturating_sub ( 1 ) ;
2021-06-13 22:27:03 +04:00
let to = std ::cmp ::min ( max_to , range . to ( ) + 1 ) ;
2021-06-07 22:13:40 +04:00
( range . from ( ) , to , Some ( yank . as_str ( ) . into ( ) ) )
} ) ;
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
}
}
}
2021-06-19 10:27:43 +04:00
fn replace_selections_with_clipboard_impl ( editor : & mut Editor ) {
let ( view , doc ) = current! ( editor ) ;
match editor . clipboard_provider . get_contents ( ) {
Ok ( contents ) = > {
let transaction =
Transaction ::change_by_selection ( doc . text ( ) , doc . selection ( view . id ) , | range | {
let max_to = doc . text ( ) . len_chars ( ) . saturating_sub ( 1 ) ;
let to = std ::cmp ::min ( max_to , range . to ( ) + 1 ) ;
( range . from ( ) , to , Some ( contents . as_str ( ) . into ( ) ) )
} ) ;
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
}
Err ( e ) = > log ::error! ( " Couldn't get system clipboard contents: {:?} " , e ) ,
}
}
fn replace_selections_with_clipboard ( cx : & mut Context ) {
replace_selections_with_clipboard_impl ( & mut cx . editor ) ;
}
2021-04-07 12:03:29 +04:00
// alt-p => paste every yanked selection after selected text
// alt-P => paste every yanked selection before selected text
// R => replace selected text with yanked text
// alt-R => replace selected text with every yanked text
//
// append => insert at next line
// insert => insert at start of line
// replace => replace
// default insert
2021-06-17 15:08:05 +04:00
fn paste_after ( cx : & mut Context ) {
2021-06-15 07:26:05 +04:00
let reg_name = cx . selected_register . name ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
let registers = & mut cx . editor . registers ;
2021-04-07 12:03:29 +04:00
2021-06-15 07:26:05 +04:00
if let Some ( transaction ) = registers
. read ( reg_name )
. and_then ( | values | paste_impl ( values , doc , view , Paste ::After ) )
{
2021-04-07 12:03:29 +04:00
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
}
}
2021-06-17 15:08:05 +04:00
fn paste_before ( cx : & mut Context ) {
2021-06-15 07:26:05 +04:00
let reg_name = cx . selected_register . name ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
let registers = & mut cx . editor . registers ;
2020-10-06 11:00:23 +04:00
2021-06-15 07:26:05 +04:00
if let Some ( transaction ) = registers
. read ( reg_name )
. and_then ( | values | paste_impl ( values , doc , view , Paste ::Before ) )
{
2021-04-01 05:39:46 +04:00
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
2020-10-06 11:00:23 +04:00
}
}
2020-10-09 11:58:43 +04:00
2021-03-31 12:17:01 +04:00
fn get_lines ( doc : & Document , view_id : ViewId ) -> Vec < usize > {
2020-10-09 11:58:43 +04:00
let mut lines = Vec ::new ( ) ;
// Get all line numbers
2021-03-31 12:17:01 +04:00
for range in doc . selection ( view_id ) {
2021-01-21 12:00:08 +04:00
let start = doc . text ( ) . char_to_line ( range . from ( ) ) ;
let end = doc . text ( ) . char_to_line ( range . to ( ) ) ;
2020-10-09 11:58:43 +04:00
for line in start ..= end {
lines . push ( line )
}
}
lines . sort_unstable ( ) ; // sorting by usize so _unstable is preferred
lines . dedup ( ) ;
2020-10-13 18:08:28 +04:00
lines
}
2021-06-17 15:08:05 +04:00
fn indent ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-04-01 05:39:46 +04:00
let lines = get_lines ( doc , view . id ) ;
2020-10-09 11:58:43 +04:00
// Indent by one level
2021-05-18 19:37:01 +04:00
let indent = Tendril ::from ( doc . indent_unit ( ) . repeat ( count ) ) ;
2020-10-09 11:58:43 +04:00
let transaction = Transaction ::change (
2021-03-18 08:28:27 +04:00
doc . text ( ) ,
2020-10-09 11:58:43 +04:00
lines . into_iter ( ) . map ( | line | {
2021-01-21 12:00:08 +04:00
let pos = doc . text ( ) . line_to_char ( line ) ;
2020-10-09 11:58:43 +04:00
( pos , pos , Some ( indent . clone ( ) ) )
} ) ,
) ;
2021-04-01 05:39:46 +04:00
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
2020-10-09 11:58:43 +04:00
}
2021-06-17 15:08:05 +04:00
fn unindent ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-04-01 05:39:46 +04:00
let lines = get_lines ( doc , view . id ) ;
2020-10-13 18:08:28 +04:00
let mut changes = Vec ::with_capacity ( lines . len ( ) ) ;
2021-03-22 08:47:39 +04:00
let tab_width = doc . tab_width ( ) ;
2021-05-18 19:34:46 +04:00
let indent_width = count * tab_width ;
2020-10-13 18:08:28 +04:00
for line_idx in lines {
2021-01-21 12:00:08 +04:00
let line = doc . text ( ) . line ( line_idx ) ;
2020-10-13 18:08:28 +04:00
let mut width = 0 ;
2021-05-18 19:34:46 +04:00
let mut pos = 0 ;
2020-10-13 18:08:28 +04:00
for ch in line . chars ( ) {
match ch {
' ' = > width + = 1 ,
2021-03-22 08:47:39 +04:00
'\t' = > width = ( width / tab_width + 1 ) * tab_width ,
2020-10-13 18:08:28 +04:00
_ = > break ,
}
2021-05-18 19:34:46 +04:00
pos + = 1 ;
if width > = indent_width {
2020-10-13 18:08:28 +04:00
break ;
}
}
2021-05-18 19:34:46 +04:00
// now delete from start to first non-blank
if pos > 0 {
2021-01-21 12:00:08 +04:00
let start = doc . text ( ) . line_to_char ( line_idx ) ;
2021-05-18 19:34:46 +04:00
changes . push ( ( start , start + pos , None ) )
2020-10-13 18:08:28 +04:00
}
}
2021-03-18 08:28:27 +04:00
let transaction = Transaction ::change ( doc . text ( ) , changes . into_iter ( ) ) ;
2020-10-13 18:08:28 +04:00
2021-04-01 05:39:46 +04:00
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
2020-10-09 11:58:43 +04:00
}
2020-10-14 13:07:42 +04:00
2021-06-17 15:08:05 +04:00
fn format_selections ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-02-26 10:52:43 +04:00
// via lsp if available
// else via tree-sitter indentation calculations
2021-04-14 10:30:15 +04:00
let language_server = match doc . language_server ( ) {
Some ( language_server ) = > language_server ,
None = > return ,
} ;
2021-02-26 10:52:43 +04:00
let ranges : Vec < lsp ::Range > = doc
2021-04-01 05:39:46 +04:00
. selection ( view . id )
2021-02-26 10:52:43 +04:00
. iter ( )
2021-04-14 10:30:15 +04:00
. map ( | range | range_to_lsp_range ( doc . text ( ) , * range , language_server . offset_encoding ( ) ) )
2021-02-26 10:52:43 +04:00
. collect ( ) ;
for range in ranges {
2021-03-18 09:40:22 +04:00
let language_server = match doc . language_server ( ) {
2021-02-26 10:52:43 +04:00
Some ( language_server ) = > language_server ,
None = > return ,
} ;
// TODO: handle fails
// TODO: concurrent map
2021-05-08 13:17:13 +04:00
// TODO: need to block to get the formatting
2021-05-06 12:15:49 +04: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 10:52:43 +04:00
}
2021-04-01 05:39:46 +04:00
doc . append_changes_to_history ( view . id ) ;
2021-02-26 10:52:43 +04:00
}
2021-06-17 15:08:05 +04:00
fn join_selections ( cx : & mut Context ) {
2021-06-11 16:57:07 +04:00
use movement ::skip_while ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-02-26 12:21:59 +04:00
let text = doc . text ( ) ;
let slice = doc . text ( ) . slice ( .. ) ;
let mut changes = Vec ::new ( ) ;
let fragment = Tendril ::from ( " " ) ;
2021-04-01 05:39:46 +04:00
for selection in doc . selection ( view . id ) {
2021-02-26 12:21:59 +04:00
let start = text . char_to_line ( selection . from ( ) ) ;
let mut end = text . char_to_line ( selection . to ( ) ) ;
if start = = end {
end + = 1
}
let lines = start .. end ;
changes . reserve ( lines . len ( ) ) ;
for line in lines {
let mut start = text . line_to_char ( line + 1 ) . saturating_sub ( 1 ) ;
let mut end = start + 1 ;
2021-06-11 16:57:07 +04:00
end = skip_while ( slice , end , | ch | matches! ( ch , ' ' | '\t' ) ) . unwrap_or ( end ) ;
2021-02-26 12:21:59 +04: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 08:28:27 +04:00
let transaction = Transaction ::change ( doc . text ( ) , changes . into_iter ( ) ) ;
2021-02-26 12:21:59 +04:00
// TODO: select inserted spaces
// .with_selection(selection);
2021-04-01 05:39:46 +04:00
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
2021-02-26 12:21:59 +04:00
}
2021-06-17 15:08:05 +04:00
fn keep_selections ( cx : & mut Context ) {
2021-03-01 13:19:08 +04:00
// keep selections matching regex
2021-06-15 07:26:05 +04:00
let prompt = ui ::regex_prompt ( cx , " keep: " . to_string ( ) , move | view , doc , _ , regex | {
2021-03-15 12:09:18 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-04-14 10:21:49 +04:00
if let Some ( selection ) = selection ::keep_matches ( text , doc . selection ( view . id ) , & regex ) {
doc . set_selection ( view . id , selection ) ;
2021-03-15 12:09:18 +04:00
}
} ) ;
cx . push_layer ( Box ::new ( prompt ) ) ;
2021-03-01 13:19:08 +04:00
}
2021-06-17 15:08:05 +04:00
fn keep_primary_selection ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-15 12:13:36 +04:00
2021-04-01 05:39:46 +04:00
let range = doc . selection ( view . id ) . primary ( ) ;
2021-03-15 12:13:36 +04:00
let selection = Selection ::single ( range . anchor , range . head ) ;
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2021-03-15 12:13:36 +04:00
}
2020-10-30 12:00:30 +04:00
//
2021-06-17 15:08:05 +04:00
fn save ( cx : & mut Context ) {
2020-10-30 12:00:30 +04:00
// Spawns an async task to actually do the saving. This way we prevent blocking.
// TODO: handle save errors somehow?
2021-06-18 02:09:10 +04:00
tokio ::spawn ( doc_mut! ( cx . editor ) . save ( ) ) ;
2020-10-30 12:00:30 +04:00
}
2020-12-23 11:20:49 +04:00
2021-06-17 15:08:05 +04:00
fn completion ( cx : & mut Context ) {
2021-03-26 11:02:13 +04: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 07:06:40 +04: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 11:02:13 +04:00
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-01-21 11:55:46 +04:00
2021-03-18 09:40:22 +04:00
let language_server = match doc . language_server ( ) {
2021-02-22 06:42:47 +04:00
Some ( language_server ) = > language_server ,
None = > return ,
} ;
2021-04-14 10:30:15 +04:00
let offset_encoding = language_server . offset_encoding ( ) ;
2021-06-12 11:04:30 +04:00
let pos = pos_to_lsp_pos ( doc . text ( ) , doc . selection ( view . id ) . cursor ( ) , offset_encoding ) ;
2021-01-06 12:48:14 +04:00
// TODO: handle fails
2021-06-18 07:39:37 +04:00
let future = language_server . completion ( doc . identifier ( ) , pos , None ) ;
2021-03-26 11:02:13 +04:00
2021-04-01 05:39:46 +04:00
let trigger_offset = doc . selection ( view . id ) . cursor ( ) ;
2021-03-27 07:06:40 +04:00
2021-03-26 11:02:13 +04:00
cx . callback (
2021-05-06 10:08:59 +04:00
future ,
2021-03-27 07:06:40 +04:00
move | editor : & mut Editor ,
compositor : & mut Compositor ,
response : Option < lsp ::CompletionResponse > | {
2021-03-26 11:02:13 +04:00
let items = match response {
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 ( ) ,
} ;
// TODO: if no completion, show some message or something
2021-05-08 10:39:42 +04:00
if items . is_empty ( ) {
return ;
2021-03-26 11:02:13 +04:00
}
2021-05-08 10:39:42 +04:00
use crate ::compositor ::AnyComponent ;
let size = compositor . size ( ) ;
2021-06-07 20:38:57 +04:00
let ui = compositor
. find ( std ::any ::type_name ::< ui ::EditorView > ( ) )
. unwrap ( ) ;
2021-05-08 10:39:42 +04:00
if let Some ( ui ) = ui . as_any_mut ( ) . downcast_mut ::< ui ::EditorView > ( ) {
ui . set_completion ( items , offset_encoding , trigger_offset , size ) ;
} ;
2021-03-26 11:02:13 +04:00
} ,
) ;
2021-04-01 06:37:18 +04:00
// TODO: Server error: content modified
2020-12-23 12:03:20 +04:00
2021-03-26 11:02:13 +04:00
// // TODO!: when iterating over items, show the docs in popup
2020-12-23 11:20:49 +04:00
2021-03-26 11:02:13 +04:00
// // language server client needs to be accessible via a registry of some sort
//}
2020-12-23 11:20:49 +04:00
}
2021-02-04 14:49:29 +04:00
2021-06-17 15:08:05 +04:00
fn hover ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-02-25 13:07:47 +04:00
2021-03-18 09:40:22 +04:00
let language_server = match doc . language_server ( ) {
2021-02-25 13:07:47 +04:00
Some ( language_server ) = > language_server ,
None = > return ,
} ;
// TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier
2021-04-14 10:30:15 +04:00
let pos = pos_to_lsp_pos (
doc . text ( ) ,
doc . selection ( view . id ) . cursor ( ) ,
language_server . offset_encoding ( ) ,
) ;
2021-02-25 13:07:47 +04:00
// TODO: handle fails
2021-06-18 07:39:37 +04:00
let future = language_server . text_document_hover ( doc . identifier ( ) , pos , None ) ;
2021-02-25 13:07:47 +04:00
2021-05-06 12:15:49 +04: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
let contents = match hover . contents {
lsp ::HoverContents ::Scalar ( contents ) = > {
// markedstring(string/languagestring to be highlighted)
// TODO
2021-05-08 13:17:13 +04:00
log ::error! ( " hover contents {:?} " , contents ) ;
return ;
2021-05-06 12:15:49 +04:00
}
lsp ::HoverContents ::Array ( contents ) = > {
2021-05-08 13:17:13 +04:00
log ::error! ( " hover contents {:?} " , contents ) ;
return ;
2021-05-06 12:15:49 +04:00
}
// TODO: render markdown
lsp ::HoverContents ::Markup ( contents ) = > contents . value ,
} ;
2021-02-25 13:07:47 +04:00
2021-05-06 12:15:49 +04:00
// skip if contents empty
2021-06-19 15:27:32 +04:00
let contents = ui ::Markdown ::new ( contents , editor . syn_loader . clone ( ) ) ;
2021-05-06 12:15:49 +04:00
let mut popup = Popup ::new ( contents ) ;
compositor . push ( Box ::new ( popup ) ) ;
}
} ,
) ;
2021-02-25 13:07:47 +04:00
}
2021-02-19 08:59:24 +04:00
// comments
2021-06-17 15:08:05 +04:00
fn toggle_comments ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-04-01 05:39:46 +04:00
let transaction = comment ::toggle_line_comments ( doc . text ( ) , doc . selection ( view . id ) ) ;
2021-02-19 08:59:24 +04:00
2021-04-01 05:39:46 +04:00
doc . apply ( & transaction , view . id ) ;
doc . append_changes_to_history ( view . id ) ;
2021-02-19 08:59:24 +04:00
}
2021-02-22 10:50:41 +04:00
// tree sitter node selection
2021-06-17 15:08:05 +04:00
fn expand_selection ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-02-22 10:50:41 +04:00
2021-03-18 09:48:42 +04:00
if let Some ( syntax ) = doc . syntax ( ) {
2021-02-22 10:50:41 +04:00
let text = doc . text ( ) . slice ( .. ) ;
2021-04-01 05:39:46 +04:00
let selection = object ::expand_selection ( syntax , text , doc . selection ( view . id ) ) ;
doc . set_selection ( view . id , selection ) ;
2021-02-22 10:50:41 +04:00
}
}
2021-03-22 12:58:49 +04:00
2021-06-17 15:08:05 +04:00
fn match_brackets ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-22 12:58:49 +04:00
if let Some ( syntax ) = doc . syntax ( ) {
2021-04-01 05:39:46 +04:00
let pos = doc . selection ( view . id ) . cursor ( ) ;
2021-03-22 12:58:49 +04:00
if let Some ( pos ) = match_brackets ::find ( syntax , doc . text ( ) , pos ) {
let selection = Selection ::point ( pos ) ;
2021-04-01 05:39:46 +04:00
doc . set_selection ( view . id , selection ) ;
2021-03-22 12:58:49 +04:00
} ;
}
}
2021-03-24 13:01:26 +04:00
//
2021-06-17 15:08:05 +04:00
fn jump_forward ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-24 13:01:26 +04:00
if let Some ( ( id , selection ) ) = view . jumps . forward ( count ) {
view . doc = * id ;
2021-04-24 06:46:46 +04:00
let selection = selection . clone ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ; // refetch doc
2021-04-24 06:46:46 +04:00
doc . set_selection ( view . id , selection ) ;
2021-05-08 10:36:27 +04:00
align_view ( doc , view , Align ::Center ) ;
2021-03-24 13:01:26 +04:00
} ;
}
2021-06-17 15:08:05 +04:00
fn jump_backward ( cx : & mut Context ) {
2021-06-08 07:24:27 +04:00
let count = cx . count ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-03-24 13:01:26 +04:00
2021-06-08 20:40:38 +04:00
if let Some ( ( id , selection ) ) = view . jumps . backward ( view . id , doc , count ) {
2021-06-12 16:21:06 +04: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 13:01:26 +04:00
view . doc = * id ;
2021-05-08 13:25:19 +04:00
let selection = selection . clone ( ) ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ; // refetch doc
2021-05-08 13:25:19 +04:00
doc . set_selection ( view . id , selection ) ;
2021-05-08 10:36:27 +04:00
align_view ( doc , view , Align ::Center ) ;
2021-03-24 13:01:26 +04:00
} ;
}
2021-03-30 13:38:26 +04:00
2021-06-17 15:08:05 +04:00
fn window_mode ( cx : & mut Context ) {
2021-06-05 11:45:24 +04:00
cx . on_next_key ( move | cx , event | {
if let KeyEvent {
code : KeyCode ::Char ( ch ) ,
..
} = event
{
match ch {
'w' = > rotate_view ( cx ) ,
'h' = > hsplit ( cx ) ,
'v' = > vsplit ( cx ) ,
'q' = > wclose ( cx ) ,
_ = > { }
}
}
} )
}
2021-03-30 13:38:26 +04:00
2021-06-17 15:08:05 +04:00
fn rotate_view ( cx : & mut Context ) {
2021-06-05 11:45:24 +04:00
cx . editor . focus_next ( )
}
// split helper, clear it later
use helix_view ::editor ::Action ;
2021-06-17 15:08:05 +04:00
use self ::cmd ::TypableCommand ;
2021-06-05 11:45:24 +04:00
fn split ( cx : & mut Context , action : Action ) {
2021-05-08 10:29:15 +04:00
use helix_view ::editor ::Action ;
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-05-08 13:25:19 +04:00
let id = doc . id ( ) ;
let selection = doc . selection ( view . id ) . clone ( ) ;
let first_line = view . first_line ;
2021-03-31 13:00:53 +04:00
2021-06-05 11:45:24 +04:00
cx . editor . switch ( id , action ) ;
2021-05-08 13:25:19 +04:00
// match the selection in the previous view
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-05-08 13:25:19 +04:00
view . first_line = first_line ;
doc . set_selection ( view . id , selection ) ;
2021-03-31 13:00:53 +04:00
}
2021-06-17 15:08:05 +04:00
fn hsplit ( cx : & mut Context ) {
2021-06-05 11:45:24 +04:00
split ( cx , Action ::HorizontalSplit ) ;
}
2021-06-17 15:08:05 +04:00
fn vsplit ( cx : & mut Context ) {
2021-06-05 11:45:24 +04:00
split ( cx , Action ::VerticalSplit ) ;
}
2021-06-17 15:08:05 +04:00
fn wclose ( cx : & mut Context ) {
2021-06-18 02:09:10 +04:00
let view_id = view! ( cx . editor ) . id ;
2021-06-05 11:45:24 +04:00
// close current split
cx . editor . close ( view_id , /* close_buffer */ false ) ;
}
2021-06-17 15:08:05 +04:00
fn select_register ( cx : & mut Context ) {
2021-06-05 06:21:31 +04:00
cx . on_next_key ( move | cx , event | {
if let KeyEvent {
code : KeyCode ::Char ( ch ) ,
..
} = event
{
2021-06-15 07:26:05 +04:00
cx . editor . selected_register . select ( ch ) ;
2021-06-05 06:21:31 +04:00
}
} )
}
2021-06-17 15:08:05 +04:00
fn space_mode ( cx : & mut Context ) {
2021-03-30 13:38:26 +04:00
cx . on_next_key ( move | cx , event | {
if let KeyEvent {
code : KeyCode ::Char ( ch ) ,
..
} = event
{
// TODO: temporarily show SPC in the mode list
match ch {
'f' = > file_picker ( cx ) ,
'b' = > buffer_picker ( cx ) ,
2021-06-12 16:45:21 +04:00
's' = > symbol_picker ( cx ) ,
2021-06-06 09:09:21 +04:00
'w' = > window_mode ( cx ) ,
2021-06-19 10:27:43 +04:00
'y' = > yank_joined_to_clipboard ( cx ) ,
'Y' = > yank_main_selection_to_clipboard ( cx ) ,
'p' = > paste_clipboard_after ( cx ) ,
'P' = > paste_clipboard_before ( cx ) ,
'R' = > replace_selections_with_clipboard ( cx ) ,
2021-03-30 13:38:26 +04:00
// ' ' => toggle_alternate_buffer(cx),
2021-06-05 06:21:31 +04:00
// TODO: temporary since space mode took its old key
2021-04-09 19:21:54 +04:00
' ' = > keep_primary_selection ( cx ) ,
2021-03-30 13:38:26 +04:00
_ = > ( ) ,
}
}
} )
}
2021-04-14 12:15:11 +04:00
2021-06-17 15:08:05 +04:00
fn view_mode ( cx : & mut Context ) {
2021-04-14 12:15:11 +04:00
cx . on_next_key ( move | cx , event | {
if let KeyEvent {
code : KeyCode ::Char ( ch ) ,
..
} = event
{
// if lock, call cx again
// TODO: temporarily show VIE in the mode list
match ch {
// center
'z' | 'c'
// top
| 't'
// bottom
| 'b' = > {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-04-14 12:15:11 +04:00
2021-05-08 10:36:27 +04:00
align_view ( doc , view , match ch {
'z' | 'c' = > Align ::Center ,
't' = > Align ::Top ,
'b' = > Align ::Bottom ,
2021-04-14 12:15:11 +04:00
_ = > unreachable! ( )
2021-05-08 10:36:27 +04:00
} ) ;
2021-04-14 12:15:11 +04:00
}
'm' = > {
2021-06-18 02:09:10 +04:00
let ( view , doc ) = current! ( cx . editor ) ;
2021-04-14 12:15:11 +04:00
let pos = doc . selection ( view . id ) . cursor ( ) ;
let pos = coords_at_pos ( doc . text ( ) . slice ( .. ) , pos ) ;
const OFFSET : usize = 7 ; // gutters
2021-06-07 04:29:21 +04:00
view . first_col = pos . col . saturating_sub ( ( ( view . area . width as usize ) . saturating_sub ( OFFSET ) ) / 2 ) ;
2021-04-14 12:15:11 +04:00
} ,
'h' = > ( ) ,
'j' = > scroll ( cx , 1 , Direction ::Forward ) ,
'k' = > scroll ( cx , 1 , Direction ::Backward ) ,
'l' = > ( ) ,
_ = > ( ) ,
}
}
} )
}
2021-06-06 13:59:32 +04:00
2021-06-17 15:08:05 +04:00
fn left_bracket_mode ( cx : & mut Context ) {
2021-06-06 13:59:32 +04:00
cx . on_next_key ( move | cx , event | {
if let KeyEvent {
code : KeyCode ::Char ( ch ) ,
..
} = event
{
match ch {
'd' = > goto_prev_diag ( cx ) ,
'D' = > goto_first_diag ( cx ) ,
_ = > ( ) ,
}
}
} )
}
2021-06-17 15:08:05 +04:00
fn right_bracket_mode ( cx : & mut Context ) {
2021-06-06 13:59:32 +04:00
cx . on_next_key ( move | cx , event | {
if let KeyEvent {
code : KeyCode ::Char ( ch ) ,
..
} = event
{
match ch {
'd' = > goto_next_diag ( cx ) ,
'D' = > goto_last_diag ( cx ) ,
_ = > ( ) ,
}
}
} )
}
2021-06-19 18:59:19 +04:00
impl fmt ::Display for Command {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
let Command ( name , _ ) = self ;
f . write_str ( name )
}
}
impl std ::str ::FromStr for Command {
type Err = anyhow ::Error ;
fn from_str ( s : & str ) -> Result < Self , Self ::Err > {
Command ::COMMAND_LIST
. iter ( )
. copied ( )
. find ( | cmd | cmd . 0 = = s )
. ok_or_else ( | | anyhow! ( " No command named '{}' " , s ) )
}
}
impl fmt ::Debug for Command {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
let Command ( name , _ ) = self ;
f . debug_tuple ( " Command " ) . field ( name ) . finish ( )
}
}