2022-03-25 05:05:20 -04:00
use arc_swap ::{ access ::Map , ArcSwap } ;
2022-10-16 18:01:31 +09:00
use futures_util ::Stream ;
2022-02-14 22:11:53 +05:30
use helix_core ::{
2022-09-20 00:21:15 -07:00
diagnostic ::{ DiagnosticTag , NumberOrString } ,
2022-04-24 15:33:43 -04:00
path ::get_relative_path ,
2022-02-14 22:11:53 +05:30
pos_at_coords , syntax , Selection ,
} ;
2021-07-01 10:41:20 -07:00
use helix_lsp ::{ lsp , util ::lsp_pos_to_pos , LspProgressMap } ;
2022-04-10 11:05:47 -04:00
use helix_view ::{
align_view ,
2022-08-30 23:08:15 -04:00
document ::DocumentSavedEventResult ,
2022-04-10 11:05:47 -04:00
editor ::{ ConfigEvent , EditorEvent } ,
2022-03-29 10:11:44 +09:00
graphics ::Rect ,
2022-04-10 11:05:47 -04:00
theme ,
tree ::Layout ,
Align , Editor ,
} ;
2021-12-21 10:21:45 +01:00
use serde_json ::json ;
2022-03-29 10:11:44 +09:00
use tui ::backend ::Backend ;
2021-07-01 10:41:20 -07:00
2021-09-05 16:09:38 +03:00
use crate ::{
2022-01-23 15:54:03 +08:00
args ::Args ,
2022-03-22 12:53:44 +09:00
commands ::apply_workspace_edit ,
2022-08-09 07:01:26 +05:30
compositor ::{ Compositor , Event } ,
2022-01-23 15:54:03 +08:00
config ::Config ,
job ::Jobs ,
2022-03-25 05:05:20 -04:00
keymap ::Keymaps ,
Add `PageUp`, `PageDown`, `Ctrl-u`, `Ctrl-d`, `Home`, `End` keyboard shortcuts to file picker (#1612)
* Add `PageUp`, `PageDown`, `Ctrl-u`, `Ctrl-d`, `Home`, `End` keyboard shortcuts to file picker
* Refactor file picker paging logic
* change key mapping
* Add overlay component
* Use closure instead of margin to calculate size
* Don't wrap file picker in `Overlay` automatically
2022-02-15 02:24:03 +01:00
ui ::{ self , overlay ::overlayed } ,
2021-09-05 16:09:38 +03:00
} ;
2020-12-06 11:53:58 +09:00
2022-04-10 11:05:47 -04:00
use log ::{ debug , error , warn } ;
2020-09-11 14:14:44 +09:00
use std ::{
2023-03-05 12:06:12 -06:00
io ::{ stdin , stdout } ,
2023-03-10 01:50:43 +11:00
path ::Path ,
2020-12-23 15:50:16 +09:00
sync ::Arc ,
2021-07-04 00:19:59 +08:00
time ::{ Duration , Instant } ,
2020-09-11 14:14:44 +09:00
} ;
2022-04-26 19:18:20 -04:00
use anyhow ::{ Context , Error } ;
2020-09-11 14:14:44 +09:00
2023-03-05 12:06:12 -06:00
use crossterm ::{ event ::Event as CrosstermEvent , tty ::IsTty } ;
2021-07-17 22:47:08 +08:00
#[ cfg(not(windows)) ]
2021-07-27 01:18:41 +09:00
use {
signal_hook ::{ consts ::signal , low_level } ,
signal_hook_tokio ::Signals ,
} ;
#[ cfg(windows) ]
type Signals = futures_util ::stream ::Empty < ( ) > ;
2020-09-08 14:32:20 +09:00
2022-04-23 21:49:31 -04:00
const LSP_DEADLINE : Duration = Duration ::from_millis ( 16 ) ;
2022-03-29 10:11:44 +09:00
#[ cfg(not(feature = " integration " )) ]
use tui ::backend ::CrosstermBackend ;
2022-11-09 22:17:14 +09:00
#[ cfg(feature = " integration " ) ]
use tui ::backend ::TestBackend ;
2022-03-29 10:11:44 +09:00
#[ cfg(not(feature = " integration " )) ]
2023-03-05 12:06:12 -06:00
type TerminalBackend = CrosstermBackend < std ::io ::Stdout > ;
2022-03-29 10:11:44 +09:00
#[ cfg(feature = " integration " ) ]
2023-03-05 12:06:12 -06:00
type TerminalBackend = TestBackend ;
type Terminal = tui ::terminal ::Terminal < TerminalBackend > ;
2022-03-29 10:11:44 +09:00
2020-12-13 12:23:50 +09:00
pub struct Application {
2020-12-06 11:53:58 +09:00
compositor : Compositor ,
2022-03-29 10:11:44 +09:00
terminal : Terminal ,
2021-10-26 18:03:03 +09:00
pub editor : Editor ,
2020-12-06 11:53:58 +09:00
2022-03-25 05:05:20 -04:00
config : Arc < ArcSwap < Config > > ,
2021-06-21 05:44:50 +02:00
2021-07-01 11:57:12 -07:00
#[ allow(dead_code) ]
2021-06-19 13:26:52 +02:00
theme_loader : Arc < theme ::Loader > ,
2021-07-01 11:57:12 -07:00
#[ allow(dead_code) ]
2021-06-19 13:26:52 +02:00
syn_loader : Arc < syntax ::Loader > ,
2021-06-18 05:43:24 +02:00
2021-07-17 22:47:08 +08:00
signals : Signals ,
2021-06-28 07:48:38 -05:00
jobs : Jobs ,
2021-06-18 05:43:24 +02:00
lsp_progress : LspProgressMap ,
2022-04-23 21:49:31 -04:00
last_render : Instant ,
2020-10-16 12:29:22 +09:00
}
2022-03-16 23:34:21 -04:00
#[ cfg(feature = " integration " ) ]
fn setup_integration_logging ( ) {
2022-04-27 00:42:18 -04:00
let level = std ::env ::var ( " HELIX_LOG_LEVEL " )
. map ( | lvl | lvl . parse ( ) . unwrap ( ) )
. unwrap_or ( log ::LevelFilter ::Info ) ;
2022-03-16 23:34:21 -04:00
// Separate file config so we can include year, month and day in file logs
let _ = fern ::Dispatch ::new ( )
. format ( | out , message , record | {
out . finish ( format_args! (
" {} {} [{}] {} " ,
chrono ::Local ::now ( ) . format ( " %Y-%m-%dT%H:%M:%S%.3f " ) ,
record . target ( ) ,
record . level ( ) ,
message
) )
} )
2022-04-27 00:42:18 -04:00
. level ( level )
2022-03-16 23:34:21 -04:00
. chain ( std ::io ::stdout ( ) )
. apply ( ) ;
}
2020-12-13 12:23:50 +09:00
impl Application {
2022-09-16 23:17:48 -04:00
pub fn new (
args : Args ,
config : Config ,
syn_loader_conf : syntax ::Configuration ,
) -> Result < Self , Error > {
2022-03-16 23:34:21 -04:00
#[ cfg(feature = " integration " ) ]
setup_integration_logging ( ) ;
2021-04-06 19:02:22 +09:00
use helix_view ::editor ::Action ;
2021-06-19 13:26:52 +02:00
2023-03-10 01:50:43 +11:00
let mut theme_parent_dirs = vec! [ helix_loader ::config_dir ( ) ] ;
theme_parent_dirs . extend ( helix_loader ::runtime_dirs ( ) . iter ( ) . cloned ( ) ) ;
let theme_loader = std ::sync ::Arc ::new ( theme ::Loader ::new ( & theme_parent_dirs ) ) ;
2021-06-19 13:26:52 +02:00
2021-11-14 07:26:48 -05:00
let true_color = config . editor . true_color | | crate ::true_color ( ) ;
let theme = config
. theme
. as_ref ( )
. and_then ( | theme | {
theme_loader
. load ( theme )
. map_err ( | e | {
log ::warn! ( " failed to load theme `{}` - {} " , theme , e ) ;
e
} )
. ok ( )
. filter ( | theme | ( true_color | | theme . is_16_color ( ) ) )
} )
2022-07-05 06:44:16 -04:00
. unwrap_or_else ( | | theme_loader . default_theme ( true_color ) ) ;
2021-06-19 13:26:52 +02:00
let syn_loader = std ::sync ::Arc ::new ( syntax ::Loader ::new ( syn_loader_conf ) ) ;
2022-03-29 10:11:44 +09:00
#[ cfg(not(feature = " integration " )) ]
let backend = CrosstermBackend ::new ( stdout ( ) ) ;
#[ cfg(feature = " integration " ) ]
let backend = TestBackend ::new ( 120 , 150 ) ;
let terminal = Terminal ::new ( backend ) ? ;
let area = terminal . size ( ) . expect ( " couldn't get terminal size " ) ;
let mut compositor = Compositor ::new ( area ) ;
2022-03-25 05:05:20 -04:00
let config = Arc ::new ( ArcSwap ::from_pointee ( config ) ) ;
2021-08-08 14:07:14 +09:00
let mut editor = Editor ::new (
2022-03-29 10:11:44 +09:00
area ,
2021-08-08 14:07:14 +09:00
theme_loader . clone ( ) ,
syn_loader . clone ( ) ,
2023-01-31 18:03:19 +01:00
Arc ::new ( Map ::new ( Arc ::clone ( & config ) , | config : & Config | {
2022-03-25 05:05:20 -04:00
& config . editor
} ) ) ,
2021-08-08 14:07:14 +09:00
) ;
2020-12-06 11:53:58 +09:00
2022-03-25 05:05:20 -04:00
let keys = Box ::new ( Map ::new ( Arc ::clone ( & config ) , | config : & Config | {
& config . keys
} ) ) ;
let editor_view = Box ::new ( ui ::EditorView ::new ( Keymaps ::new ( keys ) ) ) ;
2021-06-17 13:08:05 +02:00
compositor . push ( editor_view ) ;
2021-06-02 16:20:41 +02:00
2021-10-27 21:23:46 -04:00
if args . load_tutor {
2023-03-10 01:50:43 +11:00
let path = helix_loader ::runtime_file ( Path ::new ( " tutor " ) ) ;
2022-04-26 19:18:20 -04:00
editor . open ( & path , Action ::VerticalSplit ) ? ;
2021-10-27 21:23:46 -04:00
// Unset path to prevent accidentally saving to the original tutor file.
doc_mut! ( editor ) . set_path ( None ) ? ;
} else if ! args . files . is_empty ( ) {
2022-01-23 15:54:03 +08:00
let first = & args . files [ 0 ] . 0 ; // we know it's not empty
2021-06-02 16:20:41 +02:00
if first . is_dir ( ) {
2022-11-04 21:01:17 +09:00
std ::env ::set_current_dir ( first ) . context ( " set current dir " ) ? ;
2021-06-02 16:20:41 +02:00
editor . new_file ( Action ::VerticalSplit ) ;
2022-03-25 05:05:20 -04:00
let picker = ui ::file_picker ( " . " . into ( ) , & config . load ( ) . editor ) ;
Add `PageUp`, `PageDown`, `Ctrl-u`, `Ctrl-d`, `Home`, `End` keyboard shortcuts to file picker (#1612)
* Add `PageUp`, `PageDown`, `Ctrl-u`, `Ctrl-d`, `Home`, `End` keyboard shortcuts to file picker
* Refactor file picker paging logic
* change key mapping
* Add overlay component
* Use closure instead of margin to calculate size
* Don't wrap file picker in `Overlay` automatically
2022-02-15 02:24:03 +01:00
compositor . push ( Box ::new ( overlayed ( picker ) ) ) ;
2021-06-02 16:20:41 +02:00
} else {
2021-07-14 21:29:39 +02:00
let nr_of_files = args . files . len ( ) ;
2022-07-18 10:14:36 +09:00
for ( i , ( file , pos ) ) in args . files . into_iter ( ) . enumerate ( ) {
2021-06-02 16:20:41 +02:00
if file . is_dir ( ) {
return Err ( anyhow ::anyhow! (
" expected a path to file, found a directory. (to open a directory pass it as first argument) "
) ) ;
} else {
2022-07-01 11:27:32 +02:00
// If the user passes in either `--vsplit` or
// `--hsplit` as a command line argument, all the given
// files will be opened according to the selected
// option. If neither of those two arguments are passed
// in, just load the files normally.
let action = match args . split {
2022-07-18 10:14:36 +09:00
_ if i = = 0 = > Action ::VerticalSplit ,
2022-07-01 11:27:32 +02:00
Some ( Layout ::Vertical ) = > Action ::VerticalSplit ,
Some ( Layout ::Horizontal ) = > Action ::HorizontalSplit ,
None = > Action ::Load ,
} ;
2022-04-26 19:18:20 -04:00
let doc_id = editor
2022-07-01 11:27:32 +02:00
. open ( & file , action )
2022-04-26 19:18:20 -04:00
. context ( format! ( " open ' {} ' " , file . to_string_lossy ( ) ) ) ? ;
2022-01-23 15:54:03 +08:00
// with Action::Load all documents have the same view
2022-07-01 11:27:32 +02:00
// NOTE: this isn't necessarily true anymore. If
// `--vsplit` or `--hsplit` are used, the file which is
// opened last is focused on.
2022-01-23 15:54:03 +08:00
let view_id = editor . tree . focus ;
2022-09-04 17:27:15 +09:00
let doc = doc_mut! ( editor , & doc_id ) ;
2022-01-23 15:54:03 +08:00
let pos = Selection ::point ( pos_at_coords ( doc . text ( ) . slice ( .. ) , pos , true ) ) ;
doc . set_selection ( view_id , pos ) ;
2021-06-02 16:20:41 +02:00
}
}
2022-12-23 19:43:05 +05:30
editor . set_status ( format! (
" Loaded {} file{}. " ,
nr_of_files ,
if nr_of_files = = 1 { " " } else { " s " } // avoid "Loaded 1 files." grammo
) ) ;
2022-01-23 15:54:03 +08:00
// align the view to center after all files are loaded,
// does not affect views without pos since it is at the top
let ( view , doc ) = current! ( editor ) ;
align_view ( doc , view , Align ::Center ) ;
2021-04-06 19:02:22 +09:00
}
2022-04-27 00:48:17 -04:00
} else if stdin ( ) . is_tty ( ) | | cfg! ( feature = " integration " ) {
2021-05-07 14:40:11 +09:00
editor . new_file ( Action ::VerticalSplit ) ;
2021-11-09 18:53:14 -07:00
} else if cfg! ( target_os = " macos " ) {
// On Linux and Windows, we allow the output of a command to be piped into the new buffer.
// This doesn't currently work on macOS because of the following issue:
// https://github.com/crossterm-rs/crossterm/issues/500
anyhow ::bail! ( " Piping into helix-term is currently not supported on macOS " ) ;
} else {
editor
. new_file_from_stdin ( Action ::VerticalSplit )
. unwrap_or_else ( | _ | editor . new_file ( Action ::VerticalSplit ) ) ;
2020-12-06 11:53:58 +09:00
}
2021-06-19 13:26:52 +02:00
editor . set_theme ( theme ) ;
2021-07-27 01:18:41 +09:00
#[ cfg(windows) ]
let signals = futures_util ::stream ::empty ( ) ;
2021-07-17 22:47:08 +08:00
#[ cfg(not(windows)) ]
2022-11-04 21:01:17 +09:00
let signals = Signals ::new ( [ signal ::SIGTSTP , signal ::SIGCONT , signal ::SIGUSR1 ] )
2022-10-03 17:08:32 +02:00
. context ( " build signal handler " ) ? ;
2021-07-17 22:47:08 +08:00
2021-07-01 11:57:12 -07:00
let app = Self {
2020-12-06 11:53:58 +09:00
compositor ,
2022-03-29 10:11:44 +09:00
terminal ,
2021-03-22 12:26:04 +09:00
editor ,
2020-12-06 11:53:58 +09:00
2021-06-21 05:44:50 +02:00
config ,
2021-06-19 13:26:52 +02:00
theme_loader ,
syn_loader ,
2021-06-21 05:44:50 +02:00
2021-07-17 22:47:08 +08:00
signals ,
2021-06-28 07:48:38 -05:00
jobs : Jobs ::new ( ) ,
2021-06-18 05:43:24 +02:00
lsp_progress : LspProgressMap ::new ( ) ,
2022-04-23 21:49:31 -04:00
last_render : Instant ::now ( ) ,
2020-12-06 11:53:58 +09:00
} ;
Ok ( app )
}
2022-12-01 09:35:23 +01:00
async fn render ( & mut self ) {
2022-04-27 00:07:59 -04:00
let mut cx = crate ::compositor ::Context {
editor : & mut self . editor ,
jobs : & mut self . jobs ,
scroll : None ,
} ;
2022-01-17 19:04:40 -05:00
2022-12-01 09:35:23 +01:00
// Acquire mutable access to the redraw_handle lock
// to ensure that there are no tasks running that want to block rendering
drop ( cx . editor . redraw_handle . 1. write ( ) . await ) ;
cx . editor . needs_redraw = false ;
{
// exhaust any leftover redraw notifications
let notify = cx . editor . redraw_handle . 0. notified ( ) ;
tokio ::pin! ( notify ) ;
notify . enable ( ) ;
}
2022-03-29 10:11:44 +09:00
let area = self
. terminal
. autoresize ( )
. expect ( " Unable to determine terminal size " ) ;
// TODO: need to recalculate view tree if necessary
let surface = self . terminal . current_buffer_mut ( ) ;
self . compositor . render ( area , surface , & mut cx ) ;
let ( pos , kind ) = self . compositor . cursor ( area , & self . editor ) ;
2023-01-31 18:03:19 +01:00
// reset cursor cache
self . editor . cursor_cache . set ( None ) ;
2022-03-29 10:11:44 +09:00
let pos = pos . map ( | pos | ( pos . col as u16 , pos . row as u16 ) ) ;
self . terminal . draw ( pos , kind ) . unwrap ( ) ;
2020-12-06 11:53:58 +09:00
}
2022-04-19 01:21:31 -04:00
pub async fn event_loop < S > ( & mut self , input_stream : & mut S )
where
S : Stream < Item = crossterm ::Result < crossterm ::event ::Event > > + Unpin ,
{
2022-12-01 09:35:23 +01:00
self . render ( ) . await ;
2022-04-23 21:49:31 -04:00
self . last_render = Instant ::now ( ) ;
2020-12-06 11:53:58 +09:00
loop {
2022-06-10 23:35:34 -04:00
if ! self . event_loop_until_idle ( input_stream ) . await {
2020-12-11 18:25:09 +09:00
break ;
}
2022-04-23 21:49:31 -04:00
}
}
pub async fn event_loop_until_idle < S > ( & mut self , input_stream : & mut S ) -> bool
where
S : Stream < Item = crossterm ::Result < crossterm ::event ::Event > > + Unpin ,
{
loop {
if self . editor . should_close ( ) {
return false ;
}
2021-07-04 00:19:59 +08:00
use futures_util ::StreamExt ;
2021-05-06 14:33:53 +09:00
2021-07-17 22:47:08 +08:00
tokio ::select! {
biased ;
Some ( signal ) = self . signals . next ( ) = > {
2021-07-27 01:18:41 +09:00
self . handle_signals ( signal ) . await ;
2021-07-17 22:47:08 +08:00
}
2023-03-05 02:52:20 +01:00
Some ( event ) = input_stream . next ( ) = > {
self . handle_terminal_events ( event ) . await ;
}
2021-07-17 22:47:08 +08:00
Some ( callback ) = self . jobs . futures . next ( ) = > {
self . jobs . handle_callback ( & mut self . editor , & mut self . compositor , callback ) ;
2022-12-01 09:35:23 +01:00
self . render ( ) . await ;
2021-07-17 22:47:08 +08:00
}
Some ( callback ) = self . jobs . wait_futures . next ( ) = > {
self . jobs . handle_callback ( & mut self . editor , & mut self . compositor , callback ) ;
2022-12-01 09:35:23 +01:00
self . render ( ) . await ;
2021-07-17 22:47:08 +08:00
}
2022-04-10 11:05:47 -04:00
event = self . editor . wait_event ( ) = > {
2022-07-22 01:07:31 -04:00
let _idle_handled = self . handle_editor_event ( event ) . await ;
#[ cfg(feature = " integration " ) ]
{
if _idle_handled {
return true ;
2022-04-10 11:05:47 -04:00
}
2022-07-22 01:07:31 -04:00
}
2021-08-22 15:28:45 +09:00
}
2021-07-17 22:47:08 +08:00
}
2022-08-09 23:32:01 -04:00
// for integration tests only, reset the idle timer after every
// event to signal when test events are done processing
#[ cfg(feature = " integration " ) ]
{
self . editor . reset_idle_timer ( ) ;
}
2021-07-27 01:18:41 +09:00
}
}
2021-07-04 00:19:59 +08:00
2022-03-25 05:05:20 -04:00
pub fn handle_config_events ( & mut self , config_event : ConfigEvent ) {
match config_event {
ConfigEvent ::Refresh = > self . refresh_config ( ) ,
// Since only the Application can make changes to Editor's config,
// the Editor must send up a new copy of a modified config so that
// the Application can apply it.
ConfigEvent ::Update ( editor_config ) = > {
let mut app_config = ( * self . config . load ( ) . clone ( ) ) . clone ( ) ;
2021-12-01 17:59:23 -05:00
app_config . editor = * editor_config ;
2022-03-25 05:05:20 -04:00
self . config . store ( Arc ::new ( app_config ) ) ;
}
}
2022-06-15 19:29:58 +02:00
// Update all the relevant members in the editor after updating
// the configuration.
self . editor . refresh_config ( ) ;
2023-01-31 18:03:19 +01:00
// reset view position in case softwrap was enabled/disabled
let scrolloff = self . editor . config ( ) . scrolloff ;
for ( view , _ ) in self . editor . tree . views_mut ( ) {
let doc = & self . editor . documents [ & view . doc ] ;
view . ensure_cursor_in_view ( doc , scrolloff )
}
2022-03-25 05:05:20 -04:00
}
2022-12-29 09:51:23 -06:00
/// refresh language config after config change
fn refresh_language_config ( & mut self ) -> Result < ( ) , Error > {
let syntax_config = helix_core ::config ::user_syntax_loader ( )
. map_err ( | err | anyhow ::anyhow! ( " Failed to load language config: {} " , err ) ) ? ;
self . syn_loader = std ::sync ::Arc ::new ( syntax ::Loader ::new ( syntax_config ) ) ;
2023-01-03 21:24:48 +08:00
self . editor . syn_loader = self . syn_loader . clone ( ) ;
2022-12-29 09:51:23 -06:00
for document in self . editor . documents . values_mut ( ) {
document . detect_language ( self . syn_loader . clone ( ) ) ;
}
Ok ( ( ) )
}
2022-09-10 16:32:49 +03:00
/// Refresh theme after config change
2022-12-29 09:51:23 -06:00
fn refresh_theme ( & mut self , config : & Config ) -> Result < ( ) , Error > {
2022-03-25 05:05:20 -04:00
if let Some ( theme ) = config . theme . clone ( ) {
let true_color = self . true_color ( ) ;
2022-12-29 09:51:23 -06:00
let theme = self
. theme_loader
. load ( & theme )
. map_err ( | err | anyhow ::anyhow! ( " Failed to load theme `{}`: {} " , theme , err ) ) ? ;
if true_color | | theme . is_16_color ( ) {
self . editor . set_theme ( theme ) ;
} else {
anyhow ::bail! ( " theme requires truecolor support, which is not available " )
2022-09-10 16:32:49 +03:00
}
2022-03-25 05:05:20 -04:00
}
2022-12-29 09:51:23 -06:00
Ok ( ( ) )
2022-09-10 16:32:49 +03:00
}
2022-06-15 19:29:58 +02:00
2022-09-10 16:32:49 +03:00
fn refresh_config ( & mut self ) {
2022-12-29 09:51:23 -06:00
let mut refresh_config = | | -> Result < ( ) , Error > {
let default_config = Config ::load_default ( )
. map_err ( | err | anyhow ::anyhow! ( " Failed to load config: {} " , err ) ) ? ;
self . refresh_language_config ( ) ? ;
self . refresh_theme ( & default_config ) ? ;
2023-01-08 16:30:38 +01:00
// Store new config
self . config . store ( Arc ::new ( default_config ) ) ;
2022-12-29 09:51:23 -06:00
Ok ( ( ) )
} ;
2022-09-10 16:32:49 +03:00
2023-01-03 21:24:48 +08:00
match refresh_config ( ) {
Ok ( _ ) = > {
self . editor . set_status ( " Config refreshed " ) ;
}
Err ( err ) = > {
self . editor . set_error ( err . to_string ( ) ) ;
}
2022-09-10 16:32:49 +03:00
}
2022-03-25 05:05:20 -04:00
}
fn true_color ( & self ) -> bool {
self . config . load ( ) . editor . true_color | | crate ::true_color ( )
}
2021-07-27 01:18:41 +09:00
#[ cfg(windows) ]
// no signal handling available on windows
pub async fn handle_signals ( & mut self , _signal : ( ) ) { }
#[ cfg(not(windows)) ]
pub async fn handle_signals ( & mut self , signal : i32 ) {
match signal {
signal ::SIGTSTP = > {
2023-03-05 12:06:12 -06:00
self . restore_term ( ) . unwrap ( ) ;
2021-07-27 01:18:41 +09:00
low_level ::emulate_default_handler ( signal ::SIGTSTP ) . unwrap ( ) ;
}
signal ::SIGCONT = > {
self . claim_term ( ) . await . unwrap ( ) ;
// redraw the terminal
2022-03-29 10:11:44 +09:00
let area = self . terminal . size ( ) . expect ( " couldn't get terminal size " ) ;
self . compositor . resize ( area ) ;
self . terminal . clear ( ) . expect ( " couldn't clear terminal " ) ;
2022-12-01 09:35:23 +01:00
self . render ( ) . await ;
2020-12-06 11:53:58 +09:00
}
2022-10-03 17:08:32 +02:00
signal ::SIGUSR1 = > {
self . refresh_config ( ) ;
2022-12-01 09:35:23 +01:00
self . render ( ) . await ;
2022-10-03 17:08:32 +02:00
}
2021-07-27 01:18:41 +09:00
_ = > unreachable! ( ) ,
2020-12-06 11:53:58 +09:00
}
}
2022-12-01 09:35:23 +01:00
pub async fn handle_idle_timeout ( & mut self ) {
2022-03-02 16:10:35 +09:00
let mut cx = crate ::compositor ::Context {
2021-08-26 11:14:46 +09:00
editor : & mut self . editor ,
jobs : & mut self . jobs ,
2022-03-02 16:10:35 +09:00
scroll : None ,
2021-08-26 11:14:46 +09:00
} ;
2022-10-11 05:40:01 +05:30
let should_render = self . compositor . handle_event ( & Event ::IdleTimeout , & mut cx ) ;
2022-12-01 09:35:23 +01:00
if should_render | | self . editor . needs_redraw {
self . render ( ) . await ;
2022-03-02 16:10:35 +09:00
}
2021-08-26 11:14:46 +09:00
}
2020-12-06 11:53:58 +09:00
2022-08-30 23:08:15 -04:00
pub fn handle_document_write ( & mut self , doc_save_event : DocumentSavedEventResult ) {
2022-09-21 18:34:56 -04:00
let doc_save_event = match doc_save_event {
Ok ( event ) = > event ,
Err ( err ) = > {
self . editor . set_error ( err . to_string ( ) ) ;
return ;
}
} ;
2022-04-10 11:05:47 -04:00
2022-09-21 18:34:56 -04:00
let doc = match self . editor . document_mut ( doc_save_event . doc_id ) {
None = > {
warn! (
" received document saved event for non-existent doc id: {} " ,
doc_save_event . doc_id
) ;
2022-04-10 11:05:47 -04:00
2022-09-21 18:34:56 -04:00
return ;
}
Some ( doc ) = > doc ,
} ;
2022-04-10 11:05:47 -04:00
debug! (
" document {:?} saved with revision {} " ,
doc . path ( ) ,
doc_save_event . revision
) ;
doc . set_last_saved_revision ( doc_save_event . revision ) ;
2022-04-24 15:33:43 -04:00
2022-08-30 23:10:33 -04:00
let lines = doc_save_event . text . len_lines ( ) ;
let bytes = doc_save_event . text . len_bytes ( ) ;
2022-04-10 11:05:47 -04:00
2022-09-02 09:20:28 -04:00
if doc . path ( ) ! = Some ( & doc_save_event . path ) {
if let Err ( err ) = doc . set_path ( Some ( & doc_save_event . path ) ) {
log ::error! (
" error setting path for doc '{:?}': {} " ,
doc . path ( ) ,
err . to_string ( ) ,
) ;
self . editor . set_error ( err . to_string ( ) ) ;
return ;
}
let loader = self . editor . syn_loader . clone ( ) ;
// borrowing the same doc again to get around the borrow checker
2022-09-21 18:34:56 -04:00
let doc = doc_mut! ( self . editor , & doc_save_event . doc_id ) ;
2022-09-02 09:20:28 -04:00
let id = doc . id ( ) ;
doc . detect_language ( loader ) ;
let _ = self . editor . refresh_language_server ( id ) ;
2022-04-24 15:33:43 -04:00
}
2022-09-02 09:20:28 -04:00
// TODO: fix being overwritten by lsp
self . editor . set_status ( format! (
" '{}' written, {}L {}B " ,
get_relative_path ( & doc_save_event . path ) . to_string_lossy ( ) ,
lines ,
bytes
) ) ;
2022-04-10 11:05:47 -04:00
}
2022-07-22 01:07:31 -04:00
#[ inline(always) ]
pub async fn handle_editor_event ( & mut self , event : EditorEvent ) -> bool {
log ::debug! ( " received editor event: {:?} " , event ) ;
match event {
2022-08-30 23:08:15 -04:00
EditorEvent ::DocumentSaved ( event ) = > {
2022-07-22 01:07:31 -04:00
self . handle_document_write ( event ) ;
2022-12-01 09:35:23 +01:00
self . render ( ) . await ;
2022-07-22 01:07:31 -04:00
}
EditorEvent ::ConfigEvent ( event ) = > {
self . handle_config_events ( event ) ;
2022-12-01 09:35:23 +01:00
self . render ( ) . await ;
2022-07-22 01:07:31 -04:00
}
EditorEvent ::LanguageServerMessage ( ( id , call ) ) = > {
self . handle_language_server_message ( call , id ) . await ;
// limit render calls for fast language server messages
let last = self . editor . language_servers . incoming . is_empty ( ) ;
if last | | self . last_render . elapsed ( ) > LSP_DEADLINE {
2022-12-01 09:35:23 +01:00
self . render ( ) . await ;
2022-07-22 01:07:31 -04:00
self . last_render = Instant ::now ( ) ;
}
}
EditorEvent ::DebuggerEvent ( payload ) = > {
let needs_render = self . editor . handle_debugger_message ( payload ) . await ;
if needs_render {
2022-12-01 09:35:23 +01:00
self . render ( ) . await ;
2022-07-22 01:07:31 -04:00
}
}
EditorEvent ::IdleTimer = > {
self . editor . clear_idle_timer ( ) ;
2022-12-01 09:35:23 +01:00
self . handle_idle_timeout ( ) . await ;
2022-07-22 01:07:31 -04:00
#[ cfg(feature = " integration " ) ]
{
return true ;
}
}
}
false
}
2022-12-01 09:35:23 +01:00
pub async fn handle_terminal_events (
& mut self ,
event : Result < CrosstermEvent , crossterm ::ErrorKind > ,
) {
2020-12-11 18:25:09 +09:00
let mut cx = crate ::compositor ::Context {
editor : & mut self . editor ,
2021-06-28 07:48:38 -05:00
jobs : & mut self . jobs ,
2021-03-08 17:32:13 +09:00
scroll : None ,
2020-12-11 18:25:09 +09:00
} ;
2020-12-06 11:53:58 +09:00
// Handle key events
2022-08-29 00:48:49 +00:00
let should_redraw = match event . unwrap ( ) {
CrosstermEvent ::Resize ( width , height ) = > {
2022-03-29 10:11:44 +09:00
self . terminal
. resize ( Rect ::new ( 0 , 0 , width , height ) )
. expect ( " Unable to resize terminal " ) ;
let area = self . terminal . size ( ) . expect ( " couldn't get terminal size " ) ;
self . compositor . resize ( area ) ;
2020-12-06 11:53:58 +09:00
self . compositor
2022-08-29 00:48:49 +00:00
. handle_event ( & Event ::Resize ( width , height ) , & mut cx )
2020-12-06 11:53:58 +09:00
}
2023-02-28 19:26:02 -06:00
// Ignore keyboard release events.
CrosstermEvent ::Key ( crossterm ::event ::KeyEvent {
kind : crossterm ::event ::KeyEventKind ::Release ,
..
} ) = > false ,
2022-08-29 00:48:49 +00:00
event = > self . compositor . handle_event ( & event . into ( ) , & mut cx ) ,
2020-10-20 13:58:34 +09:00
} ;
2020-12-11 18:25:09 +09:00
2021-02-19 17:46:43 +09:00
if should_redraw & & ! self . editor . should_close ( ) {
2022-12-01 09:35:23 +01:00
self . render ( ) . await ;
2020-12-11 18:25:09 +09:00
}
2020-10-20 13:58:34 +09:00
}
2021-06-18 05:42:34 +02:00
pub async fn handle_language_server_message (
& mut self ,
call : helix_lsp ::Call ,
server_id : usize ,
) {
use helix_lsp ::{ Call , MethodCall , Notification } ;
2021-06-21 05:44:50 +02:00
2020-10-23 18:48:03 +09:00
match call {
2021-03-30 16:39:24 +09:00
Call ::Notification ( helix_lsp ::jsonrpc ::Notification { method , params , .. } ) = > {
2021-05-08 18:17:13 +09:00
let notification = match Notification ::parse ( & method , params ) {
2022-05-11 11:00:55 +09:00
Ok ( notification ) = > notification ,
Err ( err ) = > {
log ::error! (
" received malformed notification from Language Server: {} " ,
err
) ;
return ;
}
2021-05-08 18:17:13 +09:00
} ;
2020-10-23 18:48:03 +09:00
match notification {
2021-09-02 13:55:08 +09:00
Notification ::Initialized = > {
let language_server =
match self . editor . language_servers . get_by_id ( server_id ) {
Some ( language_server ) = > language_server ,
None = > {
warn! ( " can't find language server with id `{}` " , server_id ) ;
return ;
}
} ;
2020-12-22 16:58:00 +09:00
2022-02-28 09:57:22 +01:00
// Trigger a workspace/didChangeConfiguration notification after initialization.
// This might not be required by the spec but Neovim does this as well, so it's
// probably a good idea for compatibility.
if let Some ( config ) = language_server . config ( ) {
tokio ::spawn ( language_server . did_change_configuration ( config . clone ( ) ) ) ;
}
2021-09-02 13:55:08 +09:00
let docs = self . editor . documents ( ) . filter ( | doc | {
doc . language_server ( ) . map ( | server | server . id ( ) ) = = Some ( server_id )
} ) ;
2021-03-16 18:27:57 +09:00
2021-09-02 13:55:08 +09:00
// trigger textDocument/didOpen for docs that are already open
for doc in docs {
2022-09-04 17:28:17 +09:00
let url = match doc . url ( ) {
Some ( url ) = > url ,
None = > continue , // skip documents with no path
} ;
2022-10-15 18:39:55 +02:00
let language_id =
doc . language_id ( ) . map ( ToOwned ::to_owned ) . unwrap_or_default ( ) ;
2021-09-02 13:55:08 +09:00
tokio ::spawn ( language_server . text_document_did_open (
2022-09-04 17:28:17 +09:00
url ,
2021-09-02 13:55:08 +09:00
doc . version ( ) ,
doc . text ( ) ,
language_id ,
) ) ;
}
}
2022-06-30 11:16:18 +02:00
Notification ::PublishDiagnostics ( mut params ) = > {
2023-02-16 13:17:18 +08:00
let path = match params . uri . to_file_path ( ) {
Ok ( path ) = > path ,
Err ( _ ) = > {
log ::error! ( " Unsupported file URI: {} " , params . uri ) ;
return ;
}
} ;
2021-09-02 11:28:40 +09:00
let doc = self . editor . document_by_path_mut ( & path ) ;
2020-12-22 16:58:00 +09:00
2021-09-02 11:28:40 +09:00
if let Some ( doc ) = doc {
2021-12-25 06:32:43 +01:00
let lang_conf = doc . language_config ( ) ;
2021-03-16 18:27:57 +09:00
let text = doc . text ( ) ;
2020-10-23 18:48:03 +09:00
let diagnostics = params
. diagnostics
2022-06-30 11:16:18 +02:00
. iter ( )
2021-06-12 09:04:30 +02:00
. filter_map ( | diagnostic | {
2022-06-30 11:16:18 +02:00
use helix_core ::diagnostic ::{ Diagnostic , Range , Severity ::* } ;
2021-03-11 16:31:49 +09:00
use lsp ::DiagnosticSeverity ;
2021-04-14 15:30:15 +09:00
2022-08-31 03:47:18 -05:00
let language_server = if let Some ( language_server ) = doc . language_server ( ) {
language_server
} else {
log ::warn! ( " Discarding diagnostic because language server is not initialized: {:?} " , diagnostic ) ;
return None ;
} ;
2021-04-14 15:30:15 +09:00
// TODO: convert inside server
2021-06-12 09:04:30 +02:00
let start = if let Some ( start ) = lsp_pos_to_pos (
2021-04-14 15:30:15 +09:00
text ,
diagnostic . range . start ,
language_server . offset_encoding ( ) ,
2021-06-12 09:04:30 +02:00
) {
start
} else {
log ::warn! ( " lsp position out of bounds - {:?} " , diagnostic ) ;
return None ;
} ;
let end = if let Some ( end ) = lsp_pos_to_pos (
2021-04-14 15:30:15 +09:00
text ,
diagnostic . range . end ,
language_server . offset_encoding ( ) ,
2021-06-12 09:04:30 +02:00
) {
end
} else {
log ::warn! ( " lsp position out of bounds - {:?} " , diagnostic ) ;
return None ;
} ;
2020-10-23 18:48:03 +09:00
2021-12-25 06:32:43 +01:00
let severity =
diagnostic . severity . map ( | severity | match severity {
DiagnosticSeverity ::ERROR = > Error ,
DiagnosticSeverity ::WARNING = > Warning ,
DiagnosticSeverity ::INFORMATION = > Info ,
DiagnosticSeverity ::HINT = > Hint ,
severity = > unreachable! (
" unrecognized diagnostic severity: {:?} " ,
severity
) ,
} ) ;
if let Some ( lang_conf ) = lang_conf {
if let Some ( severity ) = severity {
if severity < lang_conf . diagnostic_severity {
return None ;
}
}
} ;
2022-07-26 09:26:50 +08:00
let code = match diagnostic . code . clone ( ) {
Some ( x ) = > match x {
lsp ::NumberOrString ::Number ( x ) = > {
Some ( NumberOrString ::Number ( x ) )
}
lsp ::NumberOrString ::String ( x ) = > {
Some ( NumberOrString ::String ( x ) )
}
} ,
None = > None ,
} ;
2022-09-20 00:21:15 -07:00
let tags = if let Some ( ref tags ) = diagnostic . tags {
let new_tags = tags . iter ( ) . filter_map ( | tag | {
match * tag {
lsp ::DiagnosticTag ::DEPRECATED = > Some ( DiagnosticTag ::Deprecated ) ,
lsp ::DiagnosticTag ::UNNECESSARY = > Some ( DiagnosticTag ::Unnecessary ) ,
_ = > None
}
} ) . collect ( ) ;
2022-09-20 16:28:00 +09:00
new_tags
2022-09-20 00:21:15 -07:00
} else {
2022-09-20 16:28:00 +09:00
Vec ::new ( )
2022-09-20 00:21:15 -07:00
} ;
2021-06-12 09:04:30 +02:00
Some ( Diagnostic {
2021-03-15 16:19:31 +09:00
range : Range { start , end } ,
2020-10-23 18:48:03 +09:00
line : diagnostic . range . start . line as usize ,
2022-06-30 11:16:18 +02:00
message : diagnostic . message . clone ( ) ,
2021-12-25 06:32:43 +01:00
severity ,
2022-07-26 09:26:50 +08:00
code ,
2022-09-20 00:21:15 -07:00
tags ,
2022-12-02 02:18:45 +01:00
source : diagnostic . source . clone ( ) ,
data : diagnostic . data . clone ( ) ,
2021-06-12 09:04:30 +02:00
} )
2020-10-23 18:48:03 +09:00
} )
. collect ( ) ;
2021-06-06 11:59:32 +02:00
doc . set_diagnostics ( diagnostics ) ;
2020-10-23 18:48:03 +09:00
}
2022-06-30 11:16:18 +02:00
2022-07-05 17:42:14 +05:30
// Sort diagnostics first by severity and then by line numbers.
2022-06-30 11:16:18 +02:00
// Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
2022-07-05 17:42:14 +05:30
params
. diagnostics
. sort_unstable_by_key ( | d | ( d . severity , d . range . start ) ) ;
2022-06-30 11:16:18 +02:00
// Insert the original lsp::Diagnostics here because we may have no open document
// for diagnosic message and so we can't calculate the exact position.
// When using them later in the diagnostics picker, we calculate them on-demand.
self . editor
. diagnostics
. insert ( params . uri , params . diagnostics ) ;
2020-10-23 18:48:03 +09:00
}
2021-04-15 17:34:38 +09:00
Notification ::ShowMessage ( params ) = > {
log ::warn! ( " unhandled window/showMessage: {:?} " , params ) ;
}
Notification ::LogMessage ( params ) = > {
2021-09-10 18:24:34 +09:00
log ::info! ( " window/logMessage: {:?} " , params ) ;
2021-04-15 17:34:38 +09:00
}
2021-10-15 16:36:39 +08:00
Notification ::ProgressMessage ( params )
if ! self
. compositor
. has_component ( std ::any ::type_name ::< ui ::Prompt > ( ) ) = >
{
let editor_view = self
. compositor
2021-11-18 11:08:47 +09:00
. find ::< ui ::EditorView > ( )
2021-10-15 16:36:39 +08:00
. expect ( " expected at least one EditorView " ) ;
2021-06-18 05:43:24 +02:00
let lsp ::ProgressParams { token , value } = params ;
let lsp ::ProgressParamsValue ::WorkDone ( work ) = value ;
let parts = match & work {
2021-06-18 05:43:44 +02:00
lsp ::WorkDoneProgress ::Begin ( lsp ::WorkDoneProgressBegin {
title ,
message ,
percentage ,
..
2021-06-18 05:43:24 +02:00
} ) = > ( Some ( title ) , message , percentage ) ,
2021-06-18 05:43:44 +02:00
lsp ::WorkDoneProgress ::Report ( lsp ::WorkDoneProgressReport {
message ,
percentage ,
..
2021-06-18 05:43:24 +02:00
} ) = > ( None , message , percentage ) ,
lsp ::WorkDoneProgress ::End ( lsp ::WorkDoneProgressEnd { message } ) = > {
if message . is_some ( ) {
( None , message , & None )
2021-06-18 05:43:44 +02:00
} else {
2021-06-18 05:43:24 +02:00
self . lsp_progress . end_progress ( server_id , & token ) ;
2021-06-20 21:31:45 +02:00
if ! self . lsp_progress . is_progressing ( server_id ) {
2021-06-21 05:44:50 +02:00
editor_view . spinners_mut ( ) . get_or_create ( server_id ) . stop ( ) ;
2021-06-20 21:31:45 +02:00
}
2021-06-18 05:43:44 +02:00
self . editor . clear_status ( ) ;
2021-06-21 10:14:50 +02:00
// we want to render to clear any leftover spinners or messages
2021-06-18 05:43:44 +02:00
return ;
2021-06-11 05:42:16 +02:00
}
2021-06-18 05:43:44 +02:00
}
} ;
2021-06-20 21:31:45 +02:00
2021-06-18 05:43:24 +02:00
let token_d : & dyn std ::fmt ::Display = match & token {
lsp ::NumberOrString ::Number ( n ) = > n ,
lsp ::NumberOrString ::String ( s ) = > s ,
} ;
let status = match parts {
2021-06-18 05:43:44 +02:00
( Some ( title ) , Some ( message ) , Some ( percentage ) ) = > {
2021-06-18 05:43:24 +02:00
format! ( " [ {} ] {} % {} - {} " , token_d , percentage , title , message )
2021-06-18 05:43:44 +02:00
}
( Some ( title ) , None , Some ( percentage ) ) = > {
2021-06-18 05:43:24 +02:00
format! ( " [ {} ] {} % {} " , token_d , percentage , title )
2021-06-18 05:43:44 +02:00
}
( Some ( title ) , Some ( message ) , None ) = > {
2021-06-18 05:43:24 +02:00
format! ( " [ {} ] {} - {} " , token_d , title , message )
2021-06-18 05:43:44 +02:00
}
( None , Some ( message ) , Some ( percentage ) ) = > {
2021-06-18 05:43:24 +02:00
format! ( " [ {} ] {} % {} " , token_d , percentage , message )
2021-06-18 05:43:44 +02:00
}
2021-06-18 05:43:24 +02:00
( Some ( title ) , None , None ) = > {
format! ( " [ {} ] {} " , token_d , title )
}
( None , Some ( message ) , None ) = > {
format! ( " [ {} ] {} " , token_d , message )
}
( None , None , Some ( percentage ) ) = > {
format! ( " [ {} ] {} % " , token_d , percentage )
2021-06-11 05:42:16 +02:00
}
2021-06-18 05:43:24 +02:00
( None , None , None ) = > format! ( " [ {} ] " , token_d ) ,
2021-06-11 05:42:16 +02:00
} ;
2021-06-18 05:43:24 +02:00
if let lsp ::WorkDoneProgress ::End ( _ ) = work {
self . lsp_progress . end_progress ( server_id , & token ) ;
2021-06-20 21:31:45 +02:00
if ! self . lsp_progress . is_progressing ( server_id ) {
2021-06-21 05:44:50 +02:00
editor_view . spinners_mut ( ) . get_or_create ( server_id ) . stop ( ) ;
2021-06-20 21:31:45 +02:00
}
2021-06-18 05:43:24 +02:00
} else {
self . lsp_progress . update ( server_id , token , work ) ;
}
2022-03-28 06:41:52 +05:30
if self . config . load ( ) . editor . lsp . display_messages {
2021-06-18 05:57:36 +02:00
self . editor . set_status ( status ) ;
}
2021-06-11 05:42:16 +02:00
}
2021-10-15 16:36:39 +08:00
Notification ::ProgressMessage ( _params ) = > {
// do nothing
}
2022-11-18 22:14:36 -06:00
Notification ::Exit = > {
self . editor . set_status ( " Language server exited " ) ;
// Clear any diagnostics for documents with this server open.
let urls : Vec < _ > = self
. editor
. documents_mut ( )
. filter_map ( | doc | {
if doc . language_server ( ) . map ( | server | server . id ( ) )
= = Some ( server_id )
{
doc . set_diagnostics ( Vec ::new ( ) ) ;
doc . url ( )
} else {
None
}
} )
. collect ( ) ;
for url in urls {
self . editor . diagnostics . remove ( & url ) ;
}
// Remove the language server from the registry.
self . editor . language_servers . remove_by_id ( server_id ) ;
}
2020-10-20 15:42:53 +09:00
}
}
2021-06-18 05:43:44 +02:00
Call ::MethodCall ( helix_lsp ::jsonrpc ::MethodCall {
2021-07-01 11:57:12 -07:00
method , params , id , ..
2021-06-18 05:43:44 +02:00
} ) = > {
2023-03-07 19:50:57 -06:00
let reply = match MethodCall ::parse ( & method , params ) {
2022-05-11 11:00:55 +09:00
Err ( helix_lsp ::Error ::Unhandled ) = > {
2023-03-07 19:50:57 -06:00
error! (
" Language Server: Method {} not found in request {} " ,
method , id
) ;
Err ( helix_lsp ::jsonrpc ::Error {
code : helix_lsp ::jsonrpc ::ErrorCode ::MethodNotFound ,
message : format ! ( " Method not found: {} " , method ) ,
data : None ,
} )
2022-05-11 11:00:55 +09:00
}
Err ( err ) = > {
log ::error! (
2023-03-07 19:50:57 -06:00
" Language Server: Received malformed method call {} in request {}: {} " ,
2022-05-11 11:00:55 +09:00
method ,
2023-03-07 19:50:57 -06:00
id ,
2022-05-11 11:00:55 +09:00
err
) ;
2023-03-07 19:50:57 -06:00
Err ( helix_lsp ::jsonrpc ::Error {
code : helix_lsp ::jsonrpc ::ErrorCode ::ParseError ,
message : format ! ( " Malformed method call: {} " , method ) ,
data : None ,
} )
2021-06-18 05:43:44 +02:00
}
2023-03-07 19:50:57 -06:00
Ok ( MethodCall ::WorkDoneProgressCreate ( params ) ) = > {
2021-06-18 05:43:44 +02:00
self . lsp_progress . create ( server_id , params . token ) ;
2021-10-15 16:36:39 +08:00
let editor_view = self
. compositor
2021-11-18 11:08:47 +09:00
. find ::< ui ::EditorView > ( )
2021-10-15 16:36:39 +08:00
. expect ( " expected at least one EditorView " ) ;
2021-06-21 05:44:50 +02:00
let spinner = editor_view . spinners_mut ( ) . get_or_create ( server_id ) ;
if spinner . is_stopped ( ) {
spinner . start ( ) ;
}
2021-12-21 10:21:45 +01:00
2022-04-01 11:20:41 +09:00
Ok ( serde_json ::Value ::Null )
2021-06-18 05:43:44 +02:00
}
2023-03-07 19:50:57 -06:00
Ok ( MethodCall ::ApplyWorkspaceEdit ( params ) ) = > {
2021-12-21 10:21:45 +01:00
apply_workspace_edit (
& mut self . editor ,
helix_lsp ::OffsetEncoding ::Utf8 ,
& params . edit ,
) ;
2022-04-01 11:20:41 +09:00
Ok ( json! ( lsp ::ApplyWorkspaceEditResponse {
applied : true ,
failure_reason : None ,
failed_change : None ,
} ) )
}
2023-03-07 19:50:57 -06:00
Ok ( MethodCall ::WorkspaceFolders ) = > {
2021-12-21 10:21:45 +01:00
let language_server =
2022-04-01 11:20:41 +09:00
self . editor . language_servers . get_by_id ( server_id ) . unwrap ( ) ;
2021-12-21 10:21:45 +01:00
2022-04-01 11:20:41 +09:00
Ok ( json! ( language_server . workspace_folders ( ) ) )
2021-12-21 10:21:45 +01:00
}
2023-03-07 19:50:57 -06:00
Ok ( MethodCall ::WorkspaceConfiguration ( params ) ) = > {
2022-02-28 09:57:22 +01:00
let result : Vec < _ > = params
. items
. iter ( )
. map ( | item | {
let mut config = match & item . scope_uri {
Some ( scope ) = > {
let path = scope . to_file_path ( ) . ok ( ) ? ;
let doc = self . editor . document_by_path ( path ) ? ;
doc . language_config ( ) ? . config . as_ref ( ) ?
}
2022-04-01 11:20:41 +09:00
None = > self
. editor
. language_servers
. get_by_id ( server_id )
. unwrap ( )
. config ( ) ? ,
2022-02-28 09:57:22 +01:00
} ;
if let Some ( section ) = item . section . as_ref ( ) {
for part in section . split ( '.' ) {
config = config . get ( part ) ? ;
}
}
Some ( config )
} )
. collect ( ) ;
2022-04-01 11:20:41 +09:00
Ok ( json! ( result ) )
2022-02-28 09:57:22 +01:00
}
2023-03-12 19:29:58 -05:00
Ok ( MethodCall ::RegisterCapability ( _params ) ) = > {
log ::warn! ( " Ignoring a client/registerCapability request because dynamic capability registration is not enabled. Please report this upstream to the language server " ) ;
// Language Servers based on the `vscode-languageserver-node` library often send
// client/registerCapability even though we do not enable dynamic registration
// for any capabilities. We should send a MethodNotFound JSONRPC error in this
// case but that rejects the registration promise in the server which causes an
// exit. So we work around this by ignoring the request and sending back an OK
// response.
Ok ( serde_json ::Value ::Null )
}
2022-04-01 11:20:41 +09:00
} ;
let language_server = match self . editor . language_servers . get_by_id ( server_id ) {
Some ( language_server ) = > language_server ,
None = > {
warn! ( " can't find language server with id `{}` " , server_id ) ;
return ;
}
} ;
tokio ::spawn ( language_server . reply ( id , reply ) ) ;
2020-10-23 18:48:03 +09:00
}
2022-03-23 16:16:19 +09:00
Call ::Invalid { id } = > log ::error! ( " LSP invalid method call id={:?} " , id ) ,
2020-05-25 13:02:21 +09:00
}
}
2023-03-05 12:06:12 -06:00
async fn claim_term ( & mut self ) -> std ::io ::Result < ( ) > {
let terminal_config = self . config . load ( ) . editor . clone ( ) . into ( ) ;
self . terminal . claim ( terminal_config )
}
2022-11-26 13:04:27 -06:00
2023-03-05 12:06:12 -06:00
fn restore_term ( & mut self ) -> std ::io ::Result < ( ) > {
let terminal_config = self . config . load ( ) . editor . clone ( ) . into ( ) ;
use helix_view ::graphics ::CursorKind ;
self . terminal
. backend_mut ( )
. show_cursor ( CursorKind ::Block )
. ok ( ) ;
self . terminal . restore ( terminal_config )
2021-07-17 22:47:08 +08:00
}
2022-04-19 01:21:31 -04:00
pub async fn run < S > ( & mut self , input_stream : & mut S ) -> Result < i32 , Error >
where
S : Stream < Item = crossterm ::Result < crossterm ::event ::Event > > + Unpin ,
{
2021-07-17 22:47:08 +08:00
self . claim_term ( ) . await ? ;
2020-06-05 12:21:27 +02:00
2020-09-19 11:57:22 +09:00
// Exit the alternate screen and disable raw mode before panicking
let hook = std ::panic ::take_hook ( ) ;
std ::panic ::set_hook ( Box ::new ( move | info | {
2021-07-01 12:08:00 -07:00
// We can't handle errors properly inside this closure. And it's
// probably not a good idea to `unwrap()` inside a panic handler.
2022-08-31 16:23:21 +00:00
// So we just ignore the `Result`.
2023-03-05 12:06:12 -06:00
let _ = TerminalBackend ::force_restore ( ) ;
2020-09-19 11:57:22 +09:00
hook ( info ) ;
} ) ) ;
2022-04-19 01:21:31 -04:00
self . event_loop ( input_stream ) . await ;
2022-10-03 11:35:42 -04:00
2022-10-04 10:35:15 -04:00
let close_errs = self . close ( ) . await ;
2022-03-29 10:01:02 +09:00
2023-03-05 12:06:12 -06:00
self . restore_term ( ) ? ;
2022-04-19 01:21:31 -04:00
2022-10-04 10:35:15 -04:00
for err in close_errs {
2022-10-03 11:35:42 -04:00
self . editor . exit_code = 1 ;
eprintln! ( " Error: {} " , err ) ;
}
2022-04-19 01:21:31 -04:00
Ok ( self . editor . exit_code )
}
2020-06-05 12:21:27 +02:00
2022-10-04 10:35:15 -04:00
pub async fn close ( & mut self ) -> Vec < anyhow ::Error > {
2022-07-09 22:39:40 -04:00
// [NOTE] we intentionally do not return early for errors because we
// want to try to run as much cleanup as we can, regardless of
// errors along the way
2022-10-04 10:35:15 -04:00
let mut errs = Vec ::new ( ) ;
2022-02-05 15:05:19 +09:00
2022-10-04 10:35:15 -04:00
if let Err ( err ) = self
2022-07-11 23:38:26 -04:00
. jobs
2022-09-02 23:38:38 -04:00
. finish ( & mut self . editor , Some ( & mut self . compositor ) )
2022-07-11 23:38:26 -04:00
. await
{
2022-10-04 10:35:15 -04:00
log ::error! ( " Error executing job: {} " , err ) ;
errs . push ( err ) ;
2021-08-10 10:52:02 +09:00
} ;
2021-06-19 04:56:50 +02:00
2022-10-16 21:40:40 -04:00
if let Err ( err ) = self . editor . flush_writes ( ) . await {
log ::error! ( " Error writing: {} " , err ) ;
errs . push ( err ) ;
}
2022-07-09 22:39:40 -04:00
2022-10-04 10:35:15 -04:00
if self . editor . close_language_servers ( None ) . await . is_err ( ) {
log ::error! ( " Timed out waiting for language servers to shutdown " ) ;
errs . push ( anyhow ::format_err! (
" Timed out waiting for language servers to shutdown "
) ) ;
2022-07-09 22:39:40 -04:00
}
2022-10-04 10:35:15 -04:00
errs
2020-06-01 17:42:28 +09:00
}
2020-05-25 13:02:21 +09:00
}