2020-10-03 17:25:51 +03:30
/*
* Copyright ( c ) 2020 , the SerenityOS developers .
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-10-03 17:25:51 +03:30
*/
2021-05-16 17:25:24 +03:00
# include "ShellComprehensionEngine.h"
2021-03-02 09:40:10 +03:30
# include <AK/Assertions.h>
2020-10-03 17:25:51 +03:30
# include <AK/HashTable.h>
2021-03-02 09:40:10 +03:30
# include <LibRegex/Regex.h>
2020-10-03 17:25:51 +03:30
2022-05-14 17:09:24 +03:00
namespace CodeComprehension : : Shell {
2020-10-03 17:25:51 +03:30
2021-05-16 17:25:24 +03:00
RefPtr < : : Shell : : Shell > ShellComprehensionEngine : : s_shell { } ;
2021-03-02 09:40:10 +03:30
2022-04-01 20:58:27 +03:00
ShellComprehensionEngine : : ShellComprehensionEngine ( FileDB const & filedb )
2021-05-16 17:25:24 +03:00
: CodeComprehensionEngine ( filedb , true )
2021-03-02 09:40:10 +03:30
{
}
2022-12-04 18:02:33 +00:00
ShellComprehensionEngine : : DocumentData const & ShellComprehensionEngine : : get_or_create_document_data ( DeprecatedString const & file )
2021-03-02 09:40:10 +03:30
{
auto absolute_path = filedb ( ) . to_absolute_path ( file ) ;
if ( ! m_documents . contains ( absolute_path ) ) {
set_document_data ( absolute_path , create_document_data_for ( absolute_path ) ) ;
}
return get_document_data ( absolute_path ) ;
}
2022-12-04 18:02:33 +00:00
ShellComprehensionEngine : : DocumentData const & ShellComprehensionEngine : : get_document_data ( DeprecatedString const & file ) const
2021-03-02 09:40:10 +03:30
{
auto absolute_path = filedb ( ) . to_absolute_path ( file ) ;
auto document_data = m_documents . get ( absolute_path ) ;
VERIFY ( document_data . has_value ( ) ) ;
return * document_data . value ( ) ;
}
2022-12-04 18:02:33 +00:00
OwnPtr < ShellComprehensionEngine : : DocumentData > ShellComprehensionEngine : : create_document_data_for ( DeprecatedString const & file )
2021-03-02 09:40:10 +03:30
{
2022-05-14 17:09:24 +03:00
auto document = filedb ( ) . get_or_read_from_filesystem ( file ) ;
if ( ! document . has_value ( ) )
2021-03-02 09:40:10 +03:30
return { } ;
2022-05-14 17:09:24 +03:00
auto content = document . value ( ) ;
auto document_data = make < DocumentData > ( move ( content ) , file ) ;
2021-03-02 09:40:10 +03:30
for ( auto & path : document_data - > sourced_paths ( ) )
get_or_create_document_data ( path ) ;
update_declared_symbols ( * document_data ) ;
2021-03-17 16:30:02 +01:00
return document_data ;
2021-03-02 09:40:10 +03:30
}
2022-12-04 18:02:33 +00:00
void ShellComprehensionEngine : : set_document_data ( DeprecatedString const & file , OwnPtr < DocumentData > & & data )
2021-03-02 09:40:10 +03:30
{
m_documents . set ( filedb ( ) . to_absolute_path ( file ) , move ( data ) ) ;
}
2022-12-04 18:02:33 +00:00
ShellComprehensionEngine : : DocumentData : : DocumentData ( DeprecatedString & & _text , DeprecatedString _filename )
2021-03-02 09:40:10 +03:30
: filename ( move ( _filename ) )
, text ( move ( _text ) )
, node ( parse ( ) )
{
}
2022-12-04 18:02:33 +00:00
Vector < DeprecatedString > const & ShellComprehensionEngine : : DocumentData : : sourced_paths ( ) const
2021-03-02 09:40:10 +03:30
{
if ( all_sourced_paths . has_value ( ) )
return all_sourced_paths . value ( ) ;
struct : public : : Shell : : AST : : NodeVisitor {
2022-10-17 00:06:11 +02:00
void visit ( : : Shell : : AST : : CastToCommand const * node ) override
2021-03-02 09:40:10 +03:30
{
auto & inner = node - > inner ( ) ;
if ( inner - > is_list ( ) ) {
2022-10-17 00:06:11 +02:00
if ( auto * list = dynamic_cast < : : Shell : : AST : : ListConcatenate const * > ( inner . ptr ( ) ) ) {
2021-03-02 09:40:10 +03:30
auto & entries = list - > list ( ) ;
if ( entries . size ( ) = = 2 & & entries . first ( ) - > is_bareword ( ) & & static_ptr_cast < : : Shell : : AST : : BarewordLiteral > ( entries . first ( ) ) - > text ( ) = = " source " ) {
auto & filename = entries [ 1 ] ;
if ( filename - > would_execute ( ) )
return ;
2023-02-18 17:27:14 +03:30
auto name_list_node = const_cast < : : Shell : : AST : : Node * > ( filename . ptr ( ) ) - > run ( nullptr ) . release_value_but_fixme_should_propagate_errors ( ) ;
auto name_list = name_list_node - > resolve_as_list ( nullptr ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-03-02 09:40:10 +03:30
StringBuilder builder ;
2022-07-11 20:10:18 +00:00
builder . join ( ' ' , name_list ) ;
2023-01-26 18:58:09 +00:00
sourced_files . set ( builder . to_deprecated_string ( ) ) ;
2021-03-02 09:40:10 +03:30
}
}
}
: : Shell : : AST : : NodeVisitor : : visit ( node ) ;
}
2022-12-04 18:02:33 +00:00
HashTable < DeprecatedString > sourced_files ;
2021-03-02 09:40:10 +03:30
} visitor ;
node - > visit ( visitor ) ;
2022-12-04 18:02:33 +00:00
Vector < DeprecatedString > sourced_paths ;
2021-03-02 09:40:10 +03:30
for ( auto & entry : visitor . sourced_files )
sourced_paths . append ( move ( entry ) ) ;
all_sourced_paths = move ( sourced_paths ) ;
return all_sourced_paths . value ( ) ;
}
2021-05-16 17:25:24 +03:00
NonnullRefPtr < : : Shell : : AST : : Node > ShellComprehensionEngine : : DocumentData : : parse ( ) const
2021-03-02 09:40:10 +03:30
{
: : Shell : : Parser parser { text } ;
if ( auto node = parser . parse ( ) )
return node . release_nonnull ( ) ;
2023-02-18 10:15:08 +03:30
return : : Shell : : AST : : make_ref_counted < : : Shell : : AST : : SyntaxError > ( : : Shell : : AST : : Position { } , String : : from_utf8 ( " Unable to parse file " sv ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2021-03-02 09:40:10 +03:30
}
2022-04-01 20:58:27 +03:00
size_t ShellComprehensionEngine : : resolve ( ShellComprehensionEngine : : DocumentData const & document , const GUI : : TextPosition & position )
2020-10-03 17:25:51 +03:30
{
2021-03-02 09:40:10 +03:30
size_t offset = 0 ;
if ( position . line ( ) > 0 ) {
auto first = true ;
size_t line = 0 ;
2022-10-22 15:38:21 +02:00
for ( auto & line_view : document . text . split_limit ( ' \n ' , position . line ( ) + 1 , SplitBehavior : : KeepEmpty ) ) {
2021-03-02 09:40:10 +03:30
if ( line = = position . line ( ) )
break ;
if ( first )
first = false ;
else
+ + offset ; // For the newline.
offset + = line_view . length ( ) ;
+ + line ;
}
}
offset + = position . column ( ) + 1 ;
return offset ;
}
2022-12-04 18:02:33 +00:00
Vector < CodeComprehension : : AutocompleteResultEntry > ShellComprehensionEngine : : get_suggestions ( DeprecatedString const & file , const GUI : : TextPosition & position )
2021-03-02 09:40:10 +03:30
{
2021-05-16 17:25:24 +03:00
dbgln_if ( SH_LANGUAGE_SERVER_DEBUG , " ShellComprehensionEngine position {}:{} " , position . line ( ) , position . column ( ) ) ;
2021-03-02 09:40:10 +03:30
2022-04-01 20:58:27 +03:00
auto const & document = get_or_create_document_data ( file ) ;
2021-03-02 09:40:10 +03:30
size_t offset_in_file = resolve ( document , position ) ;
: : Shell : : AST : : HitTestResult hit_test = document . node - > hit_test_position ( offset_in_file ) ;
if ( ! hit_test . matching_node ) {
dbgln_if ( SH_LANGUAGE_SERVER_DEBUG , " no node at position {}:{} " , position . line ( ) , position . column ( ) ) ;
return { } ;
}
2023-02-18 17:27:14 +03:30
auto completions = const_cast < : : Shell : : AST : : Node * > ( document . node . ptr ( ) ) - > complete_for_editor ( shell ( ) , offset_in_file , hit_test ) . release_value_but_fixme_should_propagate_errors ( ) ;
2022-05-14 17:09:24 +03:00
Vector < CodeComprehension : : AutocompleteResultEntry > entries ;
2021-03-02 09:40:10 +03:30
for ( auto & completion : completions )
entries . append ( { completion . text_string , completion . input_offset } ) ;
return entries ;
}
2022-12-04 18:02:33 +00:00
void ShellComprehensionEngine : : on_edit ( DeprecatedString const & file )
2021-03-02 09:40:10 +03:30
{
set_document_data ( file , create_document_data_for ( file ) ) ;
}
2022-12-04 18:02:33 +00:00
void ShellComprehensionEngine : : file_opened ( [[maybe_unused]] DeprecatedString const & file )
2021-03-02 09:40:10 +03:30
{
set_document_data ( file , create_document_data_for ( file ) ) ;
}
2022-12-04 18:02:33 +00:00
Optional < CodeComprehension : : ProjectLocation > ShellComprehensionEngine : : find_declaration_of ( DeprecatedString const & filename , const GUI : : TextPosition & identifier_position )
2021-03-02 09:40:10 +03:30
{
2021-04-29 21:46:15 +02:00
dbgln_if ( SH_LANGUAGE_SERVER_DEBUG , " find_declaration_of({}, {}:{}) " , filename , identifier_position . line ( ) , identifier_position . column ( ) ) ;
2022-04-01 20:58:27 +03:00
auto const & document = get_or_create_document_data ( filename ) ;
2021-03-02 09:40:10 +03:30
auto position = resolve ( document , identifier_position ) ;
auto result = document . node - > hit_test_position ( position ) ;
if ( ! result . matching_node ) {
dbgln_if ( SH_LANGUAGE_SERVER_DEBUG , " no node at position {}:{} " , identifier_position . line ( ) , identifier_position . column ( ) ) ;
2020-10-03 17:25:51 +03:30
return { } ;
2021-03-02 09:40:10 +03:30
}
2020-10-03 17:25:51 +03:30
2021-03-02 09:40:10 +03:30
if ( ! result . matching_node - > is_bareword ( ) ) {
dbgln_if ( SH_LANGUAGE_SERVER_DEBUG , " no bareword at position {}:{} " , identifier_position . line ( ) , identifier_position . column ( ) ) ;
return { } ;
}
2023-02-20 18:26:54 +01:00
auto name = static_ptr_cast < : : Shell : : AST : : BarewordLiteral const > ( result . matching_node ) - > text ( ) ;
2021-03-02 09:40:10 +03:30
auto & declarations = all_declarations ( ) ;
for ( auto & entry : declarations ) {
for ( auto & declaration : entry . value ) {
2023-02-18 10:15:08 +03:30
if ( declaration . name . view ( ) = = name )
2021-03-02 09:40:10 +03:30
return declaration . position ;
}
2020-10-03 17:25:51 +03:30
}
2021-03-02 09:40:10 +03:30
return { } ;
2020-10-03 17:25:51 +03:30
}
2022-04-01 20:58:27 +03:00
void ShellComprehensionEngine : : update_declared_symbols ( DocumentData const & document )
2021-03-02 09:40:10 +03:30
{
struct Visitor : public : : Shell : : AST : : NodeVisitor {
2022-12-04 18:02:33 +00:00
explicit Visitor ( DeprecatedString const & filename )
2021-03-02 09:40:10 +03:30
: filename ( filename )
{
}
2022-10-17 00:06:11 +02:00
void visit ( : : Shell : : AST : : VariableDeclarations const * node ) override
2021-03-02 09:40:10 +03:30
{
for ( auto & entry : node - > variables ( ) ) {
auto literal = entry . name - > leftmost_trivial_literal ( ) ;
if ( ! literal )
continue ;
2022-12-04 18:02:33 +00:00
DeprecatedString name ;
2021-03-02 09:40:10 +03:30
if ( literal - > is_bareword ( ) )
2023-02-18 10:15:08 +03:30
name = static_ptr_cast < : : Shell : : AST : : BarewordLiteral const > ( literal ) - > text ( ) . to_deprecated_string ( ) ;
2021-03-02 09:40:10 +03:30
if ( ! name . is_empty ( ) ) {
dbgln ( " Found variable {} " , name ) ;
2022-05-14 17:09:24 +03:00
declarations . append ( { move ( name ) , { filename , entry . name - > position ( ) . start_line . line_number , entry . name - > position ( ) . start_line . line_column } , CodeComprehension : : DeclarationType : : Variable , { } } ) ;
2021-03-02 09:40:10 +03:30
}
}
: : Shell : : AST : : NodeVisitor : : visit ( node ) ;
}
2022-10-17 00:06:11 +02:00
void visit ( : : Shell : : AST : : FunctionDeclaration const * node ) override
2021-03-02 09:40:10 +03:30
{
dbgln ( " Found function {} " , node - > name ( ) . name ) ;
2023-02-18 10:15:08 +03:30
declarations . append ( { node - > name ( ) . name . to_deprecated_string ( ) , { filename , node - > position ( ) . start_line . line_number , node - > position ( ) . start_line . line_column } , CodeComprehension : : DeclarationType : : Function , { } } ) ;
2021-03-02 09:40:10 +03:30
}
2022-12-04 18:02:33 +00:00
DeprecatedString const & filename ;
2022-05-14 17:09:24 +03:00
Vector < CodeComprehension : : Declaration > declarations ;
2021-03-02 09:40:10 +03:30
} visitor { document . filename } ;
document . node - > visit ( visitor ) ;
set_declarations_of_document ( document . filename , move ( visitor . declarations ) ) ;
}
2020-10-03 17:25:51 +03:30
}