Wrangler Language Server

The Wrangler Language Server is an extension to the Erlang Language Server. It provides Erlang refactorings while implementing the Language Server Protocol through Erlang LS.

You can read more about Wrangler in the Wrangler documentation.

Installation

  1. Follow the installation instructions of Wrangler.

  2. Install the Erlang Language Server in your chosen text editor.

  3. Modify the erlang_ls.config file. Example:

...
wrangler:
  enabled: true
  path: "/path/to/wrangler/ebin" 
  tab_width: 8
  search_paths: ["folder/where/you/use/refactorings"]
  enabled_refactorings:
    - "comment-out-spec"
    - "fold-expression"
    - "generalise-fun"
    - "move-fun"
    - "new-fun"
    - "new-macro"
    - "new-var"
    - "rename-fun"
    - "rename-var"
    - "inline-var"

Usage

General case:

  1. Highlight/set the cursor to the place where you want to apply the refactoring.
    Screenshot
  2. Select one of the refactorings from the appearing menu (code actions).
    Screenshot
  3. Provide additionial information if needed (eg. new variable name).
    Screenshot
  4. Save the changes.
    Screenshot

Wrangler forms:

In some cases, refactorings can be done in multiple places. Wrangler highlights the possible refactor candidates that you can choose from in a way we call Wrangler forms.

Select Refactor this instance in all the places you want the execute the refactoring.

Until you choose exit or select all the possible refactorings, please do not change the file manually.

For example, while folding fun_to_fold:
Screenshot

Supported refactorings

Comment out spec - comment-out-spec

Comment out all the lines starting with -spec in a file. This can be initiated by a code lens at the beginning of the file:
Screenshot

Fold expression - fold-expression

With Wrangler form, replace instances of the function body by the corresponding function definition.

Inline variable - inline-var

With Wrangler form, replace instances of a variable with its corresponding value.

Generalise function - generalise-fun

Refactor the highlighted expression as the function’s new argument.

Introduce new variable - new-var

Refactor the highlighted expression as a new variable.

Move function - move-fun

Move a function definition from its current module to another module. It changes all function calls in the working directory.

Extract function - new-fun

Introduce a new function definition to represent a selected expression sequence inside an existing function. That selected sequence of expressions is replaced by a call to the new function.

New macro - new-macro

Define a macro to represent a selected sequence of expressions.

Rename function - rename-fun

Rename a function.

Rename variable - rename-var

Rename a variable.

For Wrangler delevopers

Wrangler-ls extends Erlang-ls with additional codeActions, codeLens etc. The wls_utils module is used to convert between Erlang-ls/LSP and Wrangler type representations (eg. positions) and to provide some LSP request/notification constructors (eg. textEdit, showMessage).

In the Erlang-ls side, first, Erlang-ls loads the Wrangler modules and starts Wrangler. Then, wrangler_handler module adds the corresponding language features for all possible requests.

In the Wrangler side, you can find similar files and erlang behaviours to ELS’ els_code_actions, els_code_lens, els_execute_command_provider modules. (These files are prefixed with wls_).

Used LSP features:

Providing user input

User inputs (eg. asking for a new variable name) are handled by a middleware in the extension since these functions are not yet supported by LSP.

To request an input from the user, add the following attribute to the command arguments (text field is not required):

user_input => #{'type' => variable|atom|macro|file, 'text' => <<"Placeholder text">>}

On successful inputs, a value field will be added to the user_input with the given value.

User inputs are yet only supported in the VSCode`s extension. In other editors, default names will be used (eg. “NewVar”)

Adding LSP support for a refactoring

To make a refactoring available through LSP, create a new Code Action module prefixed with wls_code_action_ implementing wls_code_actions behaviour:

%% Title which is shown to the user.
-callback title() -> binary().

%% Identifier of the action and the commands. 
%% Same as the file's name without the wls_code_action_ prefix.
-callback id() -> action_id().

%% Initialize the action. 
%% Optional callback.
-callback init(els_core:uri(), els_core:range()) -> state().

%% Whether the action is offered based on the highlighted range. 
%% Optional callback, defaults to true.
-callback precondition(els_core:uri(), els_core:range()) -> boolean().

%% The command`s arguments.
-callback command_args(els_core:uri(), els_core:range(), state()) -> map().

%% Execute the command with the given arguments. 
%% The first element of the argument list is the one returned by command_args. 
-callback execute_command([any()]) -> [map()].

Register the code action in wls_code_actions:available_actions/0

You can see several examples for this in the projecc. For example, wls_code_action_generalise_fun.erl that implements the generalise refactoring.

Alternatively, to understand how ELS Code Lenses works, see this article: How to: Code Lenses. Code Actions has a similar behaviour.

Wrangler forms

For refactorings that need more complex io (eg. fold), Wrangler forms are introduced.

To make these forms, highlight, semantic token and code lens LSP features are used.

The form’s state are handled by wls_server that can be extended with more of these refactorings.