[ 
https://issues.apache.org/jira/browse/GROOVY-12448?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18127930#comment-18127930
 ] 

ASF GitHub Bot commented on GROOVY-12448:
-----------------------------------------

Copilot commented on code in PR #2976:
URL: https://github.com/apache/groovy/pull/2976#discussion_r4297567334


##########
subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/MatchingHighlighter.java:
##########
@@ -239,7 +239,7 @@ private void clearHighlighted() {
                 doc.setCharacterAttributes(
                         highlightedTokenInfo.getV2().getOffset(),
                         highlightedTokenInfo.getV3(),
-                        findStyleByTokenType(highlightedTokenInfo.getV1()),
+                        defaultStyle,

Review Comment:
   Clearing a temporary match highlight by applying `defaultStyle` will 
overwrite whatever syntax style was previously applied at that span (e.g., from 
`SmartDocumentFilter`). This can leave braces/regions de-highlighted until the 
next full reparse. A safer approach is to restore the prior attributes (store 
them when applying the highlight), or to re-run the syntax styling for just 
that span when clearing.



##########
subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/language/GroovyshLanguageSession.groovy:
##########
@@ -0,0 +1,344 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.groovy.groovysh.language
+
+import groovy.transform.CompileStatic
+import groovy.transform.Internal
+import groovy.transform.PackageScope
+import org.apache.groovy.groovysh.jline.GroovyEngine
+import org.apache.groovy.lsp.internal.engine.GroovyLanguageEngine
+import org.codehaus.groovy.control.CompilerConfiguration
+import org.jline.console.CmdDesc
+import org.jline.console.CmdLine
+import org.jline.reader.Candidate
+import org.jline.reader.Completer
+import org.jline.reader.LineReader
+import org.jline.reader.ParsedLine
+import org.jline.utils.AttributedString
+
+import java.lang.reflect.Modifier
+import java.util.regex.Pattern
+
+/**
+ * Compiler-backed session for groovysh. Does not import LSP4J. The adapter
+ * composes the REPL buffer, binding type declarations, and the current line;
+ * the engine compiles that synthetic script through semantic analysis.
+ * Implementation detail of groovysh, not an embedding API.
+ */
+@CompileStatic
+@Internal
+final class GroovyshLanguageSession implements AutoCloseable, Completer {
+
+    /** This session's unsaved buffer. The engine does not assign the name. */
+    @PackageScope
+    static final URI BUFFER_URI = URI.create('groovy-buffer:repl')
+
+    private static final Pattern SYSTEM_VAR = Pattern.compile('[A-Z]+[A-Z_]*')
+    private static final Pattern IDENTIFIER = 
Pattern.compile('[\\p{L}_$][\\p{L}\\p{N}_$]*')
+    private static final Set<String> SKIP = ['GROOVYSH_OPTIONS', 
'GROOVY_OPTIONS', 'CONSOLE_OPTIONS',
+            'ROOT', 'PWD', 'NANORC', '_args'] as Set
+
+    private final GroovyEngine groovy
+    private final GroovyLanguageEngine engine
+    private String namesPreamble
+    private Set<String> namesCache
+    private CompilerConfiguration boundConfiguration
+
+    GroovyshLanguageSession(GroovyEngine groovy) {
+        this.groovy = groovy
+        this.engine = new GroovyLanguageEngine()
+        bindEngine()
+        engine.openBuffer(BUFFER_URI, 'groovy', '')
+    }
+
+    /**
+     * Compiles the session buffer plus {@code line} and returns diagnostics.
+     *
+     * @param line current input
+     * @return hits
+     */
+    @PackageScope
+    List<GroovyLanguageEngine.Hit> diagnosticsFor(String line) {
+        bindEngine()
+        engine.diagnostics(BUFFER_URI, compose(line ?: ''))
+    }
+
+    /**
+     * Completions for the current line at end-of-buffer.
+     *
+     * @param line current input
+     * @return candidates
+     */
+    @PackageScope
+    List<GroovyLanguageEngine.Candidate> completeLine(String line) {
+        completeLine(line, -1)
+    }
+
+    /**
+     * Completions for {@code line} at {@code cursor} (0-based index into
+     * {@code line}). A negative cursor means end of the composed script.
+     *
+     * @param line current input
+     * @param cursor offset in {@code line}
+     * @return candidates
+     */
+    @PackageScope
+    List<GroovyLanguageEngine.Candidate> completeLine(String line, int cursor) 
{
+        bindEngine()
+        String current = line ?: ''
+        String script = compose(current)
+        int[] caret = caretInComposed(script, current, cursor)
+        engine.complete(BUFFER_URI, script, caret[0], caret[1])
+    }
+
+    /**
+     * Tail-tip description. {@code SYNTAX} reports compiler diagnostics and
+     * does not evaluate. {@code METHOD} asks for signatures on the full line
+     * (so an unclosed {@code (} is visible) and falls back to Inspector only
+     * when the compiler has none.
+     *
+     * @param line JLine command line
+     * @return description, or {@code null}
+     */
+    CmdDesc scriptDescription(CmdLine line) {
+        if (line != null && line.descriptionType == 
CmdLine.DescriptionType.SYNTAX) {
+            try {
+                def hits = diagnosticsFor(line.line ?: '')
+                if (hits) {
+                    def desc = new CmdDesc()
+                    desc.setMainDesc(hits.take(5).collect { new 
AttributedString(it.message() ?: '') })
+                    return desc
+                }
+            } catch (Exception ignored) {
+            }
+            return null
+        }
+        try {
+            if (line != null && line.descriptionType == 
CmdLine.DescriptionType.METHOD) {
+                String raw = line.line ?: ''
+                String head = line.head ?: ''
+                int cursor = Math.min(head.length() + 1, raw.length())
+                def desc = describeSignatures(signaturesFor(raw, cursor))
+                if (desc != null) {
+                    return desc
+                }
+            }
+        } catch (Exception ignored) {
+        }
+        try {
+            return groovy.scriptDescription(line)
+        } catch (Exception ignored) {
+            return null
+        }
+    }
+
+    @PackageScope
+    GroovyLanguageEngine.SignatureSet signaturesFor(String line) {
+        signaturesFor(line, -1)
+    }
+
+    /**
+     * Signatures for {@code line} at {@code cursor} (0-based index into
+     * {@code line}). A negative cursor means end of the composed script.
+     *
+     * @param line current input
+     * @param cursor offset in {@code line}
+     * @return signatures, never {@code null}
+     */
+    @PackageScope
+    GroovyLanguageEngine.SignatureSet signaturesFor(String line, int cursor) {
+        bindEngine()
+        String current = line ?: ''
+        String script = compose(current)
+        int[] caret = caretInComposed(script, current, cursor)
+        engine.signatures(BUFFER_URI, script, caret[0], caret[1])
+    }
+
+    /**
+     * {@code compose} always appends {@code current} last, so a cursor into
+     * {@code current} maps by prefix length of the composed script.
+     */
+    private static int[] caretInComposed(String script, String current, int 
cursor) {
+        if (cursor < 0 || cursor > current.length()) {
+            return GroovyLanguageEngine.positionOf(script, script.length())
+        }
+        int currentStart = script.length() - current.length()
+        return GroovyLanguageEngine.positionOf(script, currentStart + cursor)
+    }
+
+    /**
+     * Builds a METHOD tail-tip from compiler signatures.
+     *
+     * @param set engine signatures
+     * @return description, or {@code null} when there are none
+     */
+    @PackageScope
+    static CmdDesc describeSignatures(GroovyLanguageEngine.SignatureSet set) {
+        if (set == null || !set.signatures()) {
+            return null
+        }
+        def desc = new CmdDesc()
+        desc.setMainDesc(set.signatures().collect { new 
AttributedString(it.label() ?: '') })
+        desc
+    }
+
+    @Override
+    void complete(LineReader reader, ParsedLine line, List<Candidate> 
candidates) {
+        if (candidates == null || line == null) {
+            return
+        }
+        String raw = line.line() ?: ''
+        if (raw.startsWith('/')) {
+            return
+        }
+        try {
+            def items = completeLine(raw, line.cursor())
+            mapEngineCandidates(items, line, candidates)
+        } catch (RuntimeException ignored) {
+        }
+    }
+
+    @Override
+    void close() {
+        engine.close()
+    }
+
+    /**
+     * Synthetic script: remembered snippets, then binding declarations for
+     * names the buffer does not already introduce, then the current line.
+     *
+     * @param current current input
+     * @return script text
+     */
+    @PackageScope
+    String compose(String current) {
+        String preamble = groovy.buffer ?: ''
+        String decls = bindingDeclarations(namesIn(preamble))
+        def parts = []
+        if (preamble) {
+            parts << preamble
+        }
+        if (decls) {
+            parts << decls
+        }
+        parts << (current ?: '')
+        parts.join('\n')
+    }
+
+    private Set<String> namesIn(String preamble) {
+        if (!preamble) {
+            return [] as Set
+        }
+        if (preamble == namesPreamble) {
+            return namesCache
+        }
+        bindEngine()
+        namesCache = engine.declaredNames(BUFFER_URI, preamble)
+        namesPreamble = preamble
+        namesCache
+    }
+
+    private void bindEngine() {
+        engine.setParentLoader(groovy.classLoader)
+        CompilerConfiguration configuration = groovy.compilerConfiguration
+        // setHostConfiguration drops the compile cache. Publish only when
+        // the instance changes so a repeated completion can reuse it.
+        if (configuration != boundConfiguration) {
+            boundConfiguration = configuration
+            engine.setHostConfiguration(configuration)
+        }
+    }

Review Comment:
   This only republishes host `CompilerConfiguration` when the instance 
identity changes. If groovysh or an embedder mutates the same 
`CompilerConfiguration` in place (e.g., editing an `ImportCustomizer`), the 
engine won't be told to drop its compile cache and won't observe the change. If 
in-place mutation is a supported/expected path, consider tracking a lightweight 
fingerprint of relevant config state (script base class + customizer 
identities/count), or providing a hook to force-republish when commands mutate 
customizers.



##########
subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/SmartDocumentFilter.java:
##########
@@ -195,235 +108,169 @@ public void remove(DocumentFilter.FilterBypass fb, int 
offset, int length)
     public void replace(DocumentFilter.FilterBypass fb, int offset,
                         int length, String text, AttributeSet attrs)
             throws BadLocationException {
-
-        // text might be null and indicates no replacement text
-        if (text == null) text = "";
-
-        // remove problem meta characters returns
+        if (text == null) {
+            text = "";
+        }
         text = replaceMetaCharacters(text);
-
         fb.replace(offset, length, text, attrs);
-
         parseDocument();
     }
 
     private String replaceMetaCharacters(String string) {
-        // just in case remove carriage returns
-        string = string.replace("\r\n", "\n");
-        return string;
+        return string.replace("\r\n", "\n");
     }
 
     private void parseDocument() throws BadLocationException {
-        GroovyLangLexer lexer;
-        try {
-            lexer = createLexer(styledDocument.getText(0, 
styledDocument.getLength()));
-        } catch (IOException e) {
-            e.printStackTrace();
-            this.latest = false;
-            return;
-        }
-
-        CommonTokenStream tokenStream = new CommonTokenStream(lexer);
-
-        try {
-            tokenStream.fill();
-        } catch (LexerNoViableAltException | GroovySyntaxError e) {
-            // ignore
-            this.latest = false;
-            return;
-        } catch (Exception e) {
-            e.printStackTrace();
-            this.latest = false;
-            return;
-        }
-
-        List<Token> tokenList = tokenStream.getTokens();
-        List<Token> tokenListToRender;
+        String text = styledDocument.getText(0, styledDocument.getLength());
+        List<Token> tokenList = GroovySourceTokens.tokenizeLenient(text);
+        this.latestTokenList = tokenList;
+        this.latest = true;
+        int[] dirty = dirtyRange(text);
         try {
-            tokenListToRender = findTokensToRender(tokenList);
+            render(text, tokenList, dirty[0], dirty[1]);
         } finally {
             this.setRenderRange(null);
         }
+    }
 
-        boolean inAnnotation = false;
-        for (Token token : tokenListToRender) {
-            int tokenType = token.getType();
-
-            if (EOF == tokenType) {
-                continue;
-            }
-
-            int tokenStartIndex = token.getStartIndex();
-            int tokenStopIndex = token.getStopIndex();
-            int tokenLength = tokenStopIndex - tokenStartIndex + 1;
-
-            // color annotation names (identifier/dot tokens after @) with 
annotation style
-            // handles @ToString, @groovy.transform.CompileStatic, etc.
-            boolean isAnnotationPart = inAnnotation
-                    && (tokenType == Identifier || tokenType == 
CapitalizedIdentifier || tokenType == DOT);
-            if (tokenType == AT) {
-                inAnnotation = true;
-            } else if (isAnnotationPart) {
-                // stay in annotation for dotted names
-                inAnnotation = (tokenType == DOT);
-            } else {
-                inAnnotation = false;
-            }
+    private int[] dirtyRange(String text) {
+        int last = text.length() - 1;
+        if (last < 0) {
+            return new int[] {0, -1};
+        }
+        Tuple2<Integer, Integer> range = getRenderRange();
+        if (range == null) {
+            return new int[] {0, last};
+        }
+        int start = range.getV1();
+        int stop = range.getV2();
+        if (start < 0 || stop < 0) {
+            return new int[] {0, last};
+        }
+        int from = Math.max(0, Math.min(start, stop));
+        int to = Math.min(last, Math.max(start, stop));
+        return new int[] {from, to};
+    }
 
-            styledDocument.setCharacterAttributes(tokenStartIndex,
-                    tokenLength,
-                    isAnnotationPart ? findStyleByTokenType(AT) : 
findStyleByTokenType(tokenType),
-                    true);
+    private void render(String text, List<Token> antlrTokens, int dirtyStart, 
int dirtyStop) {
+        if (text.isEmpty() || dirtyStart > dirtyStop) {
+            return;
+        }
+        styledDocument.setCharacterAttributes(dirtyStart, dirtyStop - 
dirtyStart + 1, defaultStyle, true);
+        paintSemantic(text, antlrTokens, 
GroovyLanguageEngine.lexicalTokens(text, antlrTokens), dirtyStart, dirtyStop);
+    }
 
-            if (GStringBegin == tokenType || GStringPart == tokenType) {
-                styledDocument.setCharacterAttributes(
-                        tokenStartIndex + tokenLength - 1,
-                        1,
-                        defaultStyle,
-                        true);
+    private void paintSemantic(String text, List<Token> antlrTokens, 
List<GroovyLanguageEngine.Token> tokens,
+                               int dirtyStart, int dirtyStop) {
+        for (GroovyLanguageEngine.Token token : tokens) {
+            int[] span = GroovyLanguageEngine.spanOf(text, token);
+            int start = span[0];
+            int end = span[1];
+            if (end > dirtyStart && start <= dirtyStop && end > start) {
+                Style style = styleFor(token, antlrTokens, start);
+                styledDocument.setCharacterAttributes(start, end - start, 
style, true);
+                unstyleGStringDollar(antlrTokens, start, end);
             }
         }
-
-        this.latestTokenList = tokenList;
-        this.latest = true;
     }
 
-    private List<Token> findTokensToRender(List<Token> tokenList) {
-        final Tuple2<Integer, Integer> renderRange = getRenderRange();
-        if (null != renderRange) {
-            long startLine = lineNumber(renderRange.getV1());
-            long stopLine = lineNumber(renderRange.getV2());
-
-            if (startLine < 0 || stopLine < 0) return tokenList; // should 
never happen
-
-            return tokenList.stream()
-                    .filter(e -> e.getLine() >= startLine && 
PositionConfigureUtils.endPosition(e).getV1() <= stopLine)
-                    .collect(Collectors.toList());
+    private Style styleFor(GroovyLanguageEngine.Token token, List<Token> 
antlrTokens, int start) {
+        String type = token.type();
+        if ("keyword".equals(type)) {
+            return styleNamed(STYLE_RESERVED);
         }
-
-        List<Token> tmpLatestTokenList = filterNewlines(this.latestTokenList);
-        int latestTokenListSize = tmpLatestTokenList.size();
-        if (0 == latestTokenListSize) {
-            return tokenList;
+        if ("string".equals(type)) {
+            return styleNamed(stringTheme(antlrTokens, start));
         }
-
-        List<Token> tmpTokenList = filterNewlines(tokenList);
-        int tmpTokenListSize = tmpTokenList.size();
-        if (0 == tmpTokenListSize) {
-            return tokenList;
+        if ("number".equals(type)) {
+            return styleNamed(STYLE_DIGIT);
         }
-
-        int startTokenIndex = 0;
-        int minSize = Math.min(tmpTokenListSize, latestTokenListSize);
-        for (int i = 0; i < minSize; i++) {
-            Token token = tmpTokenList.get(i);
-            Token latestToken = tmpLatestTokenList.get(i);
-
-            if (token.getType() == latestToken.getType()
-                    && token.getStartIndex() == latestToken.getStartIndex()
-                    && token.getStopIndex() == latestToken.getStopIndex()) {
-                continue;
-            }
-
-            startTokenIndex = i;
-            break;
+        if ("comment".equals(type)) {
+            return styleNamed(STYLE_COMMENT);
         }
-
-        List<Token> newTokenList = new ReversedList<>(tmpTokenList);
-        List<Token> newLatestTokenList = new 
ReversedList<>(tmpLatestTokenList);
-
-        int stopTokenIndex = tmpTokenListSize;
-
-        Token lastToken = newTokenList.get(0);
-        Token lastLatestToken = newLatestTokenList.get(0);
-
-        for (int i = 0; i < minSize; i++) {
-            Token token = newTokenList.get(i);
-            Token latestToken = newLatestTokenList.get(i);
-
-            if ((token.getType() == latestToken.getType())
-                    && (token.getStartIndex() - lastToken.getStartIndex()) == 
(latestToken.getStartIndex() - lastLatestToken.getStartIndex())
-                    && ((token.getStopIndex() - lastToken.getStopIndex()) == 
(latestToken.getStopIndex() - lastLatestToken.getStopIndex()))) {
-                continue;
-            }
-
-            stopTokenIndex = tmpTokenListSize - i;
-            break;
+        if ("decorator".equals(type)) {
+            return styleNamed(STYLE_ANNOTATION);
         }
-
-        if (startTokenIndex == stopTokenIndex) {
-            return tmpTokenListSize != latestTokenListSize ? tokenList : 
Collections.emptyList();
-        } else if (startTokenIndex < stopTokenIndex) {
-            return tmpTokenList.subList(startTokenIndex, stopTokenIndex);
+        if ("operator".equals(type)) {
+            return styleNamed(STYLE_OPERATION);
         }
-
-        return tokenList; // should never reach here. If unexpected error 
occurred, it's better to render all tokens
+        return styleNamed(STYLE_IDENT);
     }
 
     /**
-     * Returns line number starting with 1
+     * Line-split semantic spans do not repeat the opening delimiter.
+     * The theme comes from the lexer token that opened the literal.
      */
-    private long lineNumber(int position) {
-        long result = -1;
-
-        if (position < 0) return result;
-
-        try {
-            String text = this.styledDocument.getText(0, position + 1);
-            result = StringUtils.countChar(text, '\n') + 1;
-        } catch (BadLocationException e) {
-            e.printStackTrace(System.err);
+    private static String stringTheme(List<Token> lexerTokens, int offset) {
+        int index = indexCovering(lexerTokens, offset);
+        if (index < 0) {
+            return STYLE_SINGLE_QUOTES;
         }
-
-        return result;
+        for (int i = index; i >= 0; i--) {
+            Token token = lexerTokens.get(i);
+            int type = token.getType();
+            if (type == GroovyLexer.StringLiteral || type == 
GroovyLexer.GStringBegin) {
+                return themeOfDelimiter(token.getText());
+            }
+            if (type != GroovyLexer.GStringPart && type != 
GroovyLexer.GStringEnd) {
+                break;
+            }
+        }
+        return STYLE_QUOTES;
     }
 
-    private List<Token> filterNewlines(List<Token> tokenList) {
-        return tokenList.stream()
-                .filter(e -> !(NL == e.getType() && 
e.getText().trim().isEmpty()))
-                .collect(Collectors.toList());
+    private static String themeOfDelimiter(String body) {
+        if (body != null && (body.startsWith("$/") || body.startsWith("/"))) {
+            return STYLE_SLASHY_QUOTES;
+        }
+        if (body != null && body.startsWith("\"")) {
+            return STYLE_QUOTES;
+        }
+        return STYLE_SINGLE_QUOTES;
     }
 
-    private Style findStyleByTokenType(int tokenType) {
-        Style style = styleContext.getStyle(String.valueOf(tokenType));
-
-        return null == style ? defaultStyle : style;
+    private void unstyleGStringDollar(List<Token> lexerTokens, int spanStart, 
int spanEnd) {
+        int index = indexCovering(lexerTokens, spanStart);
+        if (index < 0) {
+            return;
+        }
+        Token token = lexerTokens.get(index);
+        int type = token.getType();
+        if (type != GroovyLexer.GStringBegin && type != 
GroovyLexer.GStringPart) {
+            return;
+        }
+        int dollar = token.getStopIndex();
+        if (dollar >= spanStart && dollar < spanEnd) {
+            styledDocument.setCharacterAttributes(dollar, 1, defaultStyle, 
true);
+        }
     }
 
-    private Style createDefaultStyleByTokenType(int tokenType) {
-        return styleContext.addStyle(String.valueOf(tokenType), defaultStyle);
+    private static int indexCovering(List<Token> lexerTokens, int offset) {
+        if (lexerTokens == null) {
+            return -1;
+        }
+        for (int i = 0; i < lexerTokens.size(); i++) {
+            Token token = lexerTokens.get(i);
+            int start = token.getStartIndex();
+            int stop = token.getStopIndex();
+            if (start >= 0 && start <= offset && offset <= stop) {
+                return i;
+            }
+        }
+        return -1;
     }

Review Comment:
   `indexCovering` does a linear scan from the beginning for each lookup, and 
it's called per semantic token during painting (`stringTheme`, 
`unstyleGStringDollar`). For large documents this can become O(n²) and cause UI 
lag on the EDT. Consider using a binary search over token start/stop indices, 
or a single forward cursor/two-pointer approach since token spans are processed 
in increasing order.





> Wire Groovy Console and groovysh to the in-process language engine
> ------------------------------------------------------------------
>
>                 Key: GROOVY-12448
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12448
>             Project: Groovy
>          Issue Type: Improvement
>            Reporter: Daniel Sun
>            Priority: Major
>
> h2. Context
> GROOVY-12411 added {{groovy-lsp}} and an in-process 
> {{{}GroovyLanguageEngine{}}}. That work left Groovy Console and groovysh 
> unconnected. This change attaches those two hosts to the same engine so an 
> unsaved buffer can be queried for diagnostics, completion, hover, and 
> signatures without generating class files.
> Scripts still run through {{GroovyShell}} in the console and {{GroovyEngine}} 
> in groovysh.
> h2. User-visible behavior
>  * *Groovy Console.* The editor compiles the unsaved buffer in the background 
> and highlights compiler diagnostics. {{Ctrl+Space}} offers compiler-backed 
> completions. Adding a jar or directory to the classpath is picked up on the 
> next compile.
>  * *groovysh.* Tab completion and tail-tip descriptions consult the compiler 
> for the current buffer (imports, types, methods, and binding variables). 
> Syntax tail-tips report diagnostics and do not evaluate the line. {{/grab}} 
> remains the way to add Maven artifacts; the next completion or tail-tip 
> notices the loader change.
> h2. Design
>  * {{groovy-console}} and {{groovy-groovysh}} take an {{implementation}} 
> dependency on {{{}groovy-lsp{}}}.
>  * Adapters stay in the host trees: {{ConsoleLanguageHost}} (buffer URI 
> {{{}groovy-buffer:console{}}}) and {{GroovyshLanguageSession}} (buffer URI 
> {{{}groovy-buffer:repl{}}}). The engine does not assign those names. Both 
> adapters are {{@Internal}} and are not an embedding API.
>  * Queries return JDK records. Host source does not import LSP4J. Feature 
> services inside {{groovy-lsp}} still use LSP4J types, so LSP4J remains a 
> runtime dependency of these hosts.
>  * Positions are 0-based UTF-16.
>  * The compile fingerprint includes {{URLClassLoader}} URLs, so an in-place 
> {{addURL}} (console classpath menu, groovysh {{{}/grab{}}}) is visible on the 
> next compile.
>  * groovysh composes {{{}GroovyEngine.getBuffer(){}}}, declaration-only 
> binding types for names the buffer does not already introduce, and the 
> current line. Those names come from the compiled AST 
> ({{{}GroovyLanguageEngine.declaredNames{}}}).
>  * groovysh {{SYNTAX}} tail-tips return compiler diagnostics and do not fall 
> through to Inspector {{{}checkSyntax{}}}, which evaluates. {{METHOD}} 
> signatures use the full {{CmdLine.line}} so an unclosed opening parenthesis 
> is visible. Inspector is used only when the compiler has no signatures.
> h2. Out of scope
>  * Driving AstBrowser from an LSP snapshot.
>  * A public embedding API for the adapters.
>  * Splitting a protocol-free engine module out of {{{}groovy-lsp{}}}.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to