Refactoring, smart navigation and code assistance for CodeMirror.
This is a promising proof-of-concept but not a stable API you should rely on and the claimed features aren't ready to use.
There is some extra information and the source code in the Reflector GitHub repository.
This embeds a tiny language called Miniscript created for demonstration and exploration purposes. Some Reflector extensions are active.
The editor uses CodeMirror's default keymap and adds some custom keybindings.
selectMatchingIdentifiers
: with the cursor in the definition of an identifier (func HERE() ...
or var HERE;
), create a selection of all matching uses of this identifier, respecting scope and redefinition rules (the rules for Miniscript are explained below). This is similar to the "rename function/variable" refactoring but not as ergonomic: it doesn't check whether you are creating a conflicting name and, because it uses selections, Esc (which I would expect to cancel the renaming) will instead deselect the text.
goToDefinition
: with the cursor in an identifier, select its (first) definition. This is similar to the "go to definition" feature in many IDEs.
There is also highlighting of matching identifiers. When the cursor is in an identifier, matching definitions will be highlighted in blue (similar to how CodeMirror highlights matching brackets). This is a visual aid to help you understand the scope of the identifier you are working with. Similarly for uses of an identifier: when the cursor is in a definition, matching uses will be highlighted in green. It is possible (but not implemented) to show all uses and definitions of an identifier. Finally, an indentifier which doesn't have a matching definition will be shown in red.
This is driven by some custom props in the Miniscript grammar. These mark grammar nodes that establish scope, uses and definitions of identifiers. The Miniscript grammar is explained below. With these props, Reflector can cross-reference identifiers and show you where they are defined and used. This is probably quite an inefficient process right now but it works for small files in a simple grammar like this one.
This structural understanding is also used to drive a linter. This shows an error Diagnostic for duplicate definitions (e.g. a repeated parameter) and understands through the grammar annotations whether a duplicate is allowed to override an earlier definition on the same identifier in the scope, which is allowed for Miniscript local variables. If you use the highlighting or renaming feature, you will hopefully see that Reflector understands the scope of the identifier correctly even when it is redefined.
The linter also shows information markers when a definition is unused. Unused functions and local variables both have an Action that allows the unused definition to be removed. Here is an example of how to implement this for Miniscript. The Action
walks up the syntax tree until it finds the FunctionDeclaration
node, then removes its text from the document. Because this is not aware of the skipped space nodes around the removed text, the formatting can be a bit off. This is a limitation of the current implementation.
Another linter rule provides an Action that can fix an undefined identifier by declaring a local or global variable. It does this by inserting a text template and substituting in the identifier name.
I've also, for no good reason other than experimenting with recognising nearby document structure, included a lint rule to suggest you shouldn't put a comment after a statement.
There is an experiment with providing context-specific syntax error messages. Lezer is very good at recovering from syntax errors but it is not easy to provide a helpful error message. The experiment tries to provide some context-sensitive guidance about what might be causing the syntax error. I'm not sure it is going to be a successful idea.
The grammar is here but, in summary:
func NAME(ARG1, ARG2) { STATEMENTS }
defines a function. Functions in Miniscript are global and do not have to be defined before they are used. Function names must be unique. Function parameters must be unique within the function. Their scope ends at }
var NAME;
at the top level, alongside functions, defines a global variable. Like functions, global variables do not need to be defined before they are used.
var NAME;
within a function body ({...}
) defines a local variable. Local variables need to be defined before they can be used. It is allowed to redefine an existing local variable or parameter within the same scope.
#
and continue to the end of the line.
+ - * / ( )`
. There is operator precedence and associativity - standard Lezer features.