[
https://issues.apache.org/jira/browse/GROOVY-12411?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Daniel Sun updated GROOVY-12411:
--------------------------------
Description:
Apache Groovy needs a language server that uses the compiler in this
repository. Community servers compile against a published Groovy jar, so parser
and type-system changes reach an editor only after that jar is released. This
change adds an optional {{groovy-lsp}} module and a {{groovylsp}} command.
Editors, and later AI tools, share that language core. It is not a full IDE,
and it does not replace Groovy-Eclipse or IntelliJ.
h2. Why this shape
The server compiles the open buffers with the in-tree compiler and answers
Language Server Protocol (LSP) requests from that snapshot. A language change
in this tree shows up in the server without forking a community project. The
server refuses an edit that would guess. It does not rename an unbound dynamic
call, and it does not rewrite every token that happens to spell the same name.
There is no public extension SPI in this first cut. No ServiceLoader hook, no
OSGi bundle, and no second hover or completion API. Static type checking of
annotated members is core behavior, not a plugin. A later {{groovy-mcp}}
wrapper may call the in-process engine. That wrapper is not part of this change.
h2. What lands
* Optional subproject {{{}groovy-lsp{}}}. It is not part of
{{{}groovy-all{}}}. The binary distribution adds {{groovylsp}} and
{{{}groovylsp.bat{}}}.
* The default transport is stdio. {{--socket}} accepts one TCP client.
{{--help}} and {{--version}} print and exit. On stdio, standard output is
redirected to standard error, so a print from Groovy code cannot corrupt the
protocol stream.
* The protocol is LSP 3.18, carried by Eclipse LSP4J 1.0. Fields from a newer
draft that the server does not understand are ignored. A capability is
advertised only when it is implemented. LSP4J is an EPL 2.0 Category B binary.
Its sources are not copied into Groovy.
* Open Groovy buffers compile through semantic analysis. The server does not
generate Groovy class files. Errors are collected, and a partial AST is still
queried.
* {{.java}} files in the workspace are joint-compiled with
{{JavaAwareCompilationUnit}} ({{{}memStub{}}}) and {{javax.tools}} javac.
Annotation processing is off.
* After semantic analysis, {{StaticTypeCheckingPass}} type-checks members
annotated {{@TypeChecked}} or {{{}@CompileStatic{}}}. It skips
{{@CompileDynamic}} and {{{}TypeCheckingMode.SKIP{}}}. It does not force static
checking on ordinary dynamic Groovy, and it does not raise the compile phase.
* {{LocatedMessage.toDiagnostic()}} and {{write()}} report the message with no
line or column when the parse context is null. They no longer throw
{{NullPointerException}} in that case.
* {{ASTNode}} column javadoc now matches the compiler. Columns are 1-based
Unicode code points, the end column is exclusive, and a missing position is -1.
Getters, setters, and stored values are unchanged. See GROOVY-12085.
* User guide {{{}groovy-lsp.adoc{}}}. Contributor map
{{{}subprojects/groovy-lsp/ARCHITECTURE.md{}}}.
h2. In-process engine
{{GroovyLanguageEngine}} answers the same questions as the LSP handlers and
returns JDK records. Constructing it does not scan a workspace, so it does not
compile a checkout root.
* {{open(Path)}} reads one file from disk and returns its URI. Queries take
that URI and use 0-based UTF-16 positions.
* {{{}openBuffer{}}}, {{{}updateBuffer{}}}, and {{closeBuffer}} take any URI
the caller chooses. A URI whose scheme is not {{file}} is not treated as a
path. The engine does not reserve buffer names.
* {{open}} and {{openBuffer}} compile immediately. {{updateBuffer}} only
replaces the text. The next query compiles when the buffer has changed, and
{{compileNow}} compiles at once. {{closeBuffer}} drops the buffer and compiles
what remains.
* {{setParentLoader}} sets the parent loader. {{setClasspath}} replaces the
compile classpath. {{putExtra}} stores an opaque fingerprint so an in-place
change to the same loader still forces a new compile.
* Rename, format, and organize-imports return edits. They do not write files.
Groovy Console and groovysh are not connected to this engine in this change.
h2. What an editor gets
Recognized sources are {{{}.groovy{}}}, {{{}.gvy{}}}, {{{}.gy{}}},
{{{}.gsh{}}}, or language id {{{}groovy{}}}.
* *Editing.* Incremental sync. Completion of keywords, snippets, members,
workspace types with an import edit, and GDK members when a prefix is typed.
Groovydoc is filled when the client resolves a completion item. Format and
range format indent by braces and leave the text of a multiline string or a
block comment unchanged. A typed newline indents the next line. A typed closing
brace outdents, except inside a string, a comment, or a slashy regex.
* *Imports and types.* Organize imports keeps star, static, and alias imports,
and does not re-insert Groovy's default imports. {{source.fixAll}} only deletes
unused imports. Other actions add one import, add every unambiguous missing
import in one edit, add a package taken from the file path, and replace the
{{def}} immediately before a field or local name with an inferred type.
* *Generate.* Implement abstract methods by inserting an {{@Override}} stub
that throws {{{}UnsupportedOperationException{}}}. Add {{@Override}} on a
method that already overrides. Drop the {{private}} modifier on a field that
has no accessor, so Groovy treats the field as a property. Insert
{{{}@groovy.transform.ToString{}}},
{{{}@groovy.transform.EqualsAndHashCode{}}}, or
{{{}@groovy.transform.TupleConstructor{}}}. Those three actions insert the
annotation only. They do not paste a method body. {{@ToString}} is skipped when
that annotation, {{{}@Canonical{}}}, or {{toString}} is already present.
{{@EqualsAndHashCode}} is skipped when that annotation, {{{}@Canonical{}}},
{{{}equals{}}}, or {{hashCode}} is already present. {{@TupleConstructor}} is
skipped when that annotation, {{{}@Canonical{}}}, {{{}@MapConstructor{}}}, or a
declared constructor is already present, and when the class has no instance
field.
* *Create class.* For an unresolved type, offer a sibling {{Name.groovy}} next
to the referencing file, using that file's package. A qualified name is
accepted only when its package is the same. The action is skipped when that
type is already defined, when the sibling is already open or on disk, or when
the client did not advertise file creation ({{{}documentChanges{}}} plus the
create resource operation). The stub is written only in that new file. The
create neither overwrites nor ignores an existing file.
* *Calls.* At a call site, complete a parameter name as a named-argument
prefix when the call is bound or has one arity match. {{refactor.rewrite}}
converts such a positional call to named arguments.
* *Navigation.* Definition, type definition, implementation, declaration, and
references. References match the declaration by owner, name, and descriptor,
not by the bare spelling. Call hierarchy and type hierarchy. Document symbols
and workspace symbols. A workspace-symbol query may be a case-insensitive
substring or CamelHumps, so {{ASF}} finds {{{}AbstractServerFactory{}}}.
Folding, selection ranges, document highlight, and document links on imports.
* *Presentation.* Push and pull diagnostics: compiler messages, unused
imports, static-type errors on annotated members, and javac messages from a
joint compile. Semantic tokens for the whole file or a range. Inlay hints for
declaration types and for parameter names. A parameter-name inlay is labelled
inferred unless the compiler bound the call, and an inferred inlay does not
insert text. Code lenses show a reference count.
* *Workspace.* Workspace folders, watched files, configuration, and progress
when the client asks for it. {{workspace/willRenameFiles}} updates one
top-level Groovy class whose name matches the file name without its extension.
It refuses a script, a file that declares several types, a class name that does
not match, and {{{}.java{}}}.
Commands:
* {{groovy.lsp.organizeImports}}
* {{groovy.lsp.showReferences}}
* {{{}groovy.lsp.gotoSuperMethod{}}}, the nearest overridden method that has
source. It skips {{Object}} and {{{}GroovyObject{}}}.
h2. Rename
{{methodTarget}} is often empty on a dynamic call. A match by name and arity is
enough to display, and not enough to edit.
||Feature||When the compiler bound the call||When it did not||
|Hover|The bound signature|One arity match on a resolved receiver, marked
{{{}_inferred_{}}}. Otherwise the name only|
|Definition|That method|Every arity match that has source. Never an arbitrary
first hit|
|Rename and prepare-rename|Allowed|Refused|
A class, method, field, parameter, or bound local is renamed by identity.
Renaming a class also renames the file when the class name matches the file
name without its extension.
h2. Groovy and Java in one workspace
Joint compilation lets Groovy and Java sources in the workspace see each
other's types. Go to definition can cross that boundary, and javac diagnostics
are published with the Groovy ones. javac writes class files to a private
temporary directory whose name starts with {{{}groovy-lsp-javac-{}}}, not under
the user home directory. On POSIX the directory is owner-only. It is removed on
the next compile and when the compiler closes. If that directory cannot be
created, the compile continues with Groovy only.
This is not a Java language server. Java completion, Java formatting, and
Java-only rename stay with a Java language server. Eclipse JDT and
groovy-eclipse remain the stack for a full Java IDE. No source is copied from
groovy-language-server-edt, and its identifier-scan rename is not used.
||Question||groovy-lsp||Other Groovy language servers||
|Which compiler|The compiler in this repository|A published Groovy jar, or JDT
plus groovy-eclipse|
|Java IDE features|Joint type resolution, and definition from Groovy into
Java|groovy-language-server-edt also completes, formats, and renames Java|
|GDSL and dependency download|Not included|groovyls adds Jenkins GDSL and
dependency download|
|Unbound-call rename|Refused|groovy-language-server-edt can rename by scanning
the identifier text|
h2. Configuration and safety
On {{{}initialized{}}}, if the client advertised
{{{}workspace/configuration{}}}, the server asks for the {{groovy}} section
without blocking the JSON-RPC reader. The client can also push
{{{}workspace/didChangeConfiguration{}}}.
||Key||Meaning||
|classpath|Extra jars and directories. The server does not run Gradle or Maven|
|sourcePaths|Extra source roots. A relative path is resolved from a workspace
folder|
|grapeEnabled|Allow {{@Grab}} while compiling. Default is off|
|astTestEnabled|Allow {{@ASTTest}} while compiling. Default is off|
Any other key under {{groovy}} is stored as text. The server does not interpret
it and does not download anything for it. Changing such a key builds a new
class loader and recompiles.
An empty source-path list and an empty classpath are filled separately, and
only when the client left that list empty. Source roots are every existing
directory among {{{}src/main/groovy{}}}, {{{}src/test/groovy{}}},
{{{}src/main/java{}}}, and {{{}src/test/java{}}}. If none of those four exists,
the server uses {{src}} when that directory exists, and otherwise the workspace
folder itself. The classpath gains jars that sit directly under {{lib}} or
{{{}libs{}}}.
Diagnostics for one file stay on that file. Unchanged buffers are not compiled
again. {{@Grab}} stays off unless the client turns it on, both by omitting its
global transform and by the process property {{{}groovy.grape.enable{}}}.
{{@ASTTest}} is a local transform, so it is turned off only by
{{{}groovy.asttest.enable{}}}. Both properties are process-wide for the
duration of a compile, then restored. One compile holds them so another compile
cannot restore them early.
h2. Out of scope
* A full Java language server.
* GDSL and Jenkins pipeline descriptors.
* Invoking Gradle or Maven, including the Tooling API.
* A debugger or a test runner.
* Renaming by identifier text.
* Name-span fields on {{{}AnnotatedNode{}}}. Identifier ranges stay inside
groovy-lsp.
* A public plugin SPI.
* Wiring Groovy Console or groovysh.
h2. Positions
This is not an LSP break. Compiler columns are already 1-based Unicode code
points (GROOVY-12085). LSP positions stay 0-based in the encoding chosen at
initialize: {{utf-16}} unless the client offers {{utf-8}} or {{{}utf-32{}}}.
The server converts both ways, including characters outside the Basic
Multilingual Plane. Clients keep sending positions as they do today.
h2. Running
{code:java}
groovylsp
groovylsp --stdio
groovylsp --socket PORT
groovylsp --help
groovylsp --version
{code}
An editor starts that process and speaks JSON-RPC on its standard input and
output. After shutdown, {{exit}} ends the process with code 0. {{exit}} without
a preceding shutdown ends it with code 1.
was:
Apache Groovy needs a language server that uses the compiler in this
repository. Community servers compile against a published Groovy jar, so parser
and type-system changes reach an editor only after that jar is released. Commit
d014387ced2187dd7a129d77b34048a892bdecd5 adds an optional {{groovy-lsp}} module
and a {{groovylsp}} command. Editors, and later AI tools, share that language
core. It is not a full IDE, and it does not replace Groovy-Eclipse or IntelliJ.
h2. Why this shape
The server compiles the open buffers with the in-tree compiler and answers
Language Server Protocol (LSP) requests from that snapshot. A language change
in this tree shows up in the server without forking a community project. The
server refuses an edit that would guess. It does not rename an unbound dynamic
call, and it does not rewrite every token that happens to spell the same name.
There is no public extension SPI in this first cut. No ServiceLoader hook, no
OSGi bundle, and no second hover or completion API. Static type checking of
annotated members is core behavior, not a plugin. A later {{groovy-mcp}}
wrapper may call the in-process engine. That wrapper is not part of this change.
h2. What lands
* Optional subproject {{groovy-lsp}}. It is not part of {{groovy-all}}. The
binary distribution adds {{groovylsp}} and {{groovylsp.bat}}.
* The default transport is stdio. {{\-\-socket}} accepts one TCP client.
{{\-\-help}} and {{\-\-version}} print and exit. On stdio, standard output is
redirected to standard error, so a print from Groovy code cannot corrupt the
protocol stream.
* The protocol is LSP 3.18, carried by Eclipse LSP4J 1.0. Fields from a newer
draft that the server does not understand are ignored. A capability is
advertised only when it is implemented. LSP4J is an EPL 2.0 Category B binary.
Its sources are not copied into Groovy.
* Open Groovy buffers compile through semantic analysis. The server does not
generate Groovy class files. Errors are collected, and a partial AST is still
queried.
* {{.java}} files in the workspace are joint-compiled with
{{JavaAwareCompilationUnit}} ({{memStub}}) and {{javax.tools}} javac.
Annotation processing is off.
* After semantic analysis, {{StaticTypeCheckingPass}} type-checks members
annotated {{@TypeChecked}} or {{@CompileStatic}}. It skips {{@CompileDynamic}}
and {{TypeCheckingMode.SKIP}}. It does not force static checking on ordinary
dynamic Groovy, and it does not raise the compile phase.
* {{LocatedMessage.toDiagnostic()}} and {{write()}} report the message with no
line or column when the parse context is null. They no longer throw
{{NullPointerException}} in that case.
* {{ASTNode}} column javadoc now matches the compiler. Columns are 1-based
Unicode code points, the end column is exclusive, and a missing position is -1.
Getters, setters, and stored values are unchanged. See GROOVY-12085.
* User guide {{groovy-lsp.adoc}}. Contributor map
{{subprojects/groovy-lsp/ARCHITECTURE.md}}.
h2. In-process engine
{{GroovyLanguageEngine}} answers the same questions as the LSP handlers and
returns JDK records. Constructing it does not scan a workspace, so it does not
compile a checkout root.
* {{open(Path)}} reads one file from disk and returns its URI. Queries take
that URI and use 0-based UTF-16 positions.
* {{openBuffer}}, {{updateBuffer}}, and {{closeBuffer}} take any URI the caller
chooses. A URI whose scheme is not {{file}} is not treated as a path. The
engine does not reserve buffer names.
* {{open}} and {{openBuffer}} compile immediately. {{updateBuffer}} only
replaces the text. The next query compiles when the buffer has changed, and
{{compileNow}} compiles at once. {{closeBuffer}} drops the buffer and compiles
what remains.
* {{setParentLoader}} sets the parent loader. {{setClasspath}} replaces the
compile classpath. {{putExtra}} stores an opaque fingerprint so an in-place
change to the same loader still forces a new compile.
* Rename, format, and organize-imports return edits. They do not write files.
Groovy Console and groovysh are not connected to this engine in this change.
h2. What an editor gets
Recognized sources are {{.groovy}}, {{.gvy}}, {{.gy}}, {{.gsh}}, or language id
{{groovy}}.
* *Editing.* Incremental sync. Completion of keywords, snippets, members,
workspace types with an import edit, and GDK members when a prefix is typed.
Groovydoc is filled when the client resolves a completion item. Format and
range format indent by braces and leave the text of a multiline string or a
block comment unchanged. A typed newline indents the next line. A typed closing
brace outdents, except inside a string, a comment, or a slashy regex.
* *Imports and types.* Organize imports keeps star, static, and alias imports,
and does not re-insert Groovy's default imports. {{source.fixAll}} only deletes
unused imports. Other actions add one import, add every unambiguous missing
import in one edit, add a package taken from the file path, and replace the
{{def}} immediately before a field or local name with an inferred type.
* *Generate.* Implement abstract methods by inserting an {{@Override}} stub
that throws {{UnsupportedOperationException}}. Add {{@Override}} on a method
that already overrides. Drop the {{private}} modifier on a field that has no
accessor, so Groovy treats the field as a property. Insert
{{@groovy.transform.ToString}}, {{@groovy.transform.EqualsAndHashCode}}, or
{{@groovy.transform.TupleConstructor}}. Those three actions insert the
annotation only. They do not paste a method body. {{@ToString}} is skipped when
that annotation, {{@Canonical}}, or {{toString}} is already present.
{{@EqualsAndHashCode}} is skipped when that annotation, {{@Canonical}},
{{equals}}, or {{hashCode}} is already present. {{@TupleConstructor}} is
skipped when that annotation, {{@Canonical}}, {{@MapConstructor}}, or a
declared constructor is already present, and when the class has no instance
field.
* *Create class.* For an unresolved type, offer a sibling {{Name.groovy}} next
to the referencing file, using that file's package. A qualified name is
accepted only when its package is the same. The action is skipped when that
type is already defined, when the sibling is already open or on disk, or when
the client did not advertise file creation ({{documentChanges}} plus the create
resource operation). The stub is written only in that new file. The create
neither overwrites nor ignores an existing file.
* *Calls.* At a call site, complete a parameter name as a named-argument prefix
when the call is bound or has one arity match. {{refactor.rewrite}} converts
such a positional call to named arguments.
* *Navigation.* Definition, type definition, implementation, declaration, and
references. References match the declaration by owner, name, and descriptor,
not by the bare spelling. Call hierarchy and type hierarchy. Document symbols
and workspace symbols. A workspace-symbol query may be a case-insensitive
substring or CamelHumps, so {{ASF}} finds {{AbstractServerFactory}}. Folding,
selection ranges, document highlight, and document links on imports.
* *Presentation.* Push and pull diagnostics: compiler messages, unused imports,
static-type errors on annotated members, and javac messages from a joint
compile. Semantic tokens for the whole file or a range. Inlay hints for
declaration types and for parameter names. A parameter-name inlay is labelled
inferred unless the compiler bound the call, and an inferred inlay does not
insert text. Code lenses show a reference count.
* *Workspace.* Workspace folders, watched files, configuration, and progress
when the client asks for it. {{workspace/willRenameFiles}} updates one
top-level Groovy class whose name matches the file name without its extension.
It refuses a script, a file that declares several types, a class name that does
not match, and {{.java}}.
Commands:
* {{groovy.lsp.organizeImports}}
* {{groovy.lsp.showReferences}}
* {{groovy.lsp.gotoSuperMethod}}, the nearest overridden method that has
source. It skips {{Object}} and {{GroovyObject}}.
h2. Rename
{{methodTarget}} is often empty on a dynamic call. A match by name and arity is
enough to display, and not enough to edit.
||Feature||When the compiler bound the call||When it did not||
|Hover|The bound signature|One arity match on a resolved receiver, marked
{{_inferred_}}. Otherwise the name only|
|Definition|That method|Every arity match that has source. Never an arbitrary
first hit|
|Rename and prepare-rename|Allowed|Refused|
A class, method, field, parameter, or bound local is renamed by identity.
Renaming a class also renames the file when the class name matches the file
name without its extension.
h2. Groovy and Java in one workspace
Joint compilation lets Groovy and Java sources in the workspace see each
other's types. Go to definition can cross that boundary, and javac diagnostics
are published with the Groovy ones. javac writes class files to a private
temporary directory whose name starts with {{groovy-lsp-javac-}}, not under the
user home directory. On POSIX the directory is owner-only. It is removed on the
next compile and when the compiler closes. If that directory cannot be created,
the compile continues with Groovy only.
This is not a Java language server. Java completion, Java formatting, and
Java-only rename stay with a Java language server. Eclipse JDT and
groovy-eclipse remain the stack for a full Java IDE. No source is copied from
groovy-language-server-edt, and its identifier-scan rename is not used.
||Question||groovy-lsp||Other Groovy language servers||
|Which compiler|The compiler in this repository|A published Groovy jar, or JDT
plus groovy-eclipse|
|Java IDE features|Joint type resolution, and definition from Groovy into
Java|groovy-language-server-edt also completes, formats, and renames Java|
|GDSL and dependency download|Not included|groovyls adds Jenkins GDSL and
dependency download|
|Unbound-call rename|Refused|groovy-language-server-edt can rename by scanning
the identifier text|
h2. Configuration and safety
On {{initialized}}, if the client advertised {{workspace/configuration}}, the
server asks for the {{groovy}} section without blocking the JSON-RPC reader.
The client can also push {{workspace/didChangeConfiguration}}.
||Key||Meaning||
|classpath|Extra jars and directories. The server does not run Gradle or Maven|
|sourcePaths|Extra source roots. A relative path is resolved from a workspace
folder|
|grapeEnabled|Allow {{@Grab}} while compiling. Default is off|
|astTestEnabled|Allow {{@ASTTest}} while compiling. Default is off|
Any other key under {{groovy}} is stored as text. The server does not interpret
it and does not download anything for it. Changing such a key builds a new
class loader and recompiles.
An empty source-path list and an empty classpath are filled separately, and
only when the client left that list empty. Source roots are every existing
directory among {{src/main/groovy}}, {{src/test/groovy}}, {{src/main/java}},
and {{src/test/java}}. If none of those four exists, the server uses {{src}}
when that directory exists, and otherwise the workspace folder itself. The
classpath gains jars that sit directly under {{lib}} or {{libs}}.
Diagnostics for one file stay on that file. Unchanged buffers are not compiled
again. {{@Grab}} stays off unless the client turns it on, both by omitting its
global transform and by the process property {{groovy.grape.enable}}.
{{@ASTTest}} is a local transform, so it is turned off only by
{{groovy.asttest.enable}}. Both properties are process-wide for the duration of
a compile, then restored. One compile holds them so another compile cannot
restore them early.
h2. Out of scope
* A full Java language server.
* GDSL and Jenkins pipeline descriptors.
* Invoking Gradle or Maven, including the Tooling API.
* A debugger or a test runner.
* Renaming by identifier text.
* Name-span fields on {{AnnotatedNode}}. Identifier ranges stay inside
groovy-lsp.
* A public plugin SPI.
* Wiring Groovy Console or groovysh.
h2. Positions
This is not an LSP break. Compiler columns are already 1-based Unicode code
points (GROOVY-12085). LSP positions stay 0-based in the encoding chosen at
initialize: {{utf-16}} unless the client offers {{utf-8}} or {{utf-32}}. The
server converts both ways, including characters outside the Basic Multilingual
Plane. Clients keep sending positions as they do today.
h2. Running
{code}
groovylsp
groovylsp --stdio
groovylsp --socket PORT
groovylsp --help
groovylsp --version
{code}
An editor starts that process and speaks JSON-RPC on its standard input and
output. After shutdown, {{exit}} ends the process with code 0. {{exit}} without
a preceding shutdown ends it with code 1.
> Add a first-party Language Server Protocol implementation (groovy-lsp)
> ----------------------------------------------------------------------
>
> Key: GROOVY-12411
> URL: https://issues.apache.org/jira/browse/GROOVY-12411
> Project: Groovy
> Issue Type: Improvement
> Reporter: Daniel Sun
> Priority: Major
>
> Apache Groovy needs a language server that uses the compiler in this
> repository. Community servers compile against a published Groovy jar, so
> parser and type-system changes reach an editor only after that jar is
> released. This change adds an optional {{groovy-lsp}} module and a
> {{groovylsp}} command. Editors, and later AI tools, share that language core.
> It is not a full IDE, and it does not replace Groovy-Eclipse or IntelliJ.
> h2. Why this shape
> The server compiles the open buffers with the in-tree compiler and answers
> Language Server Protocol (LSP) requests from that snapshot. A language change
> in this tree shows up in the server without forking a community project. The
> server refuses an edit that would guess. It does not rename an unbound
> dynamic call, and it does not rewrite every token that happens to spell the
> same name.
> There is no public extension SPI in this first cut. No ServiceLoader hook, no
> OSGi bundle, and no second hover or completion API. Static type checking of
> annotated members is core behavior, not a plugin. A later {{groovy-mcp}}
> wrapper may call the in-process engine. That wrapper is not part of this
> change.
> h2. What lands
> * Optional subproject {{{}groovy-lsp{}}}. It is not part of
> {{{}groovy-all{}}}. The binary distribution adds {{groovylsp}} and
> {{{}groovylsp.bat{}}}.
> * The default transport is stdio. {{--socket}} accepts one TCP client.
> {{--help}} and {{--version}} print and exit. On stdio, standard output is
> redirected to standard error, so a print from Groovy code cannot corrupt the
> protocol stream.
> * The protocol is LSP 3.18, carried by Eclipse LSP4J 1.0. Fields from a
> newer draft that the server does not understand are ignored. A capability is
> advertised only when it is implemented. LSP4J is an EPL 2.0 Category B
> binary. Its sources are not copied into Groovy.
> * Open Groovy buffers compile through semantic analysis. The server does not
> generate Groovy class files. Errors are collected, and a partial AST is still
> queried.
> * {{.java}} files in the workspace are joint-compiled with
> {{JavaAwareCompilationUnit}} ({{{}memStub{}}}) and {{javax.tools}} javac.
> Annotation processing is off.
> * After semantic analysis, {{StaticTypeCheckingPass}} type-checks members
> annotated {{@TypeChecked}} or {{{}@CompileStatic{}}}. It skips
> {{@CompileDynamic}} and {{{}TypeCheckingMode.SKIP{}}}. It does not force
> static checking on ordinary dynamic Groovy, and it does not raise the compile
> phase.
> * {{LocatedMessage.toDiagnostic()}} and {{write()}} report the message with
> no line or column when the parse context is null. They no longer throw
> {{NullPointerException}} in that case.
> * {{ASTNode}} column javadoc now matches the compiler. Columns are 1-based
> Unicode code points, the end column is exclusive, and a missing position is
> -1. Getters, setters, and stored values are unchanged. See GROOVY-12085.
> * User guide {{{}groovy-lsp.adoc{}}}. Contributor map
> {{{}subprojects/groovy-lsp/ARCHITECTURE.md{}}}.
> h2. In-process engine
> {{GroovyLanguageEngine}} answers the same questions as the LSP handlers and
> returns JDK records. Constructing it does not scan a workspace, so it does
> not compile a checkout root.
> * {{open(Path)}} reads one file from disk and returns its URI. Queries take
> that URI and use 0-based UTF-16 positions.
> * {{{}openBuffer{}}}, {{{}updateBuffer{}}}, and {{closeBuffer}} take any URI
> the caller chooses. A URI whose scheme is not {{file}} is not treated as a
> path. The engine does not reserve buffer names.
> * {{open}} and {{openBuffer}} compile immediately. {{updateBuffer}} only
> replaces the text. The next query compiles when the buffer has changed, and
> {{compileNow}} compiles at once. {{closeBuffer}} drops the buffer and
> compiles what remains.
> * {{setParentLoader}} sets the parent loader. {{setClasspath}} replaces the
> compile classpath. {{putExtra}} stores an opaque fingerprint so an in-place
> change to the same loader still forces a new compile.
> * Rename, format, and organize-imports return edits. They do not write files.
> Groovy Console and groovysh are not connected to this engine in this change.
> h2. What an editor gets
> Recognized sources are {{{}.groovy{}}}, {{{}.gvy{}}}, {{{}.gy{}}},
> {{{}.gsh{}}}, or language id {{{}groovy{}}}.
> * *Editing.* Incremental sync. Completion of keywords, snippets, members,
> workspace types with an import edit, and GDK members when a prefix is typed.
> Groovydoc is filled when the client resolves a completion item. Format and
> range format indent by braces and leave the text of a multiline string or a
> block comment unchanged. A typed newline indents the next line. A typed
> closing brace outdents, except inside a string, a comment, or a slashy regex.
> * *Imports and types.* Organize imports keeps star, static, and alias
> imports, and does not re-insert Groovy's default imports. {{source.fixAll}}
> only deletes unused imports. Other actions add one import, add every
> unambiguous missing import in one edit, add a package taken from the file
> path, and replace the {{def}} immediately before a field or local name with
> an inferred type.
> * *Generate.* Implement abstract methods by inserting an {{@Override}} stub
> that throws {{{}UnsupportedOperationException{}}}. Add {{@Override}} on a
> method that already overrides. Drop the {{private}} modifier on a field that
> has no accessor, so Groovy treats the field as a property. Insert
> {{{}@groovy.transform.ToString{}}},
> {{{}@groovy.transform.EqualsAndHashCode{}}}, or
> {{{}@groovy.transform.TupleConstructor{}}}. Those three actions insert the
> annotation only. They do not paste a method body. {{@ToString}} is skipped
> when that annotation, {{{}@Canonical{}}}, or {{toString}} is already present.
> {{@EqualsAndHashCode}} is skipped when that annotation, {{{}@Canonical{}}},
> {{{}equals{}}}, or {{hashCode}} is already present. {{@TupleConstructor}} is
> skipped when that annotation, {{{}@Canonical{}}}, {{{}@MapConstructor{}}}, or
> a declared constructor is already present, and when the class has no instance
> field.
> * *Create class.* For an unresolved type, offer a sibling {{Name.groovy}}
> next to the referencing file, using that file's package. A qualified name is
> accepted only when its package is the same. The action is skipped when that
> type is already defined, when the sibling is already open or on disk, or when
> the client did not advertise file creation ({{{}documentChanges{}}} plus the
> create resource operation). The stub is written only in that new file. The
> create neither overwrites nor ignores an existing file.
> * *Calls.* At a call site, complete a parameter name as a named-argument
> prefix when the call is bound or has one arity match. {{refactor.rewrite}}
> converts such a positional call to named arguments.
> * *Navigation.* Definition, type definition, implementation, declaration,
> and references. References match the declaration by owner, name, and
> descriptor, not by the bare spelling. Call hierarchy and type hierarchy.
> Document symbols and workspace symbols. A workspace-symbol query may be a
> case-insensitive substring or CamelHumps, so {{ASF}} finds
> {{{}AbstractServerFactory{}}}. Folding, selection ranges, document highlight,
> and document links on imports.
> * *Presentation.* Push and pull diagnostics: compiler messages, unused
> imports, static-type errors on annotated members, and javac messages from a
> joint compile. Semantic tokens for the whole file or a range. Inlay hints for
> declaration types and for parameter names. A parameter-name inlay is labelled
> inferred unless the compiler bound the call, and an inferred inlay does not
> insert text. Code lenses show a reference count.
> * *Workspace.* Workspace folders, watched files, configuration, and progress
> when the client asks for it. {{workspace/willRenameFiles}} updates one
> top-level Groovy class whose name matches the file name without its
> extension. It refuses a script, a file that declares several types, a class
> name that does not match, and {{{}.java{}}}.
> Commands:
> * {{groovy.lsp.organizeImports}}
> * {{groovy.lsp.showReferences}}
> * {{{}groovy.lsp.gotoSuperMethod{}}}, the nearest overridden method that has
> source. It skips {{Object}} and {{{}GroovyObject{}}}.
> h2. Rename
> {{methodTarget}} is often empty on a dynamic call. A match by name and arity
> is enough to display, and not enough to edit.
> ||Feature||When the compiler bound the call||When it did not||
> |Hover|The bound signature|One arity match on a resolved receiver, marked
> {{{}_inferred_{}}}. Otherwise the name only|
> |Definition|That method|Every arity match that has source. Never an arbitrary
> first hit|
> |Rename and prepare-rename|Allowed|Refused|
> A class, method, field, parameter, or bound local is renamed by identity.
> Renaming a class also renames the file when the class name matches the file
> name without its extension.
> h2. Groovy and Java in one workspace
> Joint compilation lets Groovy and Java sources in the workspace see each
> other's types. Go to definition can cross that boundary, and javac
> diagnostics are published with the Groovy ones. javac writes class files to a
> private temporary directory whose name starts with {{{}groovy-lsp-javac-{}}},
> not under the user home directory. On POSIX the directory is owner-only. It
> is removed on the next compile and when the compiler closes. If that
> directory cannot be created, the compile continues with Groovy only.
> This is not a Java language server. Java completion, Java formatting, and
> Java-only rename stay with a Java language server. Eclipse JDT and
> groovy-eclipse remain the stack for a full Java IDE. No source is copied from
> groovy-language-server-edt, and its identifier-scan rename is not used.
> ||Question||groovy-lsp||Other Groovy language servers||
> |Which compiler|The compiler in this repository|A published Groovy jar, or
> JDT plus groovy-eclipse|
> |Java IDE features|Joint type resolution, and definition from Groovy into
> Java|groovy-language-server-edt also completes, formats, and renames Java|
> |GDSL and dependency download|Not included|groovyls adds Jenkins GDSL and
> dependency download|
> |Unbound-call rename|Refused|groovy-language-server-edt can rename by
> scanning the identifier text|
> h2. Configuration and safety
> On {{{}initialized{}}}, if the client advertised
> {{{}workspace/configuration{}}}, the server asks for the {{groovy}} section
> without blocking the JSON-RPC reader. The client can also push
> {{{}workspace/didChangeConfiguration{}}}.
> ||Key||Meaning||
> |classpath|Extra jars and directories. The server does not run Gradle or
> Maven|
> |sourcePaths|Extra source roots. A relative path is resolved from a workspace
> folder|
> |grapeEnabled|Allow {{@Grab}} while compiling. Default is off|
> |astTestEnabled|Allow {{@ASTTest}} while compiling. Default is off|
> Any other key under {{groovy}} is stored as text. The server does not
> interpret it and does not download anything for it. Changing such a key
> builds a new class loader and recompiles.
> An empty source-path list and an empty classpath are filled separately, and
> only when the client left that list empty. Source roots are every existing
> directory among {{{}src/main/groovy{}}}, {{{}src/test/groovy{}}},
> {{{}src/main/java{}}}, and {{{}src/test/java{}}}. If none of those four
> exists, the server uses {{src}} when that directory exists, and otherwise the
> workspace folder itself. The classpath gains jars that sit directly under
> {{lib}} or {{{}libs{}}}.
> Diagnostics for one file stay on that file. Unchanged buffers are not
> compiled again. {{@Grab}} stays off unless the client turns it on, both by
> omitting its global transform and by the process property
> {{{}groovy.grape.enable{}}}. {{@ASTTest}} is a local transform, so it is
> turned off only by {{{}groovy.asttest.enable{}}}. Both properties are
> process-wide for the duration of a compile, then restored. One compile holds
> them so another compile cannot restore them early.
> h2. Out of scope
> * A full Java language server.
> * GDSL and Jenkins pipeline descriptors.
> * Invoking Gradle or Maven, including the Tooling API.
> * A debugger or a test runner.
> * Renaming by identifier text.
> * Name-span fields on {{{}AnnotatedNode{}}}. Identifier ranges stay inside
> groovy-lsp.
> * A public plugin SPI.
> * Wiring Groovy Console or groovysh.
> h2. Positions
> This is not an LSP break. Compiler columns are already 1-based Unicode code
> points (GROOVY-12085). LSP positions stay 0-based in the encoding chosen at
> initialize: {{utf-16}} unless the client offers {{utf-8}} or {{{}utf-32{}}}.
> The server converts both ways, including characters outside the Basic
> Multilingual Plane. Clients keep sending positions as they do today.
> h2. Running
> {code:java}
> groovylsp
> groovylsp --stdio
> groovylsp --socket PORT
> groovylsp --help
> groovylsp --version
> {code}
> An editor starts that process and speaks JSON-RPC on its standard input and
> output. After shutdown, {{exit}} ends the process with code 0. {{exit}}
> without a preceding shutdown ends it with code 1.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)