[ 
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)

Reply via email to