2021-12-13 13:53:40 +00:00
/*
This file is part of solidity .
solidity is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
solidity is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with solidity . If not , see < http : //www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
# include <libsolidity/ast/AST.h>
# include <libsolidity/ast/ASTUtils.h>
# include <libsolidity/ast/ASTVisitor.h>
# include <libsolidity/interface/ReadFile.h>
# include <libsolidity/interface/StandardCompiler.h>
# include <libsolidity/lsp/LanguageServer.h>
2022-01-05 11:15:10 +00:00
# include <libsolidity/lsp/HandlerBase.h>
# include <libsolidity/lsp/Utils.h>
2021-12-20 13:10:17 +00:00
// LSP feature implementations
# include <libsolidity/lsp/GotoDefinition.h>
2022-05-23 12:20:03 +00:00
# include <libsolidity/lsp/RenameSymbol.h>
2022-05-03 14:32:48 +00:00
# include <libsolidity/lsp/SemanticTokensBuilder.h>
2021-12-13 13:53:40 +00:00
# include <liblangutil/SourceReferenceExtractor.h>
# include <liblangutil/CharStream.h>
2022-06-13 13:56:55 +00:00
# include <libsolutil/CommonIO.h>
2021-12-13 13:53:40 +00:00
# include <libsolutil/Visitor.h>
# include <libsolutil/JSON.h>
# include <boost/exception/diagnostic_information.hpp>
# include <boost/filesystem.hpp>
# include <boost/algorithm/string/predicate.hpp>
# include <ostream>
# include <string>
2022-06-13 13:56:55 +00:00
# include <fmt/format.h>
2021-12-13 13:53:40 +00:00
using namespace std ;
2022-01-03 15:19:04 +00:00
using namespace std : : string_literals ;
2021-12-13 13:53:40 +00:00
using namespace std : : placeholders ;
using namespace solidity : : lsp ;
using namespace solidity : : langutil ;
using namespace solidity : : frontend ;
namespace
{
2021-12-20 16:29:44 +00:00
int toDiagnosticSeverity ( Error : : Type _errorType )
2021-12-13 13:53:40 +00:00
{
// 1=Error, 2=Warning, 3=Info, 4=Hint
switch ( Error : : errorSeverity ( _errorType ) )
{
case Error : : Severity : : Error : return 1 ;
case Error : : Severity : : Warning : return 2 ;
case Error : : Severity : : Info : return 3 ;
}
solAssert ( false ) ;
return - 1 ;
}
2022-05-03 14:32:48 +00:00
Json : : Value semanticTokensLegend ( )
{
Json : : Value legend = Json : : objectValue ;
// NOTE! The (alphabetical) order and items must match exactly the items of
// their respective enum class members.
Json : : Value tokenTypes = Json : : arrayValue ;
tokenTypes . append ( " class " ) ;
tokenTypes . append ( " comment " ) ;
tokenTypes . append ( " enum " ) ;
tokenTypes . append ( " enumMember " ) ;
tokenTypes . append ( " event " ) ;
tokenTypes . append ( " function " ) ;
tokenTypes . append ( " interface " ) ;
tokenTypes . append ( " keyword " ) ;
tokenTypes . append ( " macro " ) ;
tokenTypes . append ( " method " ) ;
tokenTypes . append ( " modifier " ) ;
tokenTypes . append ( " number " ) ;
tokenTypes . append ( " operator " ) ;
tokenTypes . append ( " parameter " ) ;
tokenTypes . append ( " property " ) ;
tokenTypes . append ( " string " ) ;
tokenTypes . append ( " struct " ) ;
tokenTypes . append ( " type " ) ;
tokenTypes . append ( " typeParameter " ) ;
tokenTypes . append ( " variable " ) ;
legend [ " tokenTypes " ] = tokenTypes ;
Json : : Value tokenModifiers = Json : : arrayValue ;
tokenModifiers . append ( " abstract " ) ;
tokenModifiers . append ( " declaration " ) ;
tokenModifiers . append ( " definition " ) ;
tokenModifiers . append ( " deprecated " ) ;
tokenModifiers . append ( " documentation " ) ;
tokenModifiers . append ( " modification " ) ;
tokenModifiers . append ( " readonly " ) ;
legend [ " tokenModifiers " ] = tokenModifiers ;
return legend ;
}
2021-12-13 13:53:40 +00:00
}
LanguageServer : : LanguageServer ( Transport & _transport ) :
m_client { _transport } ,
m_handlers {
{ " $/cancelRequest " , [ ] ( auto , auto ) { /*nothing for now as we are synchronous */ } } ,
{ " cancelRequest " , [ ] ( auto , auto ) { /*nothing for now as we are synchronous */ } } ,
{ " exit " , [ this ] ( auto , auto ) { m_state = ( m_state = = State : : ShutdownRequested ? State : : ExitRequested : State : : ExitWithoutShutdown ) ; } } ,
{ " initialize " , bind ( & LanguageServer : : handleInitialize , this , _1 , _2 ) } ,
2022-06-13 13:56:55 +00:00
{ " initialized " , bind ( & LanguageServer : : handleInitialized , this , _1 , _2 ) } ,
2022-07-13 13:46:49 +00:00
{ " $/setTrace " , [ this ] ( auto , Json : : Value const & args ) { setTrace ( args [ " value " ] ) ; } } ,
2021-12-13 13:53:40 +00:00
{ " shutdown " , [ this ] ( auto , auto ) { m_state = State : : ShutdownRequested ; } } ,
2021-12-20 13:10:17 +00:00
{ " textDocument/definition " , GotoDefinition ( * this ) } ,
2022-01-03 15:19:04 +00:00
{ " textDocument/didOpen " , bind ( & LanguageServer : : handleTextDocumentDidOpen , this , _2 ) } ,
{ " textDocument/didChange " , bind ( & LanguageServer : : handleTextDocumentDidChange , this , _2 ) } ,
{ " textDocument/didClose " , bind ( & LanguageServer : : handleTextDocumentDidClose , this , _2 ) } ,
2022-05-23 12:20:03 +00:00
{ " textDocument/rename " , RenameSymbol ( * this ) } ,
2021-12-20 13:10:17 +00:00
{ " textDocument/implementation " , GotoDefinition ( * this ) } ,
2022-05-03 14:32:48 +00:00
{ " textDocument/semanticTokens/full " , bind ( & LanguageServer : : semanticTokensFull , this , _1 , _2 ) } ,
2022-01-03 15:19:04 +00:00
{ " workspace/didChangeConfiguration " , bind ( & LanguageServer : : handleWorkspaceDidChangeConfiguration , this , _2 ) } ,
2021-12-13 13:53:40 +00:00
} ,
2022-05-09 15:34:35 +00:00
m_fileRepository ( " / " /* basePath */ , { } /* no search paths */ ) ,
2021-12-13 13:53:40 +00:00
m_compilerStack { m_fileRepository . reader ( ) }
{
}
2022-01-05 11:15:10 +00:00
Json : : Value LanguageServer : : toRange ( SourceLocation const & _location )
2021-12-13 13:53:40 +00:00
{
2022-01-05 11:15:10 +00:00
return HandlerBase ( * this ) . toRange ( _location ) ;
2021-12-13 13:53:40 +00:00
}
2022-01-05 11:15:10 +00:00
Json : : Value LanguageServer : : toJson ( SourceLocation const & _location )
2021-12-13 13:53:40 +00:00
{
2022-01-05 11:15:10 +00:00
return HandlerBase ( * this ) . toJson ( _location ) ;
2021-12-13 13:53:40 +00:00
}
void LanguageServer : : changeConfiguration ( Json : : Value const & _settings )
{
2022-06-13 13:56:55 +00:00
// The settings item: "file-load-strategy" (enum) defaults to "project-directory" if not (or not correctly) set.
// It can be overridden during client's handshake or at runtime, as usual.
//
// If this value is set to "project-directory" (default), all .sol files located inside the project directory or reachable through symbolic links will be subject to operations.
//
// Operations include compiler analysis, but also finding all symbolic references or symbolic renaming.
//
// If this value is set to "directly-opened-and-on-import", then only currently directly opened files and
// those files being imported directly or indirectly will be included in operations.
if ( _settings [ " file-load-strategy " ] )
{
auto const text = _settings [ " file-load-strategy " ] . asString ( ) ;
if ( text = = " project-directory " )
m_fileLoadStrategy = FileLoadStrategy : : ProjectDirectory ;
else if ( text = = " directly-opened-and-on-import " )
m_fileLoadStrategy = FileLoadStrategy : : DirectlyOpenedAndOnImported ;
else
lspAssert ( false , ErrorCode : : InvalidParams , " Invalid file load strategy: " + text ) ;
}
2021-12-13 13:53:40 +00:00
m_settingsObject = _settings ;
2022-05-09 15:34:35 +00:00
Json : : Value jsonIncludePaths = _settings [ " include-paths " ] ;
2022-07-13 14:29:33 +00:00
if ( jsonIncludePaths )
2022-05-09 15:34:35 +00:00
{
2022-07-13 14:29:33 +00:00
int typeFailureCount = 0 ;
if ( jsonIncludePaths . isArray ( ) )
2022-05-09 15:34:35 +00:00
{
2022-07-13 14:29:33 +00:00
vector < boost : : filesystem : : path > includePaths ;
for ( Json : : Value const & jsonPath : jsonIncludePaths )
{
if ( jsonPath . isString ( ) )
includePaths . emplace_back ( boost : : filesystem : : path ( jsonPath . asString ( ) ) ) ;
else
typeFailureCount + + ;
}
m_fileRepository . setIncludePaths ( move ( includePaths ) ) ;
2022-05-09 15:34:35 +00:00
}
2022-07-13 14:29:33 +00:00
else
+ + typeFailureCount ;
2022-05-09 15:34:35 +00:00
2022-07-13 14:29:33 +00:00
if ( typeFailureCount )
m_client . trace ( " Invalid JSON configuration passed. \" include-paths \" must be an array of strings. " ) ;
}
2021-12-13 13:53:40 +00:00
}
2022-06-13 13:56:55 +00:00
vector < boost : : filesystem : : path > LanguageServer : : allSolidityFilesFromProject ( ) const
{
namespace fs = boost : : filesystem ;
std : : vector < fs : : path > collectedPaths { } ;
// We explicitly decided against including all files from include paths but leave the possibility
// open for a future PR to enable such a feature to be optionally enabled (default disabled).
auto directoryIterator = fs : : recursive_directory_iterator ( m_fileRepository . basePath ( ) , fs : : symlink_option : : recurse ) ;
for ( fs : : directory_entry const & dirEntry : directoryIterator )
if ( dirEntry . path ( ) . extension ( ) = = " .sol " )
collectedPaths . push_back ( dirEntry . path ( ) ) ;
return collectedPaths ;
}
2021-12-13 13:53:40 +00:00
void LanguageServer : : compile ( )
{
// For files that are not open, we have to take changes on disk into account,
// so we just remove all non-open files.
2022-05-09 15:34:35 +00:00
FileRepository oldRepository ( m_fileRepository . basePath ( ) , m_fileRepository . includePaths ( ) ) ;
2021-12-13 13:53:40 +00:00
swap ( oldRepository , m_fileRepository ) ;
2022-06-13 13:56:55 +00:00
// Load all solidity files from project.
if ( m_fileLoadStrategy = = FileLoadStrategy : : ProjectDirectory )
for ( auto const & projectFile : allSolidityFilesFromProject ( ) )
{
lspDebug ( fmt : : format ( " adding project file: {} " , projectFile . generic_string ( ) ) ) ;
m_fileRepository . setSourceByUri (
m_fileRepository . sourceUnitNameToUri ( projectFile . generic_string ( ) ) ,
util : : readFileAsString ( projectFile )
) ;
}
// Overwrite all files as opened by the client, including the ones which might potentially have changes.
2021-12-13 13:53:40 +00:00
for ( string const & fileName : m_openFiles )
2022-04-25 13:35:41 +00:00
m_fileRepository . setSourceByUri (
2021-12-13 13:53:40 +00:00
fileName ,
2022-04-25 13:35:41 +00:00
oldRepository . sourceUnits ( ) . at ( oldRepository . uriToSourceUnitName ( fileName ) )
2021-12-13 13:53:40 +00:00
) ;
// TODO: optimize! do not recompile if nothing has changed (file(s) not flagged dirty).
m_compilerStack . reset ( false ) ;
m_compilerStack . setSources ( m_fileRepository . sourceUnits ( ) ) ;
m_compilerStack . compile ( CompilerStack : : State : : AnalysisPerformed ) ;
}
void LanguageServer : : compileAndUpdateDiagnostics ( )
{
compile ( ) ;
// These are the source units we will sent diagnostics to the client for sure,
// even if it is just to clear previous diagnostics.
map < string , Json : : Value > diagnosticsBySourceUnit ;
for ( string const & sourceUnitName : m_fileRepository . sourceUnits ( ) | ranges : : views : : keys )
diagnosticsBySourceUnit [ sourceUnitName ] = Json : : arrayValue ;
for ( string const & sourceUnitName : m_nonemptyDiagnostics )
diagnosticsBySourceUnit [ sourceUnitName ] = Json : : arrayValue ;
for ( shared_ptr < Error const > const & error : m_compilerStack . errors ( ) )
{
SourceLocation const * location = error - > sourceLocation ( ) ;
if ( ! location | | ! location - > sourceName )
// LSP only has diagnostics applied to individual files.
continue ;
Json : : Value jsonDiag ;
jsonDiag [ " source " ] = " solc " ;
jsonDiag [ " severity " ] = toDiagnosticSeverity ( error - > type ( ) ) ;
jsonDiag [ " code " ] = Json : : UInt64 { error - > errorId ( ) . error } ;
string message = error - > typeName ( ) + " : " ;
if ( string const * comment = error - > comment ( ) )
message + = " " + * comment ;
jsonDiag [ " message " ] = move ( message ) ;
jsonDiag [ " range " ] = toRange ( * location ) ;
if ( auto const * secondary = error - > secondarySourceLocation ( ) )
for ( auto & & [ secondaryMessage , secondaryLocation ] : secondary - > infos )
{
Json : : Value jsonRelated ;
jsonRelated [ " message " ] = secondaryMessage ;
jsonRelated [ " location " ] = toJson ( secondaryLocation ) ;
jsonDiag [ " relatedInformation " ] . append ( jsonRelated ) ;
}
diagnosticsBySourceUnit [ * location - > sourceName ] . append ( jsonDiag ) ;
}
2022-04-05 11:38:59 +00:00
if ( m_client . traceValue ( ) ! = TraceValue : : Off )
{
Json : : Value extra ;
extra [ " openFileCount " ] = Json : : UInt64 ( diagnosticsBySourceUnit . size ( ) ) ;
m_client . trace ( " Number of currently open files: " + to_string ( diagnosticsBySourceUnit . size ( ) ) , extra ) ;
}
2021-12-13 13:53:40 +00:00
m_nonemptyDiagnostics . clear ( ) ;
for ( auto & & [ sourceUnitName , diagnostics ] : diagnosticsBySourceUnit )
{
Json : : Value params ;
2022-04-25 13:35:41 +00:00
params [ " uri " ] = m_fileRepository . sourceUnitNameToUri ( sourceUnitName ) ;
2021-12-13 13:53:40 +00:00
if ( ! diagnostics . empty ( ) )
m_nonemptyDiagnostics . insert ( sourceUnitName ) ;
params [ " diagnostics " ] = move ( diagnostics ) ;
m_client . notify ( " textDocument/publishDiagnostics " , move ( params ) ) ;
}
}
bool LanguageServer : : run ( )
{
while ( m_state ! = State : : ExitRequested & & m_state ! = State : : ExitWithoutShutdown & & ! m_client . closed ( ) )
{
MessageID id ;
try
{
optional < Json : : Value > const jsonMessage = m_client . receive ( ) ;
if ( ! jsonMessage )
continue ;
if ( ( * jsonMessage ) [ " method " ] . isString ( ) )
{
string const methodName = ( * jsonMessage ) [ " method " ] . asString ( ) ;
id = ( * jsonMessage ) [ " id " ] ;
2022-06-13 13:56:55 +00:00
lspDebug ( fmt : : format ( " received method call: {} " , methodName ) ) ;
2021-12-13 13:53:40 +00:00
2022-03-07 04:25:35 +00:00
if ( auto handler = util : : valueOrDefault ( m_handlers , methodName ) )
2021-12-13 13:53:40 +00:00
handler ( id , ( * jsonMessage ) [ " params " ] ) ;
else
m_client . error ( id , ErrorCode : : MethodNotFound , " Unknown method " + methodName ) ;
}
else
m_client . error ( { } , ErrorCode : : ParseError , " \" method \" has to be a string. " ) ;
}
2022-06-13 13:56:55 +00:00
catch ( Json : : Exception const & )
{
m_client . error ( id , ErrorCode : : InvalidParams , " JSON object access error. Most likely due to a badly formatted JSON request message. " s ) ;
}
2022-01-03 15:19:04 +00:00
catch ( RequestError const & error )
{
m_client . error ( id , error . code ( ) , error . comment ( ) ? * error . comment ( ) : " " s ) ;
}
2021-12-13 13:53:40 +00:00
catch ( . . . )
{
m_client . error ( id , ErrorCode : : InternalError , " Unhandled exception: " s + boost : : current_exception_diagnostic_information ( ) ) ;
}
}
return m_state = = State : : ExitRequested ;
}
2022-01-03 15:19:04 +00:00
void LanguageServer : : requireServerInitialized ( )
2021-12-13 13:53:40 +00:00
{
2022-01-05 10:30:04 +00:00
lspAssert (
m_state = = State : : Initialized ,
ErrorCode : : ServerNotInitialized ,
" Server is not properly initialized. "
) ;
2021-12-13 13:53:40 +00:00
}
void LanguageServer : : handleInitialize ( MessageID _id , Json : : Value const & _args )
{
2022-01-05 10:30:04 +00:00
lspAssert (
m_state = = State : : Started ,
ErrorCode : : RequestFailed ,
" Initialize called at the wrong time. "
) ;
2022-01-03 15:19:04 +00:00
2021-12-13 13:53:40 +00:00
m_state = State : : Initialized ;
// The default of FileReader is to use `.`, but the path from where the LSP was started
// should not matter.
string rootPath ( " / " ) ;
if ( Json : : Value uri = _args [ " rootUri " ] )
{
rootPath = uri . asString ( ) ;
2022-01-05 10:30:04 +00:00
lspAssert (
boost : : starts_with ( rootPath , " file:// " ) ,
ErrorCode : : InvalidParams ,
" rootUri only supports file URI scheme. "
) ;
2022-04-25 13:35:41 +00:00
rootPath = stripFileUriSchemePrefix ( rootPath ) ;
2021-12-13 13:53:40 +00:00
}
else if ( Json : : Value rootPath = _args [ " rootPath " ] )
rootPath = rootPath . asString ( ) ;
2022-07-13 13:46:49 +00:00
if ( _args [ " trace " ] )
setTrace ( _args [ " trace " ] ) ;
2022-05-09 15:34:35 +00:00
m_fileRepository = FileRepository ( rootPath , { } ) ;
2021-12-13 13:53:40 +00:00
if ( _args [ " initializationOptions " ] . isObject ( ) )
changeConfiguration ( _args [ " initializationOptions " ] ) ;
Json : : Value replyArgs ;
replyArgs [ " serverInfo " ] [ " name " ] = " solc " ;
replyArgs [ " serverInfo " ] [ " version " ] = string ( VersionNumber ) ;
2021-12-20 13:10:17 +00:00
replyArgs [ " capabilities " ] [ " definitionProvider " ] = true ;
replyArgs [ " capabilities " ] [ " implementationProvider " ] = true ;
2021-12-13 13:53:40 +00:00
replyArgs [ " capabilities " ] [ " textDocumentSync " ] [ " change " ] = 2 ; // 0=none, 1=full, 2=incremental
2021-12-20 13:10:17 +00:00
replyArgs [ " capabilities " ] [ " textDocumentSync " ] [ " openClose " ] = true ;
2022-05-03 14:32:48 +00:00
replyArgs [ " capabilities " ] [ " semanticTokensProvider " ] [ " legend " ] = semanticTokensLegend ( ) ;
replyArgs [ " capabilities " ] [ " semanticTokensProvider " ] [ " range " ] = false ;
replyArgs [ " capabilities " ] [ " semanticTokensProvider " ] [ " full " ] = true ; // XOR requests.full.delta = true
2022-05-23 12:20:03 +00:00
replyArgs [ " capabilities " ] [ " renameProvider " ] = true ;
2021-12-13 13:53:40 +00:00
m_client . reply ( _id , move ( replyArgs ) ) ;
}
2022-06-13 13:56:55 +00:00
void LanguageServer : : handleInitialized ( MessageID , Json : : Value const & )
{
if ( m_fileLoadStrategy = = FileLoadStrategy : : ProjectDirectory )
compileAndUpdateDiagnostics ( ) ;
}
2022-05-03 14:32:48 +00:00
void LanguageServer : : semanticTokensFull ( MessageID _id , Json : : Value const & _args )
{
auto uri = _args [ " textDocument " ] [ " uri " ] ;
compile ( ) ;
auto const sourceName = m_fileRepository . uriToSourceUnitName ( uri . as < string > ( ) ) ;
SourceUnit const & ast = m_compilerStack . ast ( sourceName ) ;
m_compilerStack . charStream ( sourceName ) ;
Json : : Value data = SemanticTokensBuilder ( ) . build ( ast , m_compilerStack . charStream ( sourceName ) ) ;
Json : : Value reply = Json : : objectValue ;
reply [ " data " ] = data ;
m_client . reply ( _id , std : : move ( reply ) ) ;
}
2022-01-03 15:19:04 +00:00
void LanguageServer : : handleWorkspaceDidChangeConfiguration ( Json : : Value const & _args )
2021-12-13 13:53:40 +00:00
{
2022-01-03 15:19:04 +00:00
requireServerInitialized ( ) ;
2021-12-13 13:53:40 +00:00
if ( _args [ " settings " ] . isObject ( ) )
changeConfiguration ( _args [ " settings " ] ) ;
}
2022-04-05 11:38:59 +00:00
void LanguageServer : : setTrace ( Json : : Value const & _args )
{
2022-07-13 13:46:49 +00:00
if ( ! _args . isString ( ) )
2022-04-05 11:38:59 +00:00
// Simply ignore invalid parameter.
return ;
2022-07-13 13:46:49 +00:00
string const stringValue = _args . asString ( ) ;
2022-04-05 11:38:59 +00:00
if ( stringValue = = " off " )
m_client . setTrace ( TraceValue : : Off ) ;
else if ( stringValue = = " messages " )
m_client . setTrace ( TraceValue : : Messages ) ;
else if ( stringValue = = " verbose " )
m_client . setTrace ( TraceValue : : Verbose ) ;
}
2022-01-03 15:19:04 +00:00
void LanguageServer : : handleTextDocumentDidOpen ( Json : : Value const & _args )
2021-12-13 13:53:40 +00:00
{
2022-01-03 15:19:04 +00:00
requireServerInitialized ( ) ;
2021-12-13 13:53:40 +00:00
2022-01-05 10:30:04 +00:00
lspAssert (
_args [ " textDocument " ] ,
ErrorCode : : RequestFailed ,
" Text document parameter missing. "
) ;
2021-12-13 13:53:40 +00:00
string text = _args [ " textDocument " ] [ " text " ] . asString ( ) ;
string uri = _args [ " textDocument " ] [ " uri " ] . asString ( ) ;
m_openFiles . insert ( uri ) ;
2022-04-25 13:35:41 +00:00
m_fileRepository . setSourceByUri ( uri , move ( text ) ) ;
2021-12-13 13:53:40 +00:00
compileAndUpdateDiagnostics ( ) ;
}
2022-01-03 15:19:04 +00:00
void LanguageServer : : handleTextDocumentDidChange ( Json : : Value const & _args )
2021-12-13 13:53:40 +00:00
{
2022-01-03 15:19:04 +00:00
requireServerInitialized ( ) ;
2021-12-13 13:53:40 +00:00
string const uri = _args [ " textDocument " ] [ " uri " ] . asString ( ) ;
for ( Json : : Value jsonContentChange : _args [ " contentChanges " ] )
{
2022-01-05 10:30:04 +00:00
lspAssert (
jsonContentChange . isObject ( ) ,
ErrorCode : : RequestFailed ,
" Invalid content reference. "
) ;
2021-12-13 13:53:40 +00:00
2022-04-25 13:35:41 +00:00
string const sourceUnitName = m_fileRepository . uriToSourceUnitName ( uri ) ;
2022-01-05 10:30:04 +00:00
lspAssert (
m_fileRepository . sourceUnits ( ) . count ( sourceUnitName ) ,
ErrorCode : : RequestFailed ,
" Unknown file: " + uri
) ;
2021-12-13 13:53:40 +00:00
string text = jsonContentChange [ " text " ] . asString ( ) ;
if ( jsonContentChange [ " range " ] . isObject ( ) ) // otherwise full content update
{
2021-12-20 13:10:17 +00:00
optional < SourceLocation > change = parseRange ( m_fileRepository , sourceUnitName , jsonContentChange [ " range " ] ) ;
2022-01-05 10:30:04 +00:00
lspAssert (
change & & change - > hasText ( ) ,
ErrorCode : : RequestFailed ,
2022-03-07 04:25:35 +00:00
" Invalid source range: " + util : : jsonCompactPrint ( jsonContentChange [ " range " ] )
2022-01-05 10:30:04 +00:00
) ;
2022-01-03 15:19:04 +00:00
2021-12-13 13:53:40 +00:00
string buffer = m_fileRepository . sourceUnits ( ) . at ( sourceUnitName ) ;
buffer . replace ( static_cast < size_t > ( change - > start ) , static_cast < size_t > ( change - > end - change - > start ) , move ( text ) ) ;
text = move ( buffer ) ;
}
2022-04-25 13:35:41 +00:00
m_fileRepository . setSourceByUri ( uri , move ( text ) ) ;
2021-12-13 13:53:40 +00:00
}
compileAndUpdateDiagnostics ( ) ;
}
2022-01-03 15:19:04 +00:00
void LanguageServer : : handleTextDocumentDidClose ( Json : : Value const & _args )
2021-12-13 13:53:40 +00:00
{
2022-01-03 15:19:04 +00:00
requireServerInitialized ( ) ;
2021-12-13 13:53:40 +00:00
2022-01-05 10:30:04 +00:00
lspAssert (
_args [ " textDocument " ] ,
ErrorCode : : RequestFailed ,
" Text document parameter missing. "
) ;
2021-12-13 13:53:40 +00:00
string uri = _args [ " textDocument " ] [ " uri " ] . asString ( ) ;
m_openFiles . erase ( uri ) ;
compileAndUpdateDiagnostics ( ) ;
}
2022-01-05 11:15:10 +00:00
2022-05-23 12:20:03 +00:00
2021-12-20 13:10:17 +00:00
ASTNode const * LanguageServer : : astNodeAtSourceLocation ( std : : string const & _sourceUnitName , LineColumn const & _filePos )
2022-01-05 11:15:10 +00:00
{
if ( m_compilerStack . state ( ) < CompilerStack : : AnalysisPerformed )
return nullptr ;
if ( ! m_fileRepository . sourceUnits ( ) . count ( _sourceUnitName ) )
return nullptr ;
2021-12-20 13:10:17 +00:00
if ( optional < int > sourcePos =
m_compilerStack . charStream ( _sourceUnitName ) . translateLineColumnToPosition ( _filePos ) )
return locateInnermostASTNode ( * sourcePos , m_compilerStack . ast ( _sourceUnitName ) ) ;
else
2022-01-05 11:15:10 +00:00
return nullptr ;
}