@ -52,7 +52,7 @@ LanguageProtocol::Diagnostic::RelatedInformation::RelatedInformation(const JSON
LanguageProtocol : : Diagnostic : : Diagnostic ( JSON & & diagnostic ) : message ( diagnostic . string ( " message " ) ) , range ( diagnostic . object ( " range " ) ) , severity ( diagnostic . integer_or ( " severity " , 0 ) ) , code ( diagnostic . string_or ( " code " , " " ) ) {
for ( auto & related_information : diagnostic . array_or_empty ( " relatedInformation " ) )
related_informations . emplace_back ( related_information ) ;
object = std : : make_shared < JSON > ( JSON : : make_owner ( std : : move ( diagnostic ) ) ) ;
object = std : : make_unique < JSON > ( JSON : : make_owner ( std : : move ( diagnostic ) ) ) ;
}
LanguageProtocol : : TextEdit : : TextEdit ( const JSON & text_edit , std : : string new_text_ ) : range ( text_edit . object ( " range " ) ) , new_text ( new_text_ . empty ( ) ? text_edit . string ( " newText " ) : std : : move ( new_text_ ) ) { }
@ -222,7 +222,8 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize() {
" workspace " : {
" symbol " : { " dynamicRegistration " : false } ,
" executeCommand " : { " dynamicRegistration " : false } ,
" workspaceFolders " : true
" workspaceFolders " : true ,
" configuration " : true
} ,
" textDocument " : {
" synchronization " : { " dynamicRegistration " : false , " didSave " : true } ,
@ -292,8 +293,21 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize() {
capabilities . text_document_sync = static_cast < LanguageProtocol : : Capabilities : : TextDocumentSync > ( child - > integer_or ( " change " , 0 ) ) ;
}
if ( auto child = object - > child_optional ( " completionProvider " ) ) {
capabilities . completion = true ;
capabilities . completion_resolve = child - > boolean_or ( " resolveProvider " , false ) ;
bool is_ltex = false ; // Workaround for https://github.com/valentjn/ltex-ls that erroneously reports completionProvider
try {
for ( auto & command : object - > child ( " executeCommandProvider " ) . array ( " commands " ) ) {
if ( starts_with ( command . string ( ) , " _ltex. " ) ) {
is_ltex = true ;
break ;
}
}
}
catch ( . . . ) {
}
if ( ! is_ltex ) {
capabilities . completion = true ;
capabilities . completion_resolve = child - > boolean_or ( " resolveProvider " , false ) ;
}
}
/// Some server capabilities are reported as either boolean or objects
auto boolean_or_object = [ & object ] ( const std : : string & provider ) {
@ -540,12 +554,12 @@ void LanguageProtocol::Client::write_notification(const std::string &method, con
void LanguageProtocol : : Client : : handle_server_notification ( const std : : string & method , JSON & & params ) {
if ( method = = " textDocument/publishDiagnostics " ) {
std : : vector < Diagnostic > diagnostics ;
std : : vector < std : : shared_ptr < Diagnostic > > diagnostics ;
auto file = filesystem : : get_path_from_uri ( params . string_or ( " uri " , " " ) ) ;
if ( ! file . empty ( ) ) {
for ( auto & child : params . array_or_empty ( " diagnostics " ) ) {
try {
diagnostics . emplace_back ( std : : move ( child ) ) ;
diagnostics . emplace_back ( std : : make_shared < Diagnostic > ( std : : m ove ( child ) ) ) ;
}
catch ( . . . ) {
}
@ -661,12 +675,32 @@ void LanguageProtocol::Client::handle_server_request(const boost::variant<size_t
}
write_response ( id , " {} " ) ;
}
else if ( method = = " workspace/configuration " ) {
auto search_path = root_path ;
auto file = ' . ' + language_id + " -lsp-configuration-response.json " ;
boost : : filesystem : : path settings_path ;
while ( true ) {
boost : : system : : error_code ec ;
auto path = search_path / file ;
if ( boost : : filesystem : : exists ( path , ec ) ) {
settings_path = std : : move ( path ) ;
break ;
}
if ( search_path = = search_path . root_directory ( ) )
break ;
search_path = search_path . parent_path ( ) ;
}
if ( ! settings_path . empty ( ) )
write_response ( id , filesystem : : read ( settings_path ) ) ;
else
write_response ( id , " {} " ) ;
}
else
write_response ( id , " {} " ) ; // TODO: write error instead on unsupported methods
}
Source : : LanguageProtocolView : : LanguageProtocolView ( const boost : : filesystem : : path & file_path , const Glib : : RefPtr < Gsv : : Language > & language , std : : string language_id_ , std : : string language_server_ )
: Source : : BaseView ( file_path , language ) , Source : : View ( file_path , language ) , uri ( filesystem : : get_uri_from_path ( file_path ) ) , uri_escaped ( JSON : : escape_string ( uri ) ) , language_id ( std : : move ( language_id_ ) ) , language_server ( std : : move ( language_server_ ) ) , client ( LanguageProtocol : : Client : : get ( file_path , language_id , language_server ) ) {
: Source : : BaseView ( file_path , language ) , Source : : Generic View( file_path , language , fals e ) , uri ( filesystem : : get_uri_from_path ( file_path ) ) , uri_escaped ( JSON : : escape_string ( uri ) ) , language_id ( std : : move ( language_id_ ) ) , language_server ( std : : move ( language_server_ ) ) , client ( LanguageProtocol : : Client : : get ( file_path , language_id , language_server ) ) {
initialize ( ) ;
}
@ -1059,9 +1093,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
std : : unordered_map < std : : string , std : : vector < std : : string > > file_lines ;
std : : vector < std : : pair < Offset , std : : string > > usages ;
auto c = static_cast < size_t > ( - 1 ) ;
for ( auto & location : locations ) {
+ + c ;
usages . emplace_back ( Offset ( location . range . start . line , location . range . start . character , location . file ) , std : : string ( ) ) ;
auto & usage = usages . back ( ) ;
Source : : View * view = nullptr ;
@ -1345,8 +1377,19 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
std : : promise < void > result_processed ;
Gtk : : TextIter start , end ;
get_buffer ( ) - > get_selection_bounds ( start , end ) ;
bool first = true ;
std : : stringstream ss ;
for ( auto & diagnostic : last_diagnostics ) {
if ( get_iter_at_line_pos ( diagnostic - > range . start . line , diagnostic - > range . start . character ) < = start & &
get_iter_at_line_pos ( diagnostic - > range . end . line , diagnostic - > range . end . character ) > = end ) {
if ( ! first )
ss < < ' , ' ;
ss < < * diagnostic - > object ;
first = false ;
}
}
std : : vector < std : : pair < std : : string , std : : shared_ptr < JSON > > > results ;
write_request ( " textDocument/codeAction " , to_string ( { make_range ( { start . get_line ( ) , get_line_pos ( start ) } , { end . get_line ( ) , get_line_pos ( end ) } ) , { " context " , " { \" diagnostics \" :[]} " } } ) , [ & result_processed , & results ] ( JSON & & result , bool error ) {
write_request ( " textDocument/codeAction " , to_string ( { make_range ( { start . get_line ( ) , get_line_pos ( start ) } , { end . get_line ( ) , get_line_pos ( end ) } ) , { " context " , " { \" diagnostics \" :[ " + ss . str ( ) + " ]} " } } ) , [ & result_processed , & results ] ( JSON & & result , bool error ) {
if ( ! error ) {
for ( auto & code_action : result . array_or_empty ( ) ) {
auto title = code_action . string_or ( " title " , " " ) ;
@ -1491,10 +1534,12 @@ void Source::LanguageProtocolView::setup_signals() {
}
void Source : : LanguageProtocolView : : setup_autocomplete ( ) {
autocomplete = std : : make_unique < Autocomplete > ( this , interactive_completion , last_keyval , false , true ) ;
if ( ! capabilities . completion )
if ( ! capabilities . completion ) {
GenericView : : setup_autocomplete ( ) ;
return ;
}
autocomplete = std : : make_unique < Autocomplete > ( this , interactive_completion , last_keyval , false , true ) ;
non_interactive_completion = [ this ] {
if ( CompletionDialog : : get ( ) & & CompletionDialog : : get ( ) - > is_visible ( ) )
@ -1975,7 +2020,7 @@ void Source::LanguageProtocolView::setup_autocomplete() {
} ;
}
void Source : : LanguageProtocolView : : update_diagnostics_async ( std : : vector < LanguageProtocol : : Diagnostic > & & diagnostics ) {
void Source : : LanguageProtocolView : : update_diagnostics_async ( std : : vector < std : : shared_ptr < LanguageProtocol : : Diagnostic > > & & diagnostics ) {
size_t last_count = + + update_diagnostics_async_count ;
if ( capabilities . code_action & & ! diagnostics . empty ( ) ) {
dispatcher . post ( [ this , diagnostics = std : : move ( diagnostics ) , last_count ] ( ) mutable {
@ -1986,12 +2031,12 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vector<Language
for ( auto & diagnostic : diagnostics ) {
if ( ! first )
ss < < ' , ' ;
ss < < * diagnostic . object ;
ss < < * diagnostic - > object ;
first = false ;
}
std : : pair < std : : string , std : : string > range ;
if ( diagnostics . size ( ) = = 1 ) // Use diagnostic range if only one diagnostic, otherwise use whole buffer
range = make_range ( { diagnostics [ 0 ] . range . start . line , diagnostics [ 0 ] . range . start . character } , { diagnostics [ 0 ] . range . end . line , diagnostics [ 0 ] . range . end . character } ) ;
range = make_range ( { diagnostics [ 0 ] - > range . start . line , diagnostics [ 0 ] - > range . start . character } , { diagnostics [ 0 ] - > range . end . line , diagnostics [ 0 ] - > range . end . character } ) ;
else {
auto start = get_buffer ( ) - > begin ( ) ;
auto end = get_buffer ( ) - > end ( ) ;
@ -2027,8 +2072,8 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vector<Language
if ( ! quickfix_diagnostics . empty ( ) ) {
for ( auto & diagnostic : diagnostics ) {
for ( auto & quickfix_diagnostic : quickfix_diagnostics ) {
if ( diagnostic . message = = quickfix_diagnostic . message & & diagnostic . range = = quickfix_diagnostic . range ) {
auto pair = diagnostic . quickfixes . emplace ( title , std : : set < Source : : FixIt > { } ) ;
if ( diagnostic - > message = = quickfix_diagnostic . message & & diagnostic - > range = = quickfix_diagnostic . range ) {
auto pair = diagnostic - > quickfixes . emplace ( title , std : : set < Source : : FixIt > { } ) ;
pair . first - > second . emplace (
text_edit . new_text ,
edit - > file ,
@ -2041,8 +2086,8 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vector<Language
}
else { // Workaround for language server that does not report quickfix diagnostics
for ( auto & diagnostic : diagnostics ) {
if ( text_edit . range . start . line = = diagnostic . range . start . line ) {
auto pair = diagnostic . quickfixes . emplace ( title , std : : set < Source : : FixIt > { } ) ;
if ( text_edit . range . start . line = = diagnostic - > range . start . line ) {
auto pair = diagnostic - > quickfixes . emplace ( title , std : : set < Source : : FixIt > { } ) ;
pair . first - > second . emplace (
text_edit . new_text ,
edit - > file ,
@ -2067,9 +2112,8 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vector<Language
result_processed . get_future ( ) . get ( ) ;
dispatcher . post ( [ this , diagnostics = std : : move ( diagnostics ) , last_count ] ( ) mutable {
if ( last_count = = update_diagnostics_async_count ) {
if ( capabilities . type_coverage )
last_diagnostics = diagnostics ;
update_diagnostics ( std : : move ( diagnostics ) ) ;
update_diagnostics ( diagnostics ) ;
last_diagnostics = std : : move ( diagnostics ) ;
}
} ) ;
} ) ;
@ -2078,15 +2122,14 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vector<Language
else {
dispatcher . post ( [ this , diagnostics = std : : move ( diagnostics ) , last_count ] ( ) mutable {
if ( last_count = = update_diagnostics_async_count ) {
if ( capabilities . type_coverage )
last_diagnostics = diagnostics ;
update_diagnostics ( std : : move ( diagnostics ) ) ;
update_diagnostics ( diagnostics ) ;
last_diagnostics = std : : move ( diagnostics ) ;
}
} ) ;
}
}
void Source : : LanguageProtocolView : : update_diagnostics ( std : : vector < LanguageProtocol : : Diagnostic > diagnostics ) {
void Source : : LanguageProtocolView : : update_diagnostics ( const std : : vector < std : : shared_ptr < LanguageProtocol : : Diagnostic > > & diagnostics ) {
diagnostic_offsets . clear ( ) ;
diagnostic_tooltips . clear ( ) ;
fix_its . clear ( ) ;
@ -2097,8 +2140,8 @@ void Source::LanguageProtocolView::update_diagnostics(std::vector<LanguageProtoc
num_fix_its = 0 ;
for ( auto & diagnostic : diagnostics ) {
auto start = get_iter_at_line_pos ( diagnostic . range . start . line , diagnostic . range . start . character ) ;
auto end = get_iter_at_line_pos ( diagnostic . range . end . line , diagnostic . range . end . character ) ;
auto start = get_iter_at_line_pos ( diagnostic - > range . start . line , diagnostic - > range . start . character ) ;
auto end = get_iter_at_line_pos ( diagnostic - > range . end . line , diagnostic - > range . end . character ) ;
if ( start = = end ) {
if ( ! end . ends_line ( ) )
@ -2109,47 +2152,47 @@ void Source::LanguageProtocolView::update_diagnostics(std::vector<LanguageProtoc
}
bool error = false ;
if ( diagnostic . severity > = 2 )
if ( diagnostic - > severity > = 2 )
num_warnings + + ;
else {
num_errors + + ;
error = true ;
}
num_fix_its + = diagnostic . quickfixes . size ( ) ;
num_fix_its + = diagnostic - > quickfixes . size ( ) ;
for ( auto & quickfix : diagnostic . quickfixes )
for ( auto & quickfix : diagnostic - > quickfixes )
fix_its . insert ( fix_its . end ( ) , quickfix . second . begin ( ) , quickfix . second . end ( ) ) ;
add_diagnostic_tooltip ( start , end , error , [ this , diagnostic = std : : move ( diagnostic ) ] ( Tooltip & tooltip ) {
add_diagnostic_tooltip ( start , end , error , [ this , diagnostic ] ( Tooltip & tooltip ) {
if ( language_id = = " python " & & ! client - > pyright ) { // pylsp might support markdown in the future
tooltip . insert_with_links_tagged ( diagnostic . message ) ;
tooltip . insert_with_links_tagged ( diagnostic - > message ) ;
return ;
}
tooltip . insert_markdown ( diagnostic . message ) ;
tooltip . insert_markdown ( diagnostic - > message ) ;
if ( ! diagnostic . related_informations . empty ( ) ) {
if ( ! diagnostic - > related_informations . empty ( ) ) {
auto link_tag = tooltip . buffer - > get_tag_table ( ) - > lookup ( " link " ) ;
for ( size_t i = 0 ; i < diagnostic . related_informations . size ( ) ; + + i ) {
auto link = filesystem : : get_relative_path ( diagnostic . related_informations [ i ] . location . file , file_path . parent_path ( ) ) . string ( ) ;
link + = ' : ' + std : : to_string ( diagnostic . related_informations [ i ] . location . range . start . line + 1 ) ;
link + = ' : ' + std : : to_string ( diagnostic . related_informations [ i ] . location . range . start . character + 1 ) ;
for ( size_t i = 0 ; i < diagnostic - > related_informations . size ( ) ; + + i ) {
auto link = filesystem : : get_relative_path ( diagnostic - > related_informations [ i ] . location . file , file_path . parent_path ( ) ) . string ( ) ;
link + = ' : ' + std : : to_string ( diagnostic - > related_informations [ i ] . location . range . start . line + 1 ) ;
link + = ' : ' + std : : to_string ( diagnostic - > related_informations [ i ] . location . range . start . character + 1 ) ;
if ( i = = 0 )
tooltip . buffer - > insert_at_cursor ( " \n \n " ) ;
else
tooltip . buffer - > insert_at_cursor ( " \n " ) ;
tooltip . insert_markdown ( diagnostic . related_informations [ i ] . message ) ;
tooltip . insert_markdown ( diagnostic - > related_informations [ i ] . message ) ;
tooltip . buffer - > insert_at_cursor ( " : " ) ;
tooltip . buffer - > insert_with_tag ( tooltip . buffer - > get_insert ( ) - > get_iter ( ) , link , link_tag ) ;
}
}
if ( ! diagnostic . quickfixes . empty ( ) ) {
if ( diagnostic . quickfixes . size ( ) = = 1 )
if ( ! diagnostic - > quickfixes . empty ( ) ) {
if ( diagnostic - > quickfixes . size ( ) = = 1 )
tooltip . buffer - > insert_at_cursor ( " \n \n Fix-it: " ) ;
else
tooltip . buffer - > insert_at_cursor ( " \n \n Fix-its: " ) ;
for ( auto & quickfix : diagnostic . quickfixes ) {
for ( auto & quickfix : diagnostic - > quickfixes ) {
tooltip . buffer - > insert_at_cursor ( " \n " ) ;
tooltip . insert_markdown ( quickfix . first ) ;
}