Skip to content

Commit

Permalink
Add formatter support (#62)
Browse files Browse the repository at this point in the history
* Update polserver to 48191e9 (ecompile-diagnostics-formatting-for-extension-support)

* Add support for formatting document, document range

* Implement reopening of workspace to pick up ecompile.cfg changes

* Add tests

* Fix windows tests (line ending differences?)

* doc format-srcs test folder

* Update polserver to ca42cbc

* Update README.md
  • Loading branch information
KevinEady committed Mar 2, 2024
1 parent 3d27d1d commit 61e9c8b
Show file tree
Hide file tree
Showing 31 changed files with 532 additions and 23 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ native/test/scripts/ecompile.cfg
.vscode/c_cpp_properties.json
.vscode/settings.json
.cache
.DS_Store
1 change: 1 addition & 0 deletions .vscodeignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ client/node_modules/**
!client/node_modules/{minimatch,brace-expansion,concat-map,balanced-match}/**
!client/node_modules/{semver,lru-cache,yallist}/**
server/test/**
native/test/**
client/jest.e2e.config.js
server/jest.config.js
native/.clang-format
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ POL shards by providing:
- go to definition
- hover information
- references
- document and selection-based formatting
- debugger

## Setup
Expand Down Expand Up @@ -76,6 +77,17 @@ entire workspace.

![references](doc-assets/find-references.gif)

### Formatting

The extension provides support for both full-document as well as selection-based
formatting using the functionality provided by `ecompile`'s libraries.
Formatting can be configured via changing the options in the workspace's
`scripts/ecompile.cfg`. See [polserver
docs](https://docs.polserver.com/pol100/configfiles.php#ecompile.cfg) for
configuration options.

![format-selection](doc-assets/format-selection.gif)

### Debugger

POL 100.2.0 includes a Debug Adapter Protocol (DAP) server which the
Expand Down
Binary file added doc-assets/format-selection.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
120 changes: 120 additions & 0 deletions native/cpp/napi/LSPDocument.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
#include "bscript/compiler/file/SourceFileIdentifier.h"
#include "bscript/compiler/file/SourceLocation.h"
#include "bscript/compiler/model/CompilerWorkspace.h"
#include "bscript/compilercfg.h"
#include "clib/strutil.h"
#include <filesystem>

using namespace Pol::Bscript;

namespace VSCodeEscript
{
LSPDocument::LSPDocument( const Napi::CallbackInfo& info )
Expand Down Expand Up @@ -106,6 +108,13 @@ void LSPDocument::add_reference_by( const Compiler::Range& defined_at,
add_reference_by( defined_at, used_at.source_file_identifier->pathname, used_at.range );
}

Napi::Value LSPDocument::throwError( const std::string& what = "Invalid arguments" )
{
auto env = Value().Env();
Napi::TypeError::New( env, Napi::String::New( env, what ) ).ThrowAsJavaScriptException();
return Napi::Value();
}

Napi::Function LSPDocument::GetClass( Napi::Env env )
{
return DefineClass( env, "LSPDocument",
Expand All @@ -119,6 +128,7 @@ Napi::Function LSPDocument::GetClass( Napi::Env env )
LSPDocument::InstanceMethod( "references", &LSPDocument::References ),
LSPDocument::InstanceMethod( "toStringTree", &LSPDocument::ToStringTree ),
LSPDocument::InstanceMethod( "buildReferences", &LSPDocument::BuildReferences ),
LSPDocument::InstanceMethod( "toFormattedString", &LSPDocument::ToFormattedString ),
LSPDocument::InstanceMethod( "dependents", &LSPDocument::Dependents ) } );
}

Expand Down Expand Up @@ -594,4 +604,114 @@ Napi::Value LSPDocument::BuildReferences( const Napi::CallbackInfo& info )
return env.Undefined();
}

Napi::Value LSPDocument::ToFormattedString( const Napi::CallbackInfo& info )
{
auto env = info.Env();

std::optional<Compiler::Range> format_range;

unsigned short tabSize = compilercfg.FormatterTabWidth;
bool insertSpaces = !compilercfg.FormatterUseTabs;

if ( info.Length() > 0 && !info[0].IsUndefined() )
{
if ( !info[0].IsObject() )
{
return throwError();
}

auto optionsObj = info[0].As<Napi::Object>();

if ( optionsObj.Has( "tabSize" ) )
{
auto tabSizeValue = optionsObj.Get( "tabSize" );
if ( !tabSizeValue.IsNumber() )
{
return throwError();
}
tabSize = static_cast<unsigned short>( tabSizeValue.As<Napi::Number>().Int32Value() );
}

if ( optionsObj.Has( "insertSpaces" ) )
{
auto insertSpacesValue = optionsObj.Get( "insertSpaces" );
if ( !insertSpacesValue.IsBoolean() )
{
return throwError();
}
insertSpaces = insertSpacesValue.As<Napi::Boolean>().Value();
}
}

if ( info.Length() > 1 && !info[1].IsUndefined() )
{
if ( !info[1].IsObject() )
{
return throwError();
}

auto rangeObj = info[1].As<Napi::Object>();
if ( !rangeObj.Has( "start" ) || !rangeObj.Has( "end" ) )
{
return throwError();
}

auto startValue = rangeObj.Get( "start" );
auto endValue = rangeObj.Get( "end" );
if ( !startValue.IsObject() || !endValue.IsObject() )
{
return throwError();
}

auto startObj = startValue.As<Napi::Object>();
auto endObj = endValue.As<Napi::Object>();
if ( !startObj.Has( "line" ) || !startObj.Has( "character" ) || !endObj.Has( "line" ) ||
!endObj.Has( "character" ) )
{
return throwError();
}

auto startLineValue = startObj.Get( "line" );
auto startCharacterValue = startObj.Get( "character" );
auto endLineValue = endObj.Get( "line" );
auto endCharacterValue = endObj.Get( "character" );
if ( !startLineValue.IsNumber() || !startCharacterValue.IsNumber() ||
!endLineValue.IsNumber() || !endCharacterValue.IsNumber() )
{
return throwError();
}

unsigned short startLine = startLineValue.As<Napi::Number>().Int32Value();
unsigned short startCharacter = startCharacterValue.As<Napi::Number>().Int32Value();
unsigned short endLine = endLineValue.As<Napi::Number>().Int32Value();
unsigned short endCharacter = endCharacterValue.As<Napi::Number>().Int32Value();

format_range = Compiler::Range( Compiler::Position{ startLine, startCharacter },
Compiler::Position{ endLine, endCharacter } );
}


auto* lsp_workspace = LSPWorkspace::Unwrap( workspace.Value() );
auto compiler = lsp_workspace->make_compiler();

auto oldFormatterTabWidth = compilercfg.FormatterTabWidth;
auto oldFormatterUseTabs = compilercfg.FormatterUseTabs;
try
{
compilercfg.FormatterTabWidth = tabSize;
compilercfg.FormatterUseTabs = !insertSpaces;
auto formatted_string =
compiler->to_formatted_string( pathname(), type == LSPDocumentType::EM, format_range );
compilercfg.FormatterTabWidth = oldFormatterTabWidth;
compilercfg.FormatterUseTabs = oldFormatterUseTabs;
return Napi::String::New( env, formatted_string );
}
catch ( std::exception& ex )
{
compilercfg.FormatterTabWidth = oldFormatterTabWidth;
compilercfg.FormatterUseTabs = oldFormatterUseTabs;
return throwError(ex.what());
}
}

} // namespace VSCodeEscript
3 changes: 3 additions & 0 deletions native/cpp/napi/LSPDocument.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class LSPDocument : public Napi::ObjectWrap<LSPDocument>
Napi::Value References( const Napi::CallbackInfo& );
Napi::Value ToStringTree( const Napi::CallbackInfo& );
Napi::Value BuildReferences( const Napi::CallbackInfo& );
Napi::Value ToFormattedString( const Napi::CallbackInfo& );

std::unique_ptr<Pol::Bscript::Compiler::DiagnosticReporter> reporter;

Expand All @@ -66,6 +67,8 @@ class LSPDocument : public Napi::ObjectWrap<LSPDocument>
referenced_by;

private:
Napi::Value throwError( const std::string& what );

std::unique_ptr<Pol::Bscript::Compiler::Report> report;
std::unique_ptr<Pol::Bscript::Compiler::CompilerWorkspace> compiler_workspace;
std::string pathname_;
Expand Down
88 changes: 88 additions & 0 deletions native/cpp/napi/LSPWorkspace.cc
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Napi::Function LSPWorkspace::GetClass( Napi::Env env )
return DefineClass(
env, "LSPWorkspace",
{ LSPWorkspace::InstanceMethod( "open", &LSPWorkspace::Open ),
LSPWorkspace::InstanceMethod( "reopen", &LSPWorkspace::Reopen ),
LSPWorkspace::InstanceMethod( "getConfigValue", &LSPWorkspace::GetConfigValue ),
LSPWorkspace::InstanceAccessor( "workspaceRoot", &LSPWorkspace::GetWorkspaceRoot, nullptr ),
LSPWorkspace::InstanceAccessor( "scripts", &LSPWorkspace::AutoCompiledScripts, nullptr ),
Expand Down Expand Up @@ -252,6 +253,93 @@ Napi::Value LSPWorkspace::Open( const Napi::CallbackInfo& info )
return Napi::Value();
}

Napi::Value LSPWorkspace::Reopen( const Napi::CallbackInfo& info )
{
auto env = info.Env();

if ( _workspaceRoot.empty() )
{
Napi::Error::New( env, "Workspace was never open()'ed." ).ThrowAsJavaScriptException();
return Napi::Value();
}

std::string cfg( ( _workspaceRoot / "scripts" / "ecompile.cfg" ).u8string() );

try
{
bool has_changes = false;

auto ModuleDirectory = compilercfg.ModuleDirectory;
auto PolScriptRoot = compilercfg.PolScriptRoot;
auto IncludeDirectory = compilercfg.IncludeDirectory;

std::set<std::string> PackageRoot( compilercfg.PackageRoot.begin(),
compilercfg.PackageRoot.end() );

compilercfg.Read( cfg );

make_absolute( compilercfg.ModuleDirectory );
make_absolute( compilercfg.PolScriptRoot );
make_absolute( compilercfg.IncludeDirectory );

if ( ModuleDirectory.compare( compilercfg.ModuleDirectory ) != 0 )
has_changes = true;
else if ( PolScriptRoot.compare( compilercfg.PolScriptRoot ) != 0 )
has_changes = true;
else if ( IncludeDirectory.compare( compilercfg.IncludeDirectory ) != 0 )
has_changes = true;

for ( std::string& packageRoot : compilercfg.PackageRoot )
{
make_absolute( packageRoot );

if ( !has_changes )
{
auto existing = PackageRoot.find( packageRoot );

if ( existing == PackageRoot.end() )
{
has_changes = true;
}
}
}

if ( !has_changes && PackageRoot.size() != compilercfg.PackageRoot.size() )
{
has_changes = true;
}

if ( has_changes )
{
CompiledScripts.Reset();
_cache.clear();
Pol::Plib::systemstate.packages.clear();
Pol::Plib::systemstate.packages_byname.clear();

for ( const auto& elem : compilercfg.PackageRoot )
{
Pol::Plib::load_packages( elem, true /* quiet */ );
}
Pol::Plib::replace_packages();
Pol::Plib::check_package_deps();
}

return Napi::Boolean::New( env, has_changes );
}
catch ( const std::exception& ex )
{
_workspaceRoot = "";
Napi::Error::New( env, ex.what() ).ThrowAsJavaScriptException();
return Napi::Value();
}
catch ( ... )
{
_workspaceRoot = "";
Napi::Error::New( env, "Unknown Error" ).ThrowAsJavaScriptException();
return Napi::Value();
}
}

void LSPWorkspace::make_absolute( std::string& path )
{
std::filesystem::path filepath( path );
Expand Down
1 change: 1 addition & 0 deletions native/cpp/napi/LSPWorkspace.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class LSPWorkspace : public Napi::ObjectWrap<LSPWorkspace>,
static Napi::Function GetClass( Napi::Env );

Napi::Value Open( const Napi::CallbackInfo& );
Napi::Value Reopen( const Napi::CallbackInfo& );
Napi::Value GetConfigValue( const Napi::CallbackInfo& );
Napi::Value GetWorkspaceRoot( const Napi::CallbackInfo& );
Napi::Value AutoCompiledScripts( const Napi::CallbackInfo& );
Expand Down
2 changes: 1 addition & 1 deletion native/polserver
Submodule polserver updated 37 files
+2 −1 cmake/compile_defs.cmake
+1 −1 cmake/core_tests.cmake
+29 −4 cmake/escripttest.cmake
+32 −10 docs/docs.polserver.com/pol100/configfiles.xml
+19 −1 docs/docs.polserver.com/pol100/corechanges.xml
+6 −0 pol-core/bscript/CMakeSources.cmake
+34 −0 pol-core/bscript/compiler/Compiler.cpp
+5 −0 pol-core/bscript/compiler/Compiler.h
+6 −5 pol-core/bscript/compiler/codegen/InstructionGenerator.cpp
+43 −0 pol-core/bscript/compiler/file/PrettifyBuilder.cpp
+25 −0 pol-core/bscript/compiler/file/PrettifyBuilder.h
+1,593 −0 pol-core/bscript/compiler/file/PrettifyFileProcessor.cpp
+206 −0 pol-core/bscript/compiler/file/PrettifyFileProcessor.h
+510 −0 pol-core/bscript/compiler/file/PrettifyLineBuilder.cpp
+129 −0 pol-core/bscript/compiler/file/PrettifyLineBuilder.h
+1 −1 pol-core/bscript/compiler/file/SourceFile.h
+18 −18 pol-core/bscript/compiler/file/SourceLocation.cpp
+13 −14 pol-core/bscript/compiler/file/SourceLocation.h
+26 −0 pol-core/bscript/compilercfg.cpp
+19 −0 pol-core/bscript/compilercfg.h
+1 −1 pol-core/clib/fdump.cpp
+5 −0 pol-core/clib/filecont.cpp
+3 −2 pol-core/clib/filecont.h
+12 −0 pol-core/doc/core-changes.txt
+93 −12 pol-core/ecompile/ECompileMain.cpp
+43 −2 pol-core/pol/login.cpp
+30 −4 pol-core/pol/network/client.cpp
+14 −4 pol-core/pol/network/client.h
+5 −0 pol-core/pol/network/clientio.cpp
+111 −0 pol-core/pol/network/clientthread.cpp
+162 −0 pol-core/pol/network/proxyprotocol.h
+2 −0 pol-core/pol/servdesc.h
+20 −0 pol-core/pol/uimport.cpp
+17 −0 pol-core/pol/uoclient.cpp
+3 −0 pol-core/pol/uoclient.h
+3 −2 pol-core/pol/uolisten.cpp
+80 −0 pol-core/support/scripts/ecompile.cfg.example
6 changes: 4 additions & 2 deletions native/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { resolve } from 'path';
import { existsSync } from 'fs';
import type { Diagnostic, Position, Range, CompletionItem, Location } from 'vscode-languageserver-types';
import type { Diagnostic, Position, Range, CompletionItem, Location, FormattingOptions } from 'vscode-languageserver-types';

// The native module uses this specific format for a SignatureHelp
export type ParameterInformation = {
Expand Down Expand Up @@ -28,6 +28,7 @@ export interface LSPWorkspace {
new(config: LSPWorkspaceConfig): LSPWorkspace;
workspaceRoot: string;
open(workspaceRoot: string): void;
reopen(): boolean; // `true` if folder changes occurred in scripts/ecompile.cfg
getConfigValue(key: 'PackageRoot'): Array<string>;
getConfigValue(key: 'IncludeDirectory' | 'ModuleDirectory' | 'PolScriptRoot'): string;
scripts: { inc: string[], src: string[] };
Expand All @@ -47,6 +48,7 @@ export interface LSPDocument {
definition(position: Position, options?: { nameOnly?: boolean }): { range: Range, fsPath: string } | undefined;
references(position: Position): { range: Range, fsPath: string }[] | undefined;
signatureHelp(position: Position): SignatureHelp | undefined;
toFormattedString(options?: Partial<Pick<FormattingOptions, "tabSize"|"insertSpaces">>, formatRange?: Range): string; // throws
tokens(): [line: number, startChar: number, length: number, tokenType: number, tokenModifiers: number][];
toStringTree(): string | undefined;
buildReferences(): undefined;
Expand Down Expand Up @@ -89,7 +91,7 @@ const filename = tries.find(filepath => existsSync(filepath));

/* istanbul ignore next */
if (!filename) {
throw new Error(`Unable to locate ${baseFilename}`);
throw new Error(`Unable to locate ${baseFilename}, tried ${tries.join('; ')}`);
}

export type UpdateCacheProgressCallback = (progress: { count: number, total: number }) => void;
Expand Down
5 changes: 5 additions & 0 deletions native/test/format-srcs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# format-srcs

Sources here (`.src`) are formatted and compared to their formatted source
(`.out.src`). The format range is defined by the the range between the two
hash-tag characters, `#`.
4 changes: 4 additions & 0 deletions native/test/format-srcs/fmt01.out.src
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Format whole file.
var a;
var b;
var c;
4 changes: 4 additions & 0 deletions native/test/format-srcs/fmt01.src
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Format whole file.
VAR a;
VAR b;
VAR c;
8 changes: 8 additions & 0 deletions native/test/format-srcs/fmt02.out.src
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Case 1: `format_range` is completely inside a skipline.
VAR a;
// format-off
VAR b;
VAR c;
// format-on
VAR d;
VAR e;
8 changes: 8 additions & 0 deletions native/test/format-srcs/fmt02.src
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Case 1: `format_range` is completely inside a skipline.
VAR a;
// format-off
#VAR b;#
VAR c;
// format-on
VAR d;
VAR e;

0 comments on commit 61e9c8b

Please sign in to comment.