Skip to content

Commit

Permalink
Migration to vscode engine 1.46 & fixes (pgourlain#166)
Browse files Browse the repository at this point in the history
* Fix external debugger lunch error by removing vscode dependencies

* update to vscode engine 1.46

* Add inline debugerAdpater implementation

* exclude breakpoint set in .src file

* compile arguments file  into memory to avoid unwanted beam file

* Add help.md

* Fix pgourlain#106

* fix pgourlain#148 : Add 'plugins' path to includepaths

* Goto definition on export atoms (pgourlain#146)

* Changelog
  • Loading branch information
pgourlain authored and eboutin committed Sep 23, 2020
1 parent 7f3f11b commit 2020087
Show file tree
Hide file tree
Showing 21 changed files with 328 additions and 53 deletions.
13 changes: 13 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@
"request": "attach",
"port": 6011
},
{
"name": "debugger Server",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"program": "${workspaceFolder}/lib/erlangDebug.ts",
"args": [
"--server=4711"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
]
},
{
"name": "Launch Extension",
"type": "extensionHost",
Expand Down
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
# Change log

## Version 0.6.4 (June 25, 2020)
## Version 0.6.4 (July 13, 2020)

* Fix [#146](https://github.com/pgourlain/vscode_erlang/issues/146) : Go to definition doesn't work from the export directive
* Fix [#138](https://github.com/pgourlain/vscode_erlang/issues/138) : Add support of parse_transform on syntax tree if present in compile attribute
* Fix [#140](https://github.com/pgourlain/vscode_erlang/issues/140) : breakpoints set in non erlang files are exclude on startup
* Fix [#106](https://github.com/pgourlain/vscode_erlang/issues/106) : exclude function that endswith "_test" in warnings
* Fix [#148](https://github.com/pgourlain/vscode_erlang/issues/148) : Add rebar plugins path in includepaths to resolve include_lib
* Migrate to vscode engine 1.46
* Add 'inline' DebuggerMode in config
* Add help.md link in readme.md that provide some tricks about erlang extension configuration.


## Version 0.6.3 (June 22, 2020)

Expand Down
21 changes: 21 additions & 0 deletions HELP.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

# Introduction
This file contains some help about Erlang extension.


# Changing Debugger Mode
- Debugger Mode setting is read at extension startup, so when you change this setting, you must restart vscode.

# How exclude directories from "goto definition" feature

Add in your local settings.json this section

```
{
...
"search.exclude" : {
"**/_build": true
}
...
}
```
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ Support for Erlang tools, including rebar3, EUnit and Dialyzer
- `erlang.codeLensEnabled` - Enable/Disable CodeLens
- `erlang.verbose` - Activate technical traces for use in the extension development

## Help

[Some configuration tricks](./HELP.MD)

## Credits

File 'Erlang.tmLanguage' is inspired from <https://github.com/textmate/erlang.tmbundle/blob/master/Syntaxes/Erlang.plist>
131 changes: 130 additions & 1 deletion apps/erlangbridge/src/lsp_navigation.erl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,129 @@ goto_definition(File, Line, Column) ->
range => lsp_utils:client_range(FoundLine, FoundColumn, FoundColumn)
};
_ ->
throw(<<"Definition not found">>)
%try to find if user press F12 in export attribute
What1 = read_and_analyse_from_tokens(File, Module, Line, Column),
case find_element(What1, FileSyntaxTree, File) of
{FoundFile1, FoundLine1, FoundColumn1} ->
#{
uri => lsp_utils:file_uri_to_vscode_uri(list_to_binary("file://" ++ FoundFile1)),
range => lsp_utils:client_range(FoundLine1, FoundColumn1, FoundColumn1)
};
_ ->
throw(<<"Definition not found">>)
end
end.

read_and_analyse_from_tokens(File, Module, Line, Column) ->
%% parse file to get all tokens with all informations (Line and Column)
case file:open(File, [read]) of
{ok, FIO} ->
FileSize = filelib:file_size(File),
Ret = case file:read(FIO, FileSize) of
{ok, Data} ->
Scan = do_scan(lsp_utils:to_string(Data), [], Line, Column,{1,1}),
%gen_lsp_server:lsp_log("scan:~p",[Scan]),
TargetExport = try case analyse_export(Scan, Module, Line, Column) of
{notfound} -> {notfound};
Other -> Other
end
catch
Err:M:STK ->
gen_lsp_server:lsp_log("read_and_analyse_from_tokens exception:~p,~p,~p",[Err,M,STK]),
{notfound}
end,
%gen_lsp_server:lsp_log("read_and_analyse_from_tokens:~p",[TargetExport]),
TargetExport;
_ -> { error, ""}
end,
file:close(FIO),
Ret;
_ ->
{error, could_not_open_file, File}
end.


analyse_export(Tokens, Module, Line, Column) ->
%first find export attribute
{_,_,ExportTokens} = lists:foldl(fun(Token, ExportAcc) ->
{IsAttrStart, ExportFound, Ts} = ExportAcc,
case Token of
{'-', _} ->
if
IsAttrStart andalso ExportFound -> ExportAcc;
true -> {true, false, [Token]}
end;
{atom, _, export} -> case IsAttrStart of
true -> {true, true, Ts ++ [Token]};
_ -> {false, false, Ts}
end;
_ -> if
IsAttrStart andalso ExportFound -> {true, true, Ts ++ [Token]};
true -> ExportAcc
end
end
end, {false, false, []}, Tokens),
%{'[',{4,9}},{atom,{4,10},functionA},{'/',{4,19}},{integer,{4,20},0},{',',{4,21}},
%{atom,{5,5},function_test},{'/',{5,18}},{integer,{5,19},0},{']',{5,20}},
%%keep only export tokens
T1 = lists:dropwhile(fun(X) ->
case X of
{'[', _} -> false;
_ -> true
end
end, ExportTokens),
T2 = lists:takewhile(fun(X) ->
case X of
{']', _} -> false;
_ -> true
end
end, T1),
%%Fns = [{functionA,{4,10},0},{function_test,{5,5},0}]
{_,_,Fns} = lists:foldl(fun(Token, Acc) ->
{F,LC, Arr} = Acc,
case Token of
{atom, {L,C}, FnAtomAme} -> {FnAtomAme, {L,C}, Arr};
{integer, _, Arity} -> {1,1, Arr ++ [{F, LC, Arity}]};
_ -> Acc
end
end, {1,1,[]}, T2),
%gen_lsp_server:lsp_log("analyse_export fns:~p", [Fns]),
%% filter only Fns that are in line and column
TargetFns = lists:filter(fun({Atom, {L,C}, _}) ->
EndColumn = C + length(atom_to_list(Atom)),
if
Line =:= L andalso C =< Column andalso Column =< EndColumn -> true;
true -> false
end
end, Fns),
%take the first one
TargetToken = lists:nth(1, TargetFns),
%gen_lsp_server:lsp_log("analyse_export TargetToken:~p", [TargetToken]),
% %% match tokens sequence for attribute
% Export = [{'-', _}, {atom, export, _}],
%{atom,{4,10},functionA}
case TargetToken of
{FnName, _, Arity} ->
%{L1, C1} = LC,
{function_use, Module, FnName, Arity};
%{ok, L1, C1};
_ -> {notfound}
end.


%% scan string into tokens until EndLine, EndColumn is reached
do_scan(Data, ScannedTokens, EndLine, EndColumn, StartLocation) ->
case erl_scan:tokens([], lsp_utils:to_string(Data), StartLocation, []) of
{done, Result, LeftOverChars} ->
case Result of
{ok, Tokens, {L,C}} ->
if
L >= EndLine andalso C >= EndColumn -> ScannedTokens;
true -> do_scan(LeftOverChars, ScannedTokens ++ Tokens, EndLine, EndColumn, {L,C})
end;
_ -> ScannedTokens
end;
_ -> ScannedTokens
end.

hover_info(File, Line, Column) ->
Expand Down Expand Up @@ -238,6 +360,8 @@ join_strings([String|Rest], Joiner) ->
String ++ Joiner ++ join_strings(Rest, Joiner).

fold_in_file_syntax_tree(FileSyntaxTree, StartAcc, Fun) ->
% FileStyntaxTree is [] of TopLevelStyntaxTree
% post-order traversal, then
lists:foldl(fun (TopLevelSyntaxTree, Acc) ->
erl_syntax_lib:fold(Fun, Acc, TopLevelSyntaxTree)
end, StartAcc, FileSyntaxTree).
Expand Down Expand Up @@ -265,6 +389,10 @@ element_at_position(CurrentModule, FileSyntaxTree, Line, Column) ->
Column =< EndColumn -> {function_use, CurrentModule, Function, length(Args)};
true -> undefined
end;
({attribute, {L, StartColumn}, export, _Exports}, _) when L =:= Line andalso StartColumn =< Column ->
%% return function_use on export
%% missing line and column info on exported atoms
undefined;
({call, {_, _}, {remote, {_, _}, {atom, {_, MStartColumn}, Module}, {atom, {L, StartColumn}, Function}}, Args}, _) when L =:= Line andalso MStartColumn =< Column ->
MEndColumn = MStartColumn + length(atom_to_list(Module)),
EndColumn = StartColumn + length(atom_to_list(Module)) + 1 + length(atom_to_list(Function)),
Expand Down Expand Up @@ -315,6 +443,7 @@ element_at_position(CurrentModule, FileSyntaxTree, Line, Column) ->
(_SyntaxTree, _File) ->
undefined
end,
%gen_lsp_server:lsp_log("element_at_position:~p", [FileSyntaxTree]),
{What, _File} = find_in_file_syntax_tree(FileSyntaxTree, Fun),
What.

Expand Down
40 changes: 36 additions & 4 deletions apps/erlangbridge/src/lsp_syntax.erl
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ get_standard_include_paths() ->
RootDir = gen_lsp_config_server:root(),
[
filename:join([RootDir, "_build", "default", "lib"]),
filename:join([RootDir, "_build", "default", "plugins"]),
filename:join([RootDir, "apps"]),
filename:join([RootDir, "lib"])
].
Expand Down Expand Up @@ -255,8 +256,19 @@ get_include_paths_from_rebar_config(File) ->
_ ->
[]
end,
Deps = [],
%TODO: if include of each rebar dependency should be add to include paths, uncomment below
% Deps = case Consult of
% {ok, DepsTerms} ->
% Profiles = proplists:get_value(profiles, DepsTerms, []),
% ErlDeps = lists:flatmap(fun({_,Opts})-> proplists:get_value(deps,Opts) end, Profiles),
% RootDir = gen_lsp_config_server:root(),
% [filename:join([RootDir, "_build", "default", "plugins", X, "include"]) || X <- ErlDeps ];
% _ ->
% []
% end,
DefaultPaths = [filename:dirname(RebarConfig), filename:join([filename:dirname(RebarConfig), "include"])],
ErlOptsPaths ++ DefaultPaths
ErlOptsPaths ++ Deps ++ DefaultPaths
end.

find_rebar_config(Dir) ->
Expand Down Expand Up @@ -301,7 +313,7 @@ lint(FileSyntaxTree, File) ->
{ok, [Warnings]} ->
#{parse_result => true,
errors_warnings =>
extract_error_or_warning(<<"warning">>, Warnings)};
extract_error_or_warning(<<"warning">>, filter_unused_functions(Warnings))};
% errors, no warnings
{error, [Errors], []} ->
#{parse_result => true,
Expand All @@ -312,15 +324,35 @@ lint(FileSyntaxTree, File) ->
#{parse_result => true,
errors_warnings =>
extract_error_or_warning(<<"error">>, Errors) ++
extract_error_or_warning(<<"warning">>, Warnings)};
extract_error_or_warning(<<"warning">>, filter_unused_functions(Warnings))};
{error, [], [Warnings]} ->
#{parse_result => true,
errors_warnings =>
extract_error_or_warning(<<"warning">>, Warnings)};
extract_error_or_warning(<<"warning">>, filter_unused_functions(Warnings))};
_Any ->
#{parse_result => false, error_message => <<"lint error">>}
end.

filter_unused_functions({_, []}) ->
[];
filter_unused_functions({File, Warnings}) ->
%Filter unused function that ends with "_test", to avoid unwanted warnings in unit tests modules
%%TODO: if more than one filter to exclude, it should be configurable
Result = {
File,
lists:filter(fun (X) ->
case X of
{_, _, {unused_function, {FuncName,_}}} ->
FindResult = string:find(atom_to_list(FuncName), "_test", trailing) =/= "_test",
%gen_lsp_server:lsp_log("FuncName:~p, ~p",[FuncName, FindResult]),
FindResult;
_ -> true
end
end,
Warnings)
},
Result.

extract_error_or_warning(_Type, {_, []}) ->
[];
extract_error_or_warning(Type, ErrorsOrWarnings) ->
Expand Down
18 changes: 14 additions & 4 deletions apps/erlangbridge/src/vscode_connection.erl
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,21 @@ get_port() ->
compile_argumentsfile() ->
case init:get_argument(compiled_args_file) of
{ok, [[FileName]]} ->
io:format("Compiling arguments file ~p~n", [FileName]),
%compile file to interpret int:ni(..), list of breapoints int:break(...)
{ok, NewModule} = c:c(FileName),
%call :configure to execute int:ni,int:break,...
NewModule:configure(),
ok;
case compile:file(FileName, [binary]) of
{ok, ModuleName, Binary} ->
io:format("Compile result: sucess ~n", []),
case code:load_binary(ModuleName, lists:flatten(io_lib:format("~p.beam", [ModuleName])), Binary) of
{module, _} ->
io:format("Module ~p loaded~n", [ModuleName]),
ModuleName:configure(), ok;
_ -> no_compiled_args_file
end;
Error ->
io:format("Compile result: failed ~p~n", [Error]),
no_compiled_args_file
end;
_ ->
no_compiled_args_file
end.
Expand Down
5 changes: 2 additions & 3 deletions lib/ErlangAdapterDescriptorFactory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
DebugAdapterDescriptorFactory, DebugSession, DebugAdapterExecutable, ProviderResult, DebugAdapterDescriptor,
DebugAdapterServer
DebugAdapterServer, DebugAdapterInlineImplementation
} from 'vscode';

import { AddressInfo, Server, createServer } from 'net';
Expand Down Expand Up @@ -31,8 +31,7 @@ export class ErlangDebugAdapterDescriptorFactory implements DebugAdapterDescript

export class InlineErlangDebugAdapterFactory implements DebugAdapterDescriptorFactory {
createDebugAdapterDescriptor(session: DebugSession, executable: DebugAdapterExecutable): ProviderResult<DebugAdapterDescriptor> {
//return <any>new DebugAdapterInlineImplementation(new ErlangDebugSession(true));
throw new Error("Method not implemented.");
return new DebugAdapterInlineImplementation(new ErlangDebugSession(true));
}
}

Expand Down
38 changes: 36 additions & 2 deletions lib/ErlangConfigurationProvider.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,45 @@
import {
workspace, WorkspaceFolder, DebugConfiguration, DebugConfigurationProvider, CancellationToken, ProviderResult
} from 'vscode';
import { ErlangSettings } from './erlangSettings';

export class ErlangDebugConfigurationProvider implements DebugConfigurationProvider {
provideDebugConfigurations?(folder: WorkspaceFolder | undefined, token?: CancellationToken): ProviderResult<DebugConfiguration[]> {
if (folder) {
return [];
}
return undefined;
}

resolveDebugConfiguration?(folder: WorkspaceFolder, debugConfiguration: DebugConfiguration, token?: CancellationToken): ProviderResult<DebugConfiguration> {
debugConfiguration.verbose = workspace.getConfiguration("erlang").get("verbose", false);
debugConfiguration.erlangPath = <string>workspace.getConfiguration("erlang").get("erlangPath", null);
let cfg = getElangConfigConfiguration();
debugConfiguration.verbose = cfg.verbose;
debugConfiguration.erlangPath = cfg.erlangPath;
return debugConfiguration;
}
};

let currentSettings : ErlangSettings = null;

export function configurationChanged() : void {
let erlangConf = workspace.getConfiguration("erlang");
let settings : ErlangSettings = {
erlangPath: erlangConf.get<string>("erlangPath", null),
rebarPath: erlangConf.get<string>("rebarPath", null),
codeLensEnabled: erlangConf.get<boolean>('codeLensEnabled', false),
debuggerRunMode: erlangConf.get<string>("debuggerRunMode", "Server"),
includePaths: erlangConf.get("includePaths", []),
linting: erlangConf.get<boolean>('linting', false),
rebarBuildArgs: erlangConf.get("rebarBuildArgs", ['compile']),
rootPath : workspace.rootPath,
verbose: erlangConf.get("verbose", false)
};
currentSettings = settings;
}

export function getElangConfigConfiguration() : ErlangSettings {
if (!currentSettings) {
configurationChanged();
}
return currentSettings;
}
Loading

0 comments on commit 2020087

Please sign in to comment.