All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.deephaven.server.console.completer.JavaAutoCompleteObserver Maven / Gradle / Ivy

The newest version!
//
// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending
//
package io.deephaven.server.console.completer;

import com.google.rpc.Code;
import io.deephaven.engine.context.QueryScope;
import io.deephaven.engine.util.ScriptSession;
import io.deephaven.internal.log.LoggerFactory;
import io.deephaven.io.logger.Logger;
import io.deephaven.lang.completion.ChunkerCompleter;
import io.deephaven.lang.completion.CompletionLookups;
import io.deephaven.lang.completion.CustomCompletion;
import io.deephaven.lang.parse.CompletionParser;
import io.deephaven.lang.parse.LspTools;
import io.deephaven.lang.parse.ParsedDocument;
import io.deephaven.lang.shared.lsp.CompletionCancelled;
import io.deephaven.proto.backplane.script.grpc.*;
import io.deephaven.proto.util.Exceptions;
import io.deephaven.server.console.ConsoleServiceGrpcImpl;
import io.deephaven.server.session.SessionCloseableObserver;
import io.deephaven.server.session.SessionState;
import io.grpc.stub.StreamObserver;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.stream.Collectors;

import static io.deephaven.extensions.barrage.util.GrpcUtil.safelyComplete;
import static io.deephaven.extensions.barrage.util.GrpcUtil.safelyOnNext;

/**
 * Autocomplete handling for JVM languages, that directly can interact with Java instances without any name mangling,
 * and are able to use our flexible parser.
 */
public class JavaAutoCompleteObserver extends SessionCloseableObserver
        implements StreamObserver {

    private static final Logger log = LoggerFactory.getLogger(JavaAutoCompleteObserver.class);
    /** Track parsers by their session state, to ensure each session has its own, singleton, parser */
    private static final Map parsers = Collections.synchronizedMap(new WeakHashMap<>());

    private final CompletionParser parser;
    private final Set customCompletionFactory;

    private static CompletionParser ensureParserForSession(SessionState session) {
        return parsers.computeIfAbsent(session, s -> {
            CompletionParser parser = new CompletionParser();
            s.addOnCloseCallback(() -> {
                parsers.remove(s);
                parser.close();
            });
            return parser;
        });
    }

    public JavaAutoCompleteObserver(SessionState session, StreamObserver responseObserver,
            Set customCompletionFactory) {
        super(session, responseObserver);
        parser = ensureParserForSession(session);
        this.customCompletionFactory = customCompletionFactory;
    }

    @Override
    public void onNext(AutoCompleteRequest value) {
        switch (value.getRequestCase()) {
            case OPEN_DOCUMENT: {
                final TextDocumentItem doc = value.getOpenDocument().getTextDocument();

                parser.open(doc.getText(), doc.getUri(), Integer.toString(doc.getVersion()));
                break;
            }
            case CHANGE_DOCUMENT: {
                ChangeDocumentRequest request = value.getChangeDocument();
                final VersionedTextDocumentIdentifier text = request.getTextDocument();
                parser.update(text.getUri(), text.getVersion(),
                        request.getContentChangesList());
                break;
            }
            case CLOSE_DOCUMENT: {
                CloseDocumentRequest request = value.getCloseDocument();
                parser.remove(request.getTextDocument().getUri());
                break;
            }
            case REQUEST_NOT_SET: {
                throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Autocomplete command missing request");
            }
            default: {
                // Maintain compatibility with older clients
                // The only autocomplete type supported before the consoleId was moved to the parent request was
                // GetCompletionItems
                final io.deephaven.proto.backplane.grpc.Ticket consoleId =
                        value.hasConsoleId() ? value.getConsoleId() : value.getGetCompletionItems().getConsoleId();
                SessionState.ExportObject exportedConsole = session.getExport(consoleId, "consoleId");
                session.nonExport()
                        .require(exportedConsole)
                        .onError(responseObserver)
                        .submit(() -> {
                            handleAutocompleteRequest(value, exportedConsole, responseObserver);
                        });
            }
        }
    }

    private void handleAutocompleteRequest(AutoCompleteRequest request,
            SessionState.ExportObject exportedConsole,
            StreamObserver responseObserver) {
        // Maintain compatibility with older clients
        // The only autocomplete type supported before the consoleId was moved to the parent request was
        // GetCompletionItems
        final int requestId =
                request.getRequestId() > 0 ? request.getRequestId() : request.getGetCompletionItems().getRequestId();
        try {
            AutoCompleteResponse.Builder response = AutoCompleteResponse.newBuilder();

            switch (request.getRequestCase()) {
                case GET_COMPLETION_ITEMS: {
                    response.setCompletionItems(getCompletionItems(request.getGetCompletionItems(), exportedConsole,
                            parser, responseObserver));
                    break;
                }
                case GET_SIGNATURE_HELP: {
                    // Not implemented. Return empty signature list
                    response.setSignatures(GetSignatureHelpResponse.getDefaultInstance());
                    break;
                }
                case GET_HOVER: {
                    // Not implemented. Return empty hover help
                    response.setHover(
                            GetHoverResponse.newBuilder().setContents(MarkupContent.getDefaultInstance()).build());
                    break;
                }
                case GET_DIAGNOSTIC: {
                    // Not implemented. Return empty diagnostics
                    response.setDiagnostic(GetPullDiagnosticResponse.getDefaultInstance());
                    break;
                }
            }

            safelyOnNext(responseObserver,
                    response
                            .setSuccess(true)
                            .setRequestId(requestId)
                            .build());

        } catch (CompletionCancelled exception) {
            if (log.isTraceEnabled()) {
                log.trace().append("Completion canceled").append(exception).endl();
            }
            safelyOnNext(responseObserver,
                    AutoCompleteResponse.newBuilder()
                            .setSuccess(false)
                            .setRequestId(request.getRequestId())
                            .build());
        } catch (Exception exception) {
            if (ConsoleServiceGrpcImpl.QUIET_AUTOCOMPLETE_ERRORS) {
                if (log.isTraceEnabled()) {
                    log.trace().append("Exception occurred during autocomplete").append(exception).endl();
                }
            } else {
                log.error().append("Exception occurred during autocomplete").append(exception).endl();
            }
            safelyOnNext(responseObserver,
                    AutoCompleteResponse.newBuilder()
                            .setSuccess(false)
                            .setRequestId(request.getRequestId())
                            .build());
        }
    }

    private GetCompletionItemsResponse getCompletionItems(GetCompletionItemsRequest request,
            SessionState.ExportObject exportedConsole, CompletionParser parser,
            StreamObserver responseObserver) {
        final ScriptSession scriptSession = exportedConsole.get();
        final QueryScope vars = scriptSession.getQueryScope();
        final VersionedTextDocumentIdentifier doc = request.getTextDocument();
        final CompletionLookups h = CompletionLookups.preload(scriptSession, customCompletionFactory);
        // The only stateful part of a completer is the CompletionLookups, which are already
        // once-per-session-cached
        // so, we'll just create a new completer for each request. No need to hang onto these guys.
        final ChunkerCompleter completer = new ChunkerCompleter(log, vars, h);

        final ParsedDocument parsed = parser.finish(doc.getUri());

        int offset = LspTools.getOffsetFromPosition(parsed.getSource(),
                request.getPosition());
        final Collection results =
                completer.runCompletion(parsed, request.getPosition(), offset);
        return GetCompletionItemsResponse.newBuilder()
                .setSuccess(true)
                .setRequestId(request.getRequestId())
                .addAllItems(results.stream().map(
                        // insertTextFormat is a default we used to set in constructor; for now, we'll
                        // just process the objects before sending back to client
                        item -> item.setInsertTextFormat(2).build())
                        .collect(Collectors.toSet()))
                .build();
    }

    @Override
    public void onError(Throwable t) {
        // ignore, client doesn't need us, will be cleaned up later
    }

    @Override
    public void onCompleted() {
        // just hang up too, browser will reconnect if interested, and we'll maintain state if the session isn't gc'd
        safelyComplete(responseObserver);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy