From 6c6c3f25af5b80fe624f51bb178425cc6b52aa9f Mon Sep 17 00:00:00 2001 From: Achal Talati Date: Fri, 28 Feb 2025 10:28:14 +0530 Subject: [PATCH 1/9] Java notebooks added - registers support - save outputs - classpath option - code completions in the notebook - recovers from out-of-order cell state changes Note: notebooks module in nbcode --- build.xml | 3 + nbcode/nbproject/project.properties | 4 +- nbcode/notebooks/build.xml | 23 + nbcode/notebooks/manifest.mf | 6 + nbcode/notebooks/nbproject/build-impl.xml | 56 ++ .../notebooks/nbproject/genfiles.properties | 23 + nbcode/notebooks/nbproject/project.properties | 30 + nbcode/notebooks/nbproject/project.xml | 152 ++++ nbcode/notebooks/nbproject/suite.properties | 16 + .../nbcode/java/notebook/Bundle.properties | 17 + .../nbcode/java/notebook/CellState.java | 115 +++ .../java/notebook/CodeCompletionProvider.java | 114 +++ .../nbcode/java/notebook/CodeEval.java | 126 +++ .../notebook/NotebookCommandsHandler.java | 56 ++ .../nbcode/java/notebook/NotebookConfigs.java | 82 ++ .../NotebookDocumentServiceHandlerImpl.java | 223 ++++++ .../NotebookDocumentStateManager.java | 240 ++++++ .../java/notebook/NotebookSessionManager.java | 129 +++ .../nbcode/java/notebook/NotebookUtils.java | 96 +++ .../nbcode/java/notebook/ResultEval.java | 61 ++ patches/java-notebooks.diff | 755 ++++++++++++++++++ patches/upgrade-lsp4j.diff | 448 +++++++++++ vscode/package.json | 25 +- vscode/src/commands/commands.ts | 7 +- vscode/src/commands/create.ts | 72 +- vscode/src/configurations/configuration.ts | 1 + vscode/src/extension.ts | 4 +- .../src/lsp/listeners/requests/notebooks.ts | 27 + vscode/src/lsp/listeners/requests/register.ts | 4 +- vscode/src/lsp/nbLanguageClient.ts | 3 +- vscode/src/lsp/protocol.ts | 10 +- vscode/src/notebooks/impl.ts | 345 ++++++++ vscode/src/notebooks/register.ts | 11 + 33 files changed, 3274 insertions(+), 10 deletions(-) create mode 100644 nbcode/notebooks/build.xml create mode 100644 nbcode/notebooks/manifest.mf create mode 100644 nbcode/notebooks/nbproject/build-impl.xml create mode 100644 nbcode/notebooks/nbproject/genfiles.properties create mode 100644 nbcode/notebooks/nbproject/project.properties create mode 100644 nbcode/notebooks/nbproject/project.xml create mode 100644 nbcode/notebooks/nbproject/suite.properties create mode 100644 nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/Bundle.properties create mode 100644 nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CellState.java create mode 100644 nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeCompletionProvider.java create mode 100644 nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeEval.java create mode 100644 nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookCommandsHandler.java create mode 100644 nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java create mode 100644 nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java create mode 100644 nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java create mode 100644 nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java create mode 100644 nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtils.java create mode 100644 nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/ResultEval.java create mode 100644 patches/java-notebooks.diff create mode 100644 patches/upgrade-lsp4j.diff create mode 100644 vscode/src/lsp/listeners/requests/notebooks.ts create mode 100644 vscode/src/notebooks/impl.ts create mode 100644 vscode/src/notebooks/register.ts diff --git a/build.xml b/build.xml index 11642ba..791c91a 100644 --- a/build.xml +++ b/build.xml @@ -79,6 +79,9 @@ patches/dev-dependency-licenses.diff patches/nb-telemetry.diff patches/change-method-parameters-refactoring-qualified-names.diff + patches/javavscode-375.diff + patches/upgrade-lsp4j.diff + patches/java-notebooks.diff diff --git a/nbcode/nbproject/project.properties b/nbcode/nbproject/project.properties index f3e0906..e748b59 100644 --- a/nbcode/nbproject/project.properties +++ b/nbcode/nbproject/project.properties @@ -28,6 +28,8 @@ auxiliary.org-netbeans-modules-apisupport-installer.os-windows=false auxiliary.org-netbeans-spi-editor-hints-projects.perProjectHintSettingsFile=nbproject/cfg_hints.xml modules=\ ${project.org.netbeans.modules.nbcode.integration} :\ - ${project.org.netbeans.modules.nbcode.java.lsp.server.telemetry} + ${project.org.netbeans.modules.nbcode.java.lsp.server.telemetry}:\ + ${project.org.netbeans.modules.nbcode.java.notebooks} project.org.netbeans.modules.nbcode.integration=integration project.org.netbeans.modules.nbcode.java.lsp.server.telemetry=telemetry +project.org.netbeans.modules.nbcode.java.notebooks=notebooks diff --git a/nbcode/notebooks/build.xml b/nbcode/notebooks/build.xml new file mode 100644 index 0000000..7edfeac --- /dev/null +++ b/nbcode/notebooks/build.xml @@ -0,0 +1,23 @@ + + + + Builds, tests, and runs the project org.netbeans.modules.nbcode.notebook. + + + + + diff --git a/nbcode/notebooks/manifest.mf b/nbcode/notebooks/manifest.mf new file mode 100644 index 0000000..4e40170 --- /dev/null +++ b/nbcode/notebooks/manifest.mf @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +AutoUpdate-Show-In-Client: false +OpenIDE-Module: org.netbeans.modules.nbcode.java.notebook +OpenIDE-Module-Implementation-Version: 1 +OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/nbcode/java/notebook/Bundle.properties + diff --git a/nbcode/notebooks/nbproject/build-impl.xml b/nbcode/notebooks/nbproject/build-impl.xml new file mode 100644 index 0000000..dade1bd --- /dev/null +++ b/nbcode/notebooks/nbproject/build-impl.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + You must set 'suite.dir' to point to your containing module suite + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nbcode/notebooks/nbproject/genfiles.properties b/nbcode/notebooks/nbproject/genfiles.properties new file mode 100644 index 0000000..4f8a333 --- /dev/null +++ b/nbcode/notebooks/nbproject/genfiles.properties @@ -0,0 +1,23 @@ +# +# Copyright (c) 2024-2025, Oracle and/or its affiliates. +# +# Licensed 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 +# +# https://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. +# +build.xml.data.CRC32=bcbc94fb +build.xml.script.CRC32=f4d83a2b +build.xml.stylesheet.CRC32=15ca8a54@2.97 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=decee057 +nbproject/build-impl.xml.script.CRC32=547e2c6a +nbproject/build-impl.xml.stylesheet.CRC32=49aa68b0@2.97 diff --git a/nbcode/notebooks/nbproject/project.properties b/nbcode/notebooks/nbproject/project.properties new file mode 100644 index 0000000..787b525 --- /dev/null +++ b/nbcode/notebooks/nbproject/project.properties @@ -0,0 +1,30 @@ +# +# Copyright (c) 2024-2025, Oracle and/or its affiliates. +# +# Licensed 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 +# +# https://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. +# +javac.source=1.8 +requires.nb.javac=true +javac.compilerargs=-Xlint -Xlint:-serial +test.run.args=--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ + --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED \ + --add-exports=jdk.compiler/com.sun.tools.javac.resources=ALL-UNNAMED \ + --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \ + +test.unit.lib.cp= +test.unit.run.cp.extra= +license.file=../../LICENSE.txt +nbm.homepage=https://github.com/oracle/javavscode/ +nbplatform.default.netbeans.dest.dir=${suite.dir}/../netbeans/nbbuild/netbeans +nbplatform.default.harness.dir=${nbplatform.default.netbeans.dest.dir}/harness +spec.version.base=1.0 diff --git a/nbcode/notebooks/nbproject/project.xml b/nbcode/notebooks/nbproject/project.xml new file mode 100644 index 0000000..04ac8d8 --- /dev/null +++ b/nbcode/notebooks/nbproject/project.xml @@ -0,0 +1,152 @@ + + + + org.netbeans.modules.apisupport.project + + + org.netbeans.modules.nbcode.java.notebook + + + + com.google.gson + + + + 2.11.0 + + + + org.netbeans.api.lsp + + + + 1 + 1.28 + + + + org.netbeans.libs.javacapi + + + + + + + + org.netbeans.modules.editor.mimelookup + + + + 1 + 1.65 + + + + org.netbeans.modules.java.lsp.server + + + + 2 + + + + + org.netbeans.modules.java.platform + + + + 1 + 1.67 + + + + org.netbeans.modules.java.project + + + + 1 + 1.97 + + + + org.netbeans.modules.java.source.base + + + + + + + + org.netbeans.modules.projectapi + + + + 1 + 1.96 + + + + org.openide.actions + + + + 6.66 + + + + org.openide.filesystems + + + + 9.38 + + + + org.openide.util + + + + 9.33 + + + + org.openide.util.lookup + + + + 8.59 + + + + + + unit + + org.netbeans.libs.junit4 + + + + org.netbeans.modules.nbjunit + + + + + + + + + diff --git a/nbcode/notebooks/nbproject/suite.properties b/nbcode/notebooks/nbproject/suite.properties new file mode 100644 index 0000000..0b44bb9 --- /dev/null +++ b/nbcode/notebooks/nbproject/suite.properties @@ -0,0 +1,16 @@ +# +# Copyright (c) 2024-2025, Oracle and/or its affiliates. +# +# Licensed 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 +# +# https://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. +# +suite.dir=${basedir}/.. diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/Bundle.properties b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/Bundle.properties new file mode 100644 index 0000000..2419dbc --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/Bundle.properties @@ -0,0 +1,17 @@ +# +# Copyright (c) 2024-2025, Oracle and/or its affiliates. +# +# Licensed 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 +# +# https://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. +# +OpenIDE-Module-Display-Category=Java +OpenIDE-Module-Name=Java notebooks diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CellState.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CellState.java new file mode 100644 index 0000000..9264167 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CellState.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed 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 + * + * https://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.netbeans.modules.nbcode.java.notebook; + +import java.util.concurrent.atomic.AtomicReference; +import org.eclipse.lsp4j.ExecutionSummary; +import org.eclipse.lsp4j.NotebookCell; +import org.eclipse.lsp4j.NotebookCellKind; +import org.eclipse.lsp4j.TextDocumentItem; + +/** + * + * @author atalati + */ +public class CellState { + + private final NotebookCellKind type; + private final String language; + private final AtomicReference metadata; + private final AtomicReference content; + private final AtomicReference executionSummary; + + CellState(NotebookCell notebookCell, TextDocumentItem details) { + String normalizedText = NotebookUtils.normalizeLineEndings(details.getText()); + this.content = new AtomicReference<>(new VersionAwareCotent(normalizedText, details.getVersion())); + this.language = details.getLanguageId(); + this.type = notebookCell.getKind(); + this.metadata = new AtomicReference<>(notebookCell.getMetadata()); + this.executionSummary = new AtomicReference<>(notebookCell.getExecutionSummary()); + } + + public String getLanguage() { + return language; + } + + public String getContent() { + return content.get().getContent(); + } + + public NotebookCellKind getType() { + return type; + } + + public Object getMetadata() { + return metadata.get(); + } + + public ExecutionSummary getExecutionSummary() { + return executionSummary.get(); + } + + public void setContent(String newContent, int newVersion) { + String normalizedContent = NotebookUtils.normalizeLineEndings(newContent); + VersionAwareCotent currentContent = content.get(); + + if (currentContent.getVersion() != newVersion - 1) { + throw new IllegalStateException("Version mismatch: expected " + (newVersion - 1) + ", got " + currentContent.getVersion()); + } + + VersionAwareCotent newVersionContent = new VersionAwareCotent(normalizedContent, newVersion); + + if (!content.compareAndSet(currentContent, newVersionContent)) { + throw new IllegalStateException("Concurrent modification detected. Version expected: " + (newVersion - 1) + ", current: " + content.get().getVersion()); + } + } + + public void setExecutionSummary(ExecutionSummary executionSummary) { + this.executionSummary.set(executionSummary); + } + + public void setMetadata(Object metadata) { + this.metadata.set(metadata); + } + + private class VersionAwareCotent { + + private String content; + private int version; + + public VersionAwareCotent(String content, int version) { + this.content = content; + this.version = version; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + } +} diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeCompletionProvider.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeCompletionProvider.java new file mode 100644 index 0000000..f794820 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeCompletionProvider.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed 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 + * + * https://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.netbeans.modules.nbcode.java.notebook; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import jdk.jshell.JShell; +import jdk.jshell.SourceCodeAnalysis; +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.CompletionList; +import org.eclipse.lsp4j.CompletionParams; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.jsonrpc.messages.Either; + +/** + * + * @author atalati + */ +public class CodeCompletionProvider { + + private CodeCompletionProvider() { + } + + public static CodeCompletionProvider getInstance() { + return Singleton.instance; + } + + private static class Singleton { + + private static final CodeCompletionProvider instance = new CodeCompletionProvider(); + } + + public CompletableFuture, CompletionList>> getCodeCompletions(CompletionParams params, NotebookDocumentStateManager state, JShell instance) { + try { + String uri = params.getTextDocument().getUri(); + CellState cellState = state.getCell(uri); + String content = cellState.getContent(); + + Position position = params.getPosition(); + int cursorOffset = NotebookUtils.getOffset(content, position); + + String inputText = content.substring(0, cursorOffset); + + SourceCodeAnalysis sourceAnalysis = instance.sourceCodeAnalysis(); + int[] anchor = new int[1]; + + sourceAnalysis.analyzeCompletion(inputText); + // Need to get snippets because JShell doesn't provide suggestions sometimes if import statement is there in the cell + String finalString = getSnippets(sourceAnalysis, inputText).getLast(); + finalString = finalString.charAt(finalString.length() - 1) == ';' ? finalString.substring(0, finalString.length() - 1) : finalString; + + List suggestions = sourceAnalysis.completionSuggestions( + finalString, finalString.length(), anchor); + + List completionItems = new ArrayList<>(); + Map visited = new HashMap<>(); + + for (SourceCodeAnalysis.Suggestion suggestion : suggestions) { + if (visited.containsKey(suggestion.continuation())) { + continue; + } + + CompletionItem item = new CompletionItem(); + item.setLabel(suggestion.continuation()); + + if (!suggestion.continuation().isEmpty()) { + item.setDocumentation(suggestion.continuation()); + } + + completionItems.add(item); + visited.put(suggestion.continuation(), Boolean.TRUE); + } + + return CompletableFuture.completedFuture(Either., CompletionList>forLeft(completionItems)); + } catch (Exception e) { + System.err.println("Error getting code completions: " + e.getMessage()); + return CompletableFuture.completedFuture(Either., CompletionList>forLeft(new ArrayList<>())); + } + } + + private List getSnippets(SourceCodeAnalysis analysis, String code) { + String codeRemaining = code.trim(); + + List codeSnippets = new ArrayList<>(); + while (!codeRemaining.isEmpty()) { + SourceCodeAnalysis.CompletionInfo info = analysis.analyzeCompletion(codeRemaining); + if (info.completeness().isComplete()) { + codeSnippets.add(info.source()); + } else { + codeSnippets.add(codeRemaining); + break; + } + codeRemaining = info.remaining().trim(); + } + + return codeSnippets; + } +} diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeEval.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeEval.java new file mode 100644 index 0000000..da72165 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeEval.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed 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 + * + * https://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.netbeans.modules.nbcode.java.notebook; + +import jdk.jshell.JShell; +import jdk.jshell.SnippetEvent; +import com.google.gson.JsonPrimitive; +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import jdk.jshell.SourceCodeAnalysis; + +/** + * + * @author atalati + */ +public class CodeEval { + + private static final Logger LOG = Logger.getLogger(CodeEval.class.getName()); + + public static List evaluate(List arguments) { + if (arguments != null) { + String sourceCode = null, notebookId = null; + if (arguments.get(0) != null && arguments.get(0) instanceof JsonPrimitive) { + sourceCode = ((JsonPrimitive) arguments.get(0)).getAsString(); + } + if (arguments.size() > 1 && arguments.get(1) != null && arguments.get(1) instanceof JsonPrimitive) { + notebookId = ((JsonPrimitive) arguments.get(1)).getAsString(); + } + if (sourceCode != null && notebookId != null) { + JShell jshell = NotebookSessionManager.getInstance().getSession(notebookId); + + ByteArrayOutputStream outputStream = NotebookSessionManager.getInstance().getOutputStreamById(notebookId); + ByteArrayOutputStream errorStream = NotebookSessionManager.getInstance().getErrorStreamById(notebookId); + + if (jshell == null) { + throw new ExceptionInInitializerError("Error creating session for notebook"); + } + + return runCode(jshell, sourceCode, outputStream, errorStream, true); + } + LOG.warning("sourceCode or notebookId are not present in code cell evaluation request"); + } else { + LOG.warning("Empty arguments recevied in code cell evaluate request"); + } + + return new ArrayList<>(); + } + + public static List runCode(JShell jshell, String code) { + List results = new ArrayList<>(); + try { + List events = jshell.eval(code); + events.forEach(event -> { + if (event.value() != null && !event.value().isEmpty()) { + results.add(ResultEval.text(event.value())); + } + if (event.exception() != null) { + results.add(ResultEval.text(event.exception().getMessage())); + } + }); + } catch (Exception e) { + LOG.log(Level.SEVERE, "Error while executing code in JShell instance {0}", e.getMessage()); + } + return results; + } + + public static List runCode(JShell jshell, String code, ByteArrayOutputStream outStream, ByteArrayOutputStream errStream, boolean getVariableValues) { + List results = new ArrayList<>(); + try { + String codeLeftToEval = code.trim(); + while (!codeLeftToEval.isEmpty()) { + SourceCodeAnalysis analysis = jshell.sourceCodeAnalysis(); + SourceCodeAnalysis.CompletionInfo info = analysis.analyzeCompletion(codeLeftToEval); + if (info.completeness().isComplete()) { + for (SnippetEvent event : jshell.eval(info.source())) { + if (event.exception() != null) { + results.add(ResultEval.text(event.exception().getMessage())); + } else if (event.value() != null && getVariableValues) { + results.add(ResultEval.text(event.value())); + } + + jshell.diagnostics(event.snippet()).forEach(diag + -> results.add(ResultEval.text(diag.getMessage(null))) + ); + + flushStreams(results, outStream, errStream); + } + } else { + results.add(ResultEval.text("Code snippet is incomplete")); + } + codeLeftToEval = info.remaining(); + } + } catch (Exception e) { + LOG.log(Level.SEVERE, "Error while evaluation of the code : {0}", e.getMessage()); + results.add(ResultEval.text(("Evaluation error: " + e.getMessage()))); + } + return results; + } + + private static void flushStreams(List results, ByteArrayOutputStream out, ByteArrayOutputStream err) { + if (out.size() > 0) { + results.add(ResultEval.text(out.toString())); + out.reset(); + } + if (err.size() > 0) { + results.add(ResultEval.text(err.toString())); + err.reset(); + } + } +} diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookCommandsHandler.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookCommandsHandler.java new file mode 100644 index 0000000..06f21f8 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookCommandsHandler.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed 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 + * + * https://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.netbeans.modules.nbcode.java.notebook; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import org.netbeans.spi.lsp.CommandProvider; +import org.openide.util.lookup.ServiceProvider; + +/** + * + * @author atalati + */ +@ServiceProvider(service = CommandProvider.class) +public class NotebookCommandsHandler implements CommandProvider { + + private static final String NBLS_JSHELL_EXEC = "nbls.jshell.execute.cell"; + private static final String NBLS_JSHELL_CLOSE = "nbls.jshell.cleanup"; + private static final Set COMMANDS = new HashSet<>(Arrays.asList(NBLS_JSHELL_EXEC, NBLS_JSHELL_CLOSE)); + + @Override + public Set getCommands() { + return COMMANDS; + } + + @Override + public CompletableFuture runCommand(String command, List arguments) { + try { + + switch (command) { + case NBLS_JSHELL_EXEC: + return CompletableFuture.completedFuture(CodeEval.evaluate(arguments)); + default: + return CompletableFuture.failedFuture(new UnsupportedOperationException("Command not supported: " + command)); + } + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } +} diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java new file mode 100644 index 0000000..15c071c --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed 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 + * + * https://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.netbeans.modules.nbcode.java.notebook; + +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import java.util.List; +import java.util.concurrent.ExecutionException; +import org.eclipse.lsp4j.ConfigurationItem; +import org.eclipse.lsp4j.ConfigurationParams; +import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient; + +/** + * + * @author atalati + */ +public class NotebookConfigs { + + private static final String NOTEBOOK_JDK_HOME = "notebook.jdkhome"; + private String jdkVersion = null; + private NbCodeLanguageClient client = null; + + private NotebookConfigs() { + } + + public static NotebookConfigs getInstance() { + return Singleton.instance; + } + + private static class Singleton { + + private static final NotebookConfigs instance = new NotebookConfigs(); + } + + public void setLanguageClient(NbCodeLanguageClient client) { + this.client = client; + } + + public NbCodeLanguageClient getLanguageClient() { + return client; + } + + public String getJdkVersion() throws InterruptedException, ExecutionException { + // Figure out how to get Java major version from the path to jdk home + // Option-1: Run java --version in a different process and get it's output. + // Option-2: Check release file in the home path it might have info about major version. + String notebookJdkVersion = jdkVersion != null ? jdkVersion : null; + if (notebookJdkVersion != null && client != null) { + if (client != null) { + ConfigurationItem configItem = new ConfigurationItem(); + configItem.setSection(client.getNbCodeCapabilities().getConfigurationPrefix() + NOTEBOOK_JDK_HOME); + notebookJdkVersion = client.configuration(new ConfigurationParams(List.of(configItem))).thenApply((c) -> { + if (c != null) { + return ((JsonPrimitive) c.get(0)).getAsString(); + } + return null; + }).get(); + if (notebookJdkVersion != null) { + this.jdkVersion = notebookJdkVersion; + } + } + } + return notebookJdkVersion; + } + + public void notebookConfigsChangeListener(JsonObject settings) { + // depends on #8514 PR open in Netbeans + } +} diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java new file mode 100644 index 0000000..cb15e45 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed 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 + * + * https://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.netbeans.modules.nbcode.java.notebook; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import jdk.jshell.JShell; +import org.eclipse.lsp4j.CallHierarchyItem; +import org.eclipse.lsp4j.CallHierarchyPrepareParams; +import org.eclipse.lsp4j.CodeAction; +import org.eclipse.lsp4j.CodeActionParams; +import org.eclipse.lsp4j.CodeLens; +import org.eclipse.lsp4j.CodeLensParams; +import org.eclipse.lsp4j.Command; +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.CompletionList; +import org.eclipse.lsp4j.CompletionParams; +import org.eclipse.lsp4j.DefinitionParams; +import org.eclipse.lsp4j.services.LanguageClient; +import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient; +import org.netbeans.modules.java.lsp.server.notebook.NotebookDocumentServiceHandler; +import org.eclipse.lsp4j.DidChangeNotebookDocumentParams; +import org.eclipse.lsp4j.DidCloseNotebookDocumentParams; +import org.eclipse.lsp4j.DidOpenNotebookDocumentParams; +import org.eclipse.lsp4j.DidSaveNotebookDocumentParams; +import org.eclipse.lsp4j.DocumentHighlight; +import org.eclipse.lsp4j.DocumentHighlightParams; +import org.eclipse.lsp4j.FoldingRange; +import org.eclipse.lsp4j.FoldingRangeRequestParams; +import org.eclipse.lsp4j.Hover; +import org.eclipse.lsp4j.HoverParams; +import org.eclipse.lsp4j.ImplementationParams; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.LocationLink; +import org.eclipse.lsp4j.PrepareRenameDefaultBehavior; +import org.eclipse.lsp4j.PrepareRenameParams; +import org.eclipse.lsp4j.PrepareRenameResult; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.ReferenceParams; +import org.eclipse.lsp4j.SemanticTokens; +import org.eclipse.lsp4j.SemanticTokensParams; +import org.eclipse.lsp4j.SignatureHelp; +import org.eclipse.lsp4j.SignatureHelpParams; +import org.eclipse.lsp4j.TextEdit; +import org.eclipse.lsp4j.TypeDefinitionParams; +import org.eclipse.lsp4j.WillSaveTextDocumentParams; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.lsp4j.jsonrpc.messages.Either3; +import org.openide.util.lookup.ServiceProvider; + +/** + * + * @author atalati + */ +@ServiceProvider(service = NotebookDocumentServiceHandler.class) +public class NotebookDocumentServiceHandlerImpl implements NotebookDocumentServiceHandler { + + private static final Logger LOG = Logger.getLogger(NotebookDocumentServiceHandler.class.getName()); + private NbCodeLanguageClient client; + private final Map notebookStateMap = new ConcurrentHashMap<>(); + // Below map is required because completion request doesn't send notebook uri in the params + private final Map notebookCellMap = new ConcurrentHashMap<>(); + + @Override + public void didOpen(DidOpenNotebookDocumentParams params) { + try { + NotebookSessionManager.getInstance().createSession(params.getNotebookDocument()); + NotebookDocumentStateManager state = new NotebookDocumentStateManager(params.getNotebookDocument(), params.getCellTextDocuments()); + params.getNotebookDocument().getCells().forEach(cell -> { + notebookCellMap.put(cell.getDocument(), params.getNotebookDocument().getUri()); + }); + + notebookStateMap.put(params.getNotebookDocument().getUri(), state); + + } catch (Exception e) { + LOG.log(Level.SEVERE, "Error while opening notebook {0}", e.getMessage()); + } + } + + @Override + public void didChange(DidChangeNotebookDocumentParams params) { + NotebookDocumentStateManager state = notebookStateMap.get(params.getNotebookDocument().getUri()); + state.syncState(params.getNotebookDocument(), params.getChange(), notebookCellMap); + } + + @Override + public void didSave(DidSaveNotebookDocumentParams params) { + // do nothing + } + + @Override + public void didClose(DidCloseNotebookDocumentParams params) { + NotebookSessionManager.getInstance().closeSession(params.getNotebookDocument().getUri()); + } + + @Override + public CompletableFuture, CompletionList>> completion(CompletionParams params) { + try { + String cellUri = params.getTextDocument().getUri(); + String notebookUri = notebookCellMap.get(cellUri); + NotebookDocumentStateManager stateManager = notebookStateMap.get(notebookUri); + JShell instance = NotebookSessionManager.getInstance().getSession(notebookUri); + + return CodeCompletionProvider.getInstance().getCodeCompletions(params, stateManager, instance); + } catch (Exception e) { + LOG.log(Level.SEVERE, "Unable to compute code completions {0}", e.getMessage()); + return CompletableFuture.completedFuture(Either.forRight(new CompletionList())); + } + } + + @Override + public void connect(LanguageClient client) { + this.client = (NbCodeLanguageClient) client; + NotebookConfigs.getInstance().setLanguageClient((NbCodeLanguageClient) client); + } + + public NbCodeLanguageClient getNbCodeLanguageClient() { + return this.client; + } + + @Override + public CompletableFuture semanticTokensFull(SemanticTokensParams params) { + LOG.finer("SemanticTokensFull is not supported yet in notebookDocumentService"); + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletableFuture> prepareCallHierarchy(CallHierarchyPrepareParams params) { + LOG.finer("prepareCallHierarchy is not supported yet in notebookDocumentService"); + return CompletableFuture.completedFuture(Collections.emptyList()); + } + + @Override + public CompletableFuture> prepareRename(PrepareRenameParams params) { + LOG.finer("prepareRename is not supported yet in notebookDocumentService"); + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletableFuture> foldingRange(FoldingRangeRequestParams params) { + LOG.finer("foldingRange is not supported yet in notebookDocumentService"); + return CompletableFuture.completedFuture(Collections.emptyList()); + } + + @Override + public CompletableFuture> willSaveWaitUntil(WillSaveTextDocumentParams params) { + LOG.finer("willSaveWaitUntil is not supported yet in notebookDocumentService"); + return CompletableFuture.completedFuture(Collections.emptyList()); + } + + @Override + public CompletableFuture> codeLens(CodeLensParams params) { + LOG.finer("codeLens is not supported yet in notebookDocumentService"); + return CompletableFuture.completedFuture(Collections.emptyList()); + } + + @Override + public CompletableFuture>> codeAction(CodeActionParams params) { + LOG.finer("codeAction is not supported yet in notebookDocumentService"); + return CompletableFuture.completedFuture(Collections.emptyList()); + } + + @Override + public CompletableFuture> documentHighlight(DocumentHighlightParams params) { + LOG.finer("documentHighlight is not supported yet in notebookDocumentService"); + return CompletableFuture.completedFuture(Collections.emptyList()); + } + + @Override + public CompletableFuture> references(ReferenceParams params) { + LOG.finer("references is not supported yet in notebookDocumentService"); + return CompletableFuture.completedFuture(Collections.emptyList()); + } + + @Override + public CompletableFuture, List>> implementation(ImplementationParams params) { + LOG.finer("implementation is not supported yet in notebookDocumentService"); + return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList())); + } + + @Override + public CompletableFuture, List>> typeDefinition(TypeDefinitionParams params) { + LOG.finer("typeDefinition is not supported yet in notebookDocumentService"); + return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList())); + } + + @Override + public CompletableFuture, List>> definition(DefinitionParams params) { + LOG.finer("definition is not supported yet in notebookDocumentService"); + return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList())); + } + + @Override + public CompletableFuture signatureHelp(SignatureHelpParams params) { + LOG.finer("signatureHelp is not supported yet in notebookDocumentService"); + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletableFuture hover(HoverParams params) { + LOG.finer("hover is not supported yet in notebookDocumentService"); + return CompletableFuture.completedFuture(null); + } + +} diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java new file mode 100644 index 0000000..5fb6034 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed 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 + * + * https://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.netbeans.modules.nbcode.java.notebook; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.eclipse.lsp4j.NotebookCell; +import org.eclipse.lsp4j.NotebookDocument; +import org.eclipse.lsp4j.NotebookDocumentChangeEvent; +import org.eclipse.lsp4j.NotebookDocumentChangeEventCellStructure; +import org.eclipse.lsp4j.NotebookDocumentChangeEventCellTextContent; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.TextDocumentContentChangeEvent; +import org.eclipse.lsp4j.TextDocumentItem; +import org.eclipse.lsp4j.VersionedNotebookDocumentIdentifier; +import org.netbeans.modules.java.lsp.server.notebook.NotebookCellContentRequestParams; +import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient; + +/** + * + * @author atalati + */ +public class NotebookDocumentStateManager { + + private static final Logger LOG = Logger.getLogger(NotebookDocumentStateManager.class.getName()); + + private final NotebookDocument notebookDoc; + private final Map cellsMap = new ConcurrentHashMap<>(); + private final List cellsOrder; + + public NotebookDocumentStateManager(NotebookDocument notebookDoc, List cells) { + this.notebookDoc = notebookDoc; + this.cellsOrder = new ArrayList<>(); + for (int i = 0; i < cells.size(); i++) { + addNewCellState(notebookDoc.getCells().get(i), cells.get(i)); + this.cellsOrder.add(cells.get(i).getUri()); + } + } + + public void syncState(VersionedNotebookDocumentIdentifier notebook, NotebookDocumentChangeEvent changeEvent, Map cellsNotebookMap) { + try { + if (changeEvent.getCells() != null) { + updateNotebookCellStructure(changeEvent.getCells().getStructure(), cellsNotebookMap); + updateNotebookCellData(changeEvent.getCells().getData()); + + if (changeEvent.getCells().getTextContent() != null) { + for (NotebookDocumentChangeEventCellTextContent contentChange : changeEvent.getCells().getTextContent()) { + updateNotebookCellContent(contentChange); + } + } + } + } catch (Exception e) { + LOG.log(Level.WARNING, "Failed to sync notebook state", e); + throw new RuntimeException("Failed to sync notebook state", e); + } + } + + public NotebookDocument getNotebookDocument() { + return notebookDoc; + } + + public CellState getCell(String uri) { + return cellsMap.get(uri); + } + + private void updateNotebookCellStructure(NotebookDocumentChangeEventCellStructure updatedStructure, Map cellsNotebookMap) { + if (updatedStructure == null) { + return; + } + // Handle deleted cells + int deletedCells = updatedStructure.getArray().getDeleteCount(); + if (deletedCells > 0 && updatedStructure.getDidClose() != null) { + updatedStructure.getDidClose().forEach(cell -> { + String uri = cell.getUri(); + + CellState removed = cellsMap.remove(uri); + cellsNotebookMap.remove(uri); + cellsOrder.remove(uri); + if (removed != null) { + LOG.log(Level.FINE, "Removed cell: {0}", uri); + } + }); + } + + // Handle added cells + int startIdx = updatedStructure.getArray().getStart(); + List cellsItem = updatedStructure.getDidOpen(); + List cellsDetail = updatedStructure.getArray().getCells(); + + if (cellsItem != null && cellsDetail != null && cellsDetail.size() == cellsItem.size()) { + for (int i = 0; i < cellsDetail.size(); i++) { + addNewCellState(cellsDetail.get(i), cellsItem.get(i)); + cellsNotebookMap.put(cellsItem.get(i).getUri(), notebookDoc.getUri()); + if (startIdx + i <= cellsOrder.size()) { + cellsOrder.add(startIdx + i, cellsItem.get(i).getUri()); + } else { + LOG.warning("unable to add cell in the list of cells"); + } + } + } else { + LOG.severe("cell details is null or array size mismatch is present"); + throw new IllegalStateException("Error while adding cell to the notebook state"); + } + + } + + private void updateNotebookCellData(List data) { + if (data == null) { + return; + } + + data.forEach(cell -> { + String cellUri = cell.getDocument(); + CellState cellState = cellsMap.get(cellUri); + if (cellState != null) { + cellState.setExecutionSummary(cell.getExecutionSummary()); + cellState.setMetadata(cell.getMetadata()); + LOG.log(Level.FINE, "Updated cell data for: {0}", cellUri); + } else { + LOG.log(Level.WARNING, "Attempted to update non-existent cell: {0}", cellUri); + } + }); + } + + private void updateNotebookCellContent(NotebookDocumentChangeEventCellTextContent contentChange) { + if (contentChange == null) { + return; + } + String uri = contentChange.getDocument().getUri(); + CellState cellState = cellsMap.get(uri); + if (cellState == null) { + LOG.log(Level.WARNING, "Attempted to update content of non-existent cell: {0}", uri); + return; + } + int newVersion = contentChange.getDocument().getVersion(); + String currentContent = cellState.getContent(); + if (!contentChange.getChanges().isEmpty()) { + try { + String updatedContent = applyContentChanges(currentContent, contentChange.getChanges()); + cellState.setContent(updatedContent, newVersion); + LOG.log(Level.FINE, "Updated content for cell: {0}, version: {1}", new Object[]{uri, newVersion}); + } catch (Exception e) { + LOG.log(Level.SEVERE, "Failed to apply content changes to cell: " + uri, e); + throw new RuntimeException("Failed to apply content changes", e); + } + } + } + + private String applyContentChanges(String originalContent, List changes) { + if (originalContent == null) { + originalContent = ""; + } + + String currentContent = originalContent; + + for (TextDocumentContentChangeEvent change : changes) { + if (change.getRange() != null) { + currentContent = applyRangeChange(currentContent, change); + } else { + currentContent = change.getText(); + } + } + + return currentContent; + } + + private String applyRangeChange(String content, TextDocumentContentChangeEvent change) { + Range range = change.getRange(); + Position start = range.getStart(); + Position end = range.getEnd(); + + String[] lines = content.split("\n", -1); + + if (start.getLine() < 0 || start.getLine() >= lines.length + || end.getLine() < 0 || end.getLine() >= lines.length) { + throw new IllegalArgumentException("Invalid range positions"); + } + + StringBuilder result = new StringBuilder(); + + for (int i = 0; i < start.getLine(); i++) { + result.append(lines[i]); + if (i < lines.length - 1) { + result.append("\n"); + } + } + + String startLine = lines[start.getLine()]; + String beforeChange = startLine.substring(0, Math.min(start.getCharacter(), startLine.length())); + result.append(beforeChange); + + result.append(change.getText()); + + String endLine = lines[end.getLine()]; + String afterChange = endLine.substring(Math.min(end.getCharacter(), endLine.length())); + result.append(afterChange); + + for (int i = end.getLine() + 1; i < lines.length; i++) { + result.append("\n").append(lines[i]); + } + + return result.toString(); + } + + private void addNewCellState(NotebookCell cell, TextDocumentItem item) { + if (cell == null || item == null) { + LOG.log(Level.WARNING, "Attempted to add null cell or item"); + return; + } + + try { + CellState cellState = new CellState(cell, item); + cellsMap.put(item.getUri(), cellState); + LOG.log(Level.FINE, "Added new cell state: {0}", item.getUri()); + } catch (Exception e) { + LOG.log(Level.SEVERE, "Failed to create cell state for: " + item.getUri(), e); + throw new RuntimeException("Failed to create cell state", e); + } + } +} diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java new file mode 100644 index 0000000..33b61b9 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed 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 + * + * https://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.netbeans.modules.nbcode.java.notebook; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.logging.Logger; +import jdk.jshell.JShell; +import org.eclipse.lsp4j.NotebookDocument; + +/** + * + * @author atalati + */ +public class NotebookSessionManager { + + private static final Logger LOGGER = Logger.getLogger(NotebookSessionManager.class.getName()); + private final Map sessions = new ConcurrentHashMap<>(); + private final Map outputStreams = new ConcurrentHashMap<>(); + private final Map errorStreams = new ConcurrentHashMap<>(); + private static final String SOURCE_FLAG = "--source"; + private static final String ENABLE_PREVIEW = "--enable-preview"; + + private NotebookSessionManager() { + } + + public static NotebookSessionManager getInstance() { + return Singleton.instance; + } + + private static class Singleton { + + private static final NotebookSessionManager instance = new NotebookSessionManager(); + } + + private JShell jshellBuilder(PrintStream outPrintStream, PrintStream errPrintStream) throws InterruptedException, ExecutionException { + List compilerOptions = new ArrayList<>(); + List remoteOptions = new ArrayList<>(); + + boolean isEnablePreview = true; + if (isEnablePreview) { + compilerOptions.add(ENABLE_PREVIEW); + } + + String notebookJdkVersion = NotebookConfigs.getInstance().getJdkVersion(); + if (notebookJdkVersion == null) { + notebookJdkVersion = System.getProperty("java.version").split("\\.")[0]; + } + + compilerOptions.addAll(List.of(SOURCE_FLAG, notebookJdkVersion)); + + return JShell.builder() + .out(outPrintStream) + .err(errPrintStream) + .compilerOptions(compilerOptions.toArray(new String[0])) + .remoteVMOptions(remoteOptions.toArray(new String[0])) + .build(); + } + + public void createSession(NotebookDocument notebookDoc) { + String notebookId = notebookDoc.getUri(); + + sessions.computeIfAbsent(notebookId, (String id) -> { + try { + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + ByteArrayOutputStream errStream = new ByteArrayOutputStream(); + outputStreams.put(id, outStream); + errorStreams.put(id, errStream); + + PrintStream outPrintStream = new PrintStream(outStream, true); + PrintStream errPrintStream = new PrintStream(errStream, true); + + JShell jshell = jshellBuilder(outPrintStream, errPrintStream); + jshell.onShutdown(shell -> closeSession(notebookId)); + + boolean implicitImports = true; + if (implicitImports) { + List.of("java.util", "java.io", "java.math") + .forEach(pkg -> CodeEval.runCode(jshell, "import " + pkg + ".*")); + } + + return jshell; + } catch (IllegalStateException | InterruptedException | ExecutionException e) { + LOGGER.log(Level.SEVERE, "Error creating notebook session: {0}", e.getMessage()); + throw new IllegalStateException("Error while creating notebook session"); + } + }); + } + + public JShell getSession(String notebookId) { + return sessions.get(notebookId); + } + + public ByteArrayOutputStream getOutputStreamById(String notebookId) { + return outputStreams.get(notebookId); + } + + public ByteArrayOutputStream getErrorStreamById(String notebookId) { + return errorStreams.get(notebookId); + } + + public void closeSession(String notebookUri) { + JShell jshell = sessions.remove(notebookUri); + if (jshell != null) { + jshell.close(); + } + outputStreams.remove(notebookUri); + errorStreams.remove(notebookUri); + } +} diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtils.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtils.java new file mode 100644 index 0000000..5f41140 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtils.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed 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 + * + * https://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.netbeans.modules.nbcode.java.notebook; + +import org.eclipse.lsp4j.Position; + +/** + * + * @author atalati + */ +public class NotebookUtils { + + public static String normalizeLineEndings(String text) { + if (text == null) { + return null; + } + + if (text.indexOf('\r') == -1) { + return text; + } + + StringBuilder normalized = new StringBuilder(text.length()); + int len = text.length(); + + for (int i = 0; i < len; i++) { + char c = text.charAt(i); + if (c == '\r') { + if (i + 1 < len && text.charAt(i + 1) == '\n') { + i++; + } + normalized.append('\n'); + } else { + normalized.append(c); + } + } + + return normalized.toString(); + } + + public static int getOffset(String content, Position position) { + if (content == null || position == null) { + return 0; + } + + String[] lines = content.split("\n", -1); + int offset = 0; + int targetLine = position.getLine(); + int targetChar = position.getCharacter(); + + if (targetLine < 0) { + return 0; + } + if (targetLine >= lines.length) { + return content.length(); + } + + for (int i = 0; i < targetLine; i++) { + offset += lines[i].length() + 1; + } + + String currentLine = lines[targetLine]; + int charPosition = Math.min(Math.max(targetChar, 0), currentLine.length()); + offset += charPosition; + + return Math.min(offset, content.length()); + } + + public static Position getPosition(String content, int offset) { + if (content == null || offset <= 0) { + return new Position(0, 0); + } + + int clampedOffset = Math.min(offset, content.length()); + + String textUpToOffset = content.substring(0, clampedOffset); + String[] lines = textUpToOffset.split("\n", -1); + + int line = lines.length - 1; + int character = lines[line].length(); + + return new Position(line, character); + } +} diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/ResultEval.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/ResultEval.java new file mode 100644 index 0000000..3000959 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/ResultEval.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed 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 + * + * https://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.netbeans.modules.nbcode.java.notebook; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URLConnection; + +/** + * + * @author atalati + */ +public class ResultEval { + + private final String data; + private final String mimeType; + + public ResultEval(String rawData, String mimeType) { + this.data = rawData; + this.mimeType = mimeType; + } + + public String getData() { + return data; + } + + public String getMimeType() { + return mimeType; + } + + public static ResultEval text(String data) { + return new ResultEval(data, "text/plain"); + } + + private String detectMime(byte[] data) { + try (ByteArrayInputStream in = new ByteArrayInputStream(data)) { + String detected = URLConnection.guessContentTypeFromStream(in); + return detected != null ? detected : "text/plain"; + } catch (IOException ex) { + return "text/plain"; + } + } + + private String detectMime(ByteArrayOutputStream data) { + return detectMime(data.toByteArray()); + } +} diff --git a/patches/java-notebooks.diff b/patches/java-notebooks.diff new file mode 100644 index 0000000..2db3f5a --- /dev/null +++ b/patches/java-notebooks.diff @@ -0,0 +1,755 @@ +--- /dev/null ++++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/notebook/NotebookCellContentRequestParams.java +@@ -0,0 +1,121 @@ ++/* ++ * 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.netbeans.modules.java.lsp.server.notebook; ++import java.util.Objects; ++import org.eclipse.lsp4j.jsonrpc.validation.NonNull; ++import org.eclipse.lsp4j.util.Preconditions; ++import org.eclipse.xtext.xbase.lib.Pure; ++import org.eclipse.xtext.xbase.lib.util.ToStringBuilder; ++/** ++ * ++ * @author atalati ++ */ ++@SuppressWarnings("all") ++public class NotebookCellContentRequestParams { ++ /** ++ * URI of the notebook. ++ */ ++ @NonNull ++ private String notebookUri; ++ ++ /** ++ * URI of the cell. ++ */ ++ @NonNull ++ private String cellUri; ++ ++ public NotebookCellContentRequestParams() { ++ this("", ""); ++ } ++ ++ public NotebookCellContentRequestParams(@NonNull final String notebookUri, @NonNull final String cellUri) { ++ this.notebookUri = Preconditions.checkNotNull(notebookUri, "notebookUri"); ++ this.cellUri = Preconditions.checkNotNull(cellUri, "cellUri"); ++ } ++ ++ /** ++ * URI of the notebook. ++ */ ++ @Pure ++ @NonNull ++ public String getNotebookUri() { ++ return notebookUri; ++ } ++ ++ /** ++ * URI of the notebook. ++ */ ++ public void setNotebookUri(@NonNull final String notebookUri) { ++ this.notebookUri = Preconditions.checkNotNull(notebookUri, "notebookUri"); ++ } ++ ++ /** ++ * URI of the cell. ++ */ ++ @Pure ++ @NonNull ++ public String getCellUri() { ++ return cellUri; ++ } ++ ++ /** ++ * URI of the cell. ++ */ ++ public void setCellUri(@NonNull final String cellUri) { ++ this.cellUri = Preconditions.checkNotNull(cellUri, "cellUri"); ++ } ++ ++ @Override ++ @Pure ++ public String toString() { ++ ToStringBuilder b = new ToStringBuilder(this); ++ b.add("notebookUri", notebookUri); ++ b.add("cellUri", cellUri); ++ return b.toString(); ++ } ++ ++ @Override ++ public int hashCode() { ++ int hash = 5; ++ hash = 43 * hash + Objects.hashCode(this.notebookUri); ++ hash = 43 * hash + Objects.hashCode(this.cellUri); ++ return hash; ++ } ++ ++ @Override ++ public boolean equals(Object obj) { ++ if (this == obj) { ++ return true; ++ } ++ if (obj == null) { ++ return false; ++ } ++ if (getClass() != obj.getClass()) { ++ return false; ++ } ++ final NotebookCellContentRequestParams other = (NotebookCellContentRequestParams) obj; ++ if (!Objects.equals(this.notebookUri, other.notebookUri)) { ++ return false; ++ } ++ if (!Objects.equals(this.cellUri, other.cellUri)) { ++ return false; ++ } ++ return true; ++ } ++} +--- /dev/null ++++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/notebook/NotebookDocumentServiceHandler.java +@@ -0,0 +1,59 @@ ++/* ++ * 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.netbeans.modules.java.lsp.server.notebook; ++ ++import org.eclipse.lsp4j.DidChangeTextDocumentParams; ++import org.eclipse.lsp4j.DidCloseTextDocumentParams; ++import org.eclipse.lsp4j.DidOpenTextDocumentParams; ++import org.eclipse.lsp4j.DidSaveTextDocumentParams; ++import org.eclipse.lsp4j.services.LanguageClientAware; ++import org.eclipse.lsp4j.services.NotebookDocumentService; ++import org.eclipse.lsp4j.services.TextDocumentService; ++ ++/** ++ * ++ * @author atalati ++ */ ++public interface NotebookDocumentServiceHandler extends NotebookDocumentService, TextDocumentService, LanguageClientAware { ++ ++ @Override ++ public default void didOpen(DidOpenTextDocumentParams params) { ++ // placholder method, not required to implement in the notebook service handler ++ } ++ ++ @Override ++ public default void didChange(DidChangeTextDocumentParams params) { ++ // placholder method, not required to implement in the notebook service handler ++ } ++ ++ @Override ++ public default void didClose(DidCloseTextDocumentParams params) { ++ // placholder method, not required to implement in the notebook service handler ++ } ++ ++ @Override ++ public default void didSave(DidSaveTextDocumentParams params) { ++ // placholder method, not required to implement in the notebook service handler ++ } ++ ++ public default String getSchemeSupportedInTextDocUri() { ++ return "vscode-notebook-cell"; ++ } ++ ++} +--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeLanguageClient.java ++++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeLanguageClient.java +@@ -32,6 +32,7 @@ + import org.netbeans.modules.java.lsp.server.input.ShowMutliStepInputParams; + import org.netbeans.modules.java.lsp.server.input.ShowInputBoxParams; + import org.netbeans.modules.java.lsp.server.explorer.api.NodeChangedParams; ++import org.netbeans.modules.java.lsp.server.notebook.NotebookCellContentRequestParams; + + /** + * An extension to the standard LanguageClient that adds several messages missing +@@ -166,4 +167,6 @@ + @JsonRequest("output/reset") + public CompletableFuture resetOutput(String outputName); + ++ @JsonRequest("notebookDocument/cellContentRequest") ++ public CompletableFuture notebookDocumentCellContentRequest(@NonNull NotebookCellContentRequestParams params); + } +--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientWrapper.java ++++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientWrapper.java +@@ -44,6 +44,7 @@ + import org.netbeans.modules.java.lsp.server.input.ShowMutliStepInputParams; + import org.netbeans.modules.java.lsp.server.input.ShowInputBoxParams; + import org.netbeans.modules.java.lsp.server.explorer.api.NodeChangedParams; ++import org.netbeans.modules.java.lsp.server.notebook.NotebookCellContentRequestParams; + + /** + * Convenience wrapper that binds language client's remote proxy together with +@@ -244,4 +245,8 @@ + return remote.resetOutput(outputName); + } + ++ @Override ++ public CompletableFuture notebookDocumentCellContentRequest(NotebookCellContentRequestParams params) { ++ return remote.notebookDocumentCellContentRequest(params); + } ++} +--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientCapabilities.java ++++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientCapabilities.java +@@ -95,6 +95,11 @@ + */ + private Boolean wantsTelemetryEnabled = Boolean.FALSE; + ++ /** ++ * Whether Notebook support needs to be enabled. ++ */ ++ private Boolean wantsNotebookSupport = Boolean.FALSE; ++ + public ClientCapabilities getClientCapabilities() { + return clientCaps; + } +@@ -188,6 +193,10 @@ + return wantsTelemetryEnabled == Boolean.TRUE; + } + ++ public boolean wantsNotebookSupport() { ++ return wantsNotebookSupport == Boolean.TRUE; ++ } ++ + private NbCodeClientCapabilities withCapabilities(ClientCapabilities caps) { + if (caps == null) { + caps = new ClientCapabilities(); +--- a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/TestCodeLanguageClient.java ++++ b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/TestCodeLanguageClient.java +@@ -43,6 +43,7 @@ + import org.netbeans.modules.java.lsp.server.input.ShowInputBoxParams; + import org.netbeans.modules.java.lsp.server.input.ShowMutliStepInputParams; + import org.netbeans.modules.java.lsp.server.input.ShowQuickPickParams; ++import org.netbeans.modules.java.lsp.server.notebook.NotebookCellContentRequestParams; + import org.netbeans.modules.java.lsp.server.protocol.OutputMessage; + import org.netbeans.modules.java.lsp.server.protocol.SaveDocumentRequestParams; + import org.netbeans.modules.java.lsp.server.protocol.ShowStatusMessageParams; +@@ -182,4 +183,9 @@ + public CompletableFuture resetOutput(String outputName) { + return CompletableFuture.completedFuture(null); + } ++ ++ @Override ++ public CompletableFuture notebookDocumentCellContentRequest(NotebookCellContentRequestParams params) { ++ return CompletableFuture.completedFuture(null); + } ++} +--- a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/explorer/ProjectViewTest.java ++++ b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/explorer/ProjectViewTest.java +@@ -77,6 +77,7 @@ + import org.netbeans.modules.java.lsp.server.input.ShowInputBoxParams; + import org.netbeans.modules.java.lsp.server.input.ShowMutliStepInputParams; + import org.netbeans.modules.java.lsp.server.input.ShowQuickPickParams; ++import org.netbeans.modules.java.lsp.server.notebook.NotebookCellContentRequestParams; + import org.netbeans.modules.java.lsp.server.protocol.OutputMessage; + import org.netbeans.modules.java.lsp.server.protocol.SaveDocumentRequestParams; + import org.netbeans.modules.java.lsp.server.protocol.SetTextEditorDecorationParams; +@@ -306,7 +307,12 @@ + public CompletableFuture resetOutput(String outputName) { + return CompletableFuture.completedFuture(null); + } ++ ++ @Override ++ public CompletableFuture notebookDocumentCellContentRequest(NotebookCellContentRequestParams params) { ++ return CompletableFuture.completedFuture(null); + } ++ } + + private static Launcher createLauncher(NbCodeLanguageClient client, InputStream in, OutputStream out, + Function processor) { +--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java ++++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java +@@ -134,6 +134,7 @@ import org.netbeans.modules.java.lsp.server.input.QuickPickItem; + import org.netbeans.modules.java.lsp.server.input.ShowQuickPickParams; + import org.netbeans.modules.java.lsp.server.input.ShowMutliStepInputParams; + import org.netbeans.modules.java.lsp.server.input.ShowInputBoxParams; ++import org.netbeans.modules.java.lsp.server.notebook.NotebookCellContentRequestParams; + import org.netbeans.modules.java.lsp.server.progress.OperationContext; + import org.netbeans.modules.parsing.spi.indexing.Context; + import org.netbeans.modules.parsing.spi.indexing.CustomIndexer; +@@ -1383,6 +1384,12 @@ public final class Server { + logWarning("Reset output: " + outputName); //NOI18N + return CompletableFuture.completedFuture(null); + } ++ ++ @Override ++ public CompletableFuture notebookDocumentCellContentRequest(NotebookCellContentRequestParams params) { ++ logWarning("notebook document cell content request: " + params.getNotebookUri() + " cell uri: " + params.getCellUri()); //NOI18N ++ return CompletableFuture.completedFuture(null); ++ } + }; + + + +--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java ++++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java +@@ -117,9 +117,13 @@ import org.eclipse.lsp4j.ConfigurationParams; + import org.eclipse.lsp4j.DefinitionParams; + import org.eclipse.lsp4j.Diagnostic; + import org.eclipse.lsp4j.DiagnosticSeverity; ++import org.eclipse.lsp4j.DidChangeNotebookDocumentParams; + import org.eclipse.lsp4j.DidChangeTextDocumentParams; ++import org.eclipse.lsp4j.DidCloseNotebookDocumentParams; + import org.eclipse.lsp4j.DidCloseTextDocumentParams; ++import org.eclipse.lsp4j.DidOpenNotebookDocumentParams; + import org.eclipse.lsp4j.DidOpenTextDocumentParams; ++import org.eclipse.lsp4j.DidSaveNotebookDocumentParams; + import org.eclipse.lsp4j.DidSaveTextDocumentParams; + import org.eclipse.lsp4j.DocumentFormattingParams; + import org.eclipse.lsp4j.DocumentHighlight; +@@ -141,6 +145,9 @@ import org.eclipse.lsp4j.LocationLink; + import org.eclipse.lsp4j.MarkupContent; + import org.eclipse.lsp4j.MessageParams; + import org.eclipse.lsp4j.MessageType; ++import org.eclipse.lsp4j.NotebookDocumentSyncRegistrationOptions; ++import org.eclipse.lsp4j.NotebookSelector; ++import org.eclipse.lsp4j.NotebookSelectorCell; + import org.eclipse.lsp4j.ParameterInformation; + import org.eclipse.lsp4j.Position; + import org.eclipse.lsp4j.PrepareRenameDefaultBehavior; +@@ -176,6 +183,7 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either3; + import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode; + import org.eclipse.lsp4j.services.LanguageClient; + import org.eclipse.lsp4j.services.LanguageClientAware; ++import org.eclipse.lsp4j.services.NotebookDocumentService; + import org.eclipse.lsp4j.services.TextDocumentService; + import org.netbeans.api.annotations.common.CheckForNull; + import org.netbeans.api.editor.mimelookup.MimeLookup; +@@ -247,6 +255,7 @@ import org.netbeans.modules.refactoring.spi.Transaction; + import org.netbeans.api.lsp.StructureElement; + import org.netbeans.modules.editor.indent.api.Reformat; + import org.netbeans.modules.java.lsp.server.URITranslator; ++import org.netbeans.modules.java.lsp.server.notebook.NotebookDocumentServiceHandler; + import org.netbeans.modules.java.lsp.server.ui.AbstractJavaPlatformProviderOverride; + import org.netbeans.modules.parsing.impl.SourceAccessor; + import org.netbeans.modules.sampler.Sampler; +@@ -287,7 +296,7 @@ import org.openide.util.lookup.ServiceProvider; + * + * @author lahvac + */ +-public class TextDocumentServiceImpl implements TextDocumentService, LanguageClientAware { ++public class TextDocumentServiceImpl implements TextDocumentService, LanguageClientAware, NotebookDocumentService { + private static final Logger LOG = Logger.getLogger(TextDocumentServiceImpl.class.getName()); + + private static final String COMMAND_RUN_SINGLE = "nbls.run.single"; // NOI18N +@@ -305,13 +305,14 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + private static final String NETBEANS_COMPLETION_WARNING_TIME = "completion.warning.time";// NOI18N + private static final String NETBEANS_JAVA_ON_SAVE_ORGANIZE_IMPORTS = "java.onSave.organizeImports";// NOI18N + private static final String NETBEANS_CODE_COMPLETION_COMMIT_CHARS = "java.completion.commit.chars";// NOI18N ++ private static final String NOTEBOOK_TEXT_DOC_URI_IDENTIFIER = "vscode-notebook-cell"; + + private static final String URL = "url";// NOI18N + private static final String INDEX = "index";// NOI18N + + private static final RequestProcessor BACKGROUND_TASKS = new RequestProcessor(TextDocumentServiceImpl.class.getName(), 1, false, false); + private static final RequestProcessor WORKER = new RequestProcessor(TextDocumentServiceImpl.class.getName(), 1, false, false); +- ++ private NotebookDocumentServiceHandler notebookDocumentServiceDelegator = null; + /** + * File URIs touched / queried by the client. + */ +@@ -334,6 +335,48 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + runDiagnosticTasks(doc, true); + } + } ++ ++ public boolean isNotebookSupportEnabled(){ ++ return notebookDocumentServiceDelegator != null; ++ } ++ ++ public boolean isRequiredToDelegateToNotebooks(String textDocumentUri){ ++ return textDocumentUri.startsWith(NOTEBOOK_TEXT_DOC_URI_IDENTIFIER); ++ } ++ ++ @Override ++ public void didOpen(DidOpenNotebookDocumentParams params) { ++ if (notebookDocumentServiceDelegator == null) { ++ notebookDocumentServiceDelegator = Lookup.getDefault().lookup(NotebookDocumentServiceHandler.class); ++ if (notebookDocumentServiceDelegator != null) { ++ notebookDocumentServiceDelegator.connect(client); ++ } else { ++ return; ++ } ++ } ++ notebookDocumentServiceDelegator.didOpen(params); ++ } ++ ++ @Override ++ public void didChange(DidChangeNotebookDocumentParams params) { ++ if(isNotebookSupportEnabled()){ ++ notebookDocumentServiceDelegator.didChange(params); ++ } ++ } ++ ++ @Override ++ public void didSave(DidSaveNotebookDocumentParams params) { ++ if(isNotebookSupportEnabled()){ ++ notebookDocumentServiceDelegator.didSave(params); ++ } ++ } ++ ++ @Override ++ public void didClose(DidCloseNotebookDocumentParams params) { ++ if(isNotebookSupportEnabled()){ ++ notebookDocumentServiceDelegator.didClose(params); ++ } ++ } + + @ServiceProvider(service=IndexingAware.class, position=0) + public static final class RefreshDocument implements IndexingAware { +@@ -378,6 +421,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + "INFO_LongCodeCompletion=Analyze completions taking longer than {0}. A sampler snapshot has been saved to: {1}" + }) + public CompletableFuture, CompletionList>> completion(CompletionParams params) { ++ if (isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ return notebookDocumentServiceDelegator.completion(params); ++ } ++ return CompletableFuture.completedFuture(Either.forRight(new CompletionList())); ++ } + AtomicBoolean done = new AtomicBoolean(); + AtomicReference samplerRef = new AtomicReference<>(); + AtomicLong samplingStart = new AtomicLong(); +@@ -626,8 +675,19 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + cap.setLegend(legend); + severCapabilities.setSemanticTokensProvider(cap); + } ++ checkNotebookSupport(severCapabilities); + } +- ++ private void checkNotebookSupport(ServerCapabilities serverCapabilities) { ++ if (client != null && client.getNbCodeCapabilities().wantsNotebookSupport()) { ++ NotebookDocumentSyncRegistrationOptions opts = new NotebookDocumentSyncRegistrationOptions(); ++ NotebookSelector ns = new NotebookSelector(); ++ ns.setNotebook("*"); ++ ns.setCells(List.of(new NotebookSelectorCell("java"), new NotebookSelectorCell("markdown"))); ++ opts.setNotebookSelector(List.of(ns)); ++ serverCapabilities.setNotebookDocumentSync(opts); ++ } ++ } ++ + @Override + public CompletableFuture resolveCompletionItem(CompletionItem ci) { + JsonObject rawData = (JsonObject) ci.getData(); +@@ -702,6 +762,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + + @Override + public CompletableFuture hover(HoverParams params) { ++ if(isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())){ ++ if(isNotebookSupportEnabled()){ ++ return notebookDocumentServiceDelegator.hover(params); ++ } ++ return CompletableFuture.completedFuture(null); ++ } + // shortcut: if the projects are not yet initialized, return empty: + if (server.openedProjects().getNow(null) == null) { + return CompletableFuture.completedFuture(null); +@@ -726,6 +792,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + + @Override + public CompletableFuture signatureHelp(SignatureHelpParams params) { ++ if(isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())){ ++ if(isNotebookSupportEnabled()){ ++ return notebookDocumentServiceDelegator.signatureHelp(params); ++ } ++ return CompletableFuture.completedFuture(null); ++ } + // shortcut: if the projects are not yet initialized, return empty: + if (server.openedProjects().getNow(null) == null) { + return CompletableFuture.completedFuture(null); +@@ -777,6 +849,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + + @Override + public CompletableFuture, List>> definition(DefinitionParams params) { ++ if (isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ return notebookDocumentServiceDelegator.definition(params); ++ } ++ return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList())); ++ } + try { + String uri = params.getTextDocument().getUri(); + Document rawDoc = server.getOpenedDocuments().getDocument(uri); +@@ -801,6 +879,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + + @Override + public CompletableFuture, List>> typeDefinition(TypeDefinitionParams params) { ++ if (isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ return notebookDocumentServiceDelegator.typeDefinition(params); ++ } ++ return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList())); ++ } + try { + String uri = params.getTextDocument().getUri(); + Document rawDoc = server.getOpenedDocuments().getDocument(uri); +@@ -825,11 +909,23 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + + @Override + public CompletableFuture, List>> implementation(ImplementationParams params) { ++ if (isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ return notebookDocumentServiceDelegator.implementation(params); ++ } ++ return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList())); ++ } + return usages(params.getTextDocument().getUri(), params.getPosition(), true, false).thenApply(locations -> Either.forLeft(locations)); + } + + @Override + public CompletableFuture> references(ReferenceParams params) { ++ if (isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ return notebookDocumentServiceDelegator.references(params); ++ } ++ return CompletableFuture.completedFuture(Collections.emptyList()); ++ } + return usages(params.getTextDocument().getUri(), params.getPosition(), false, params.getContext().isIncludeDeclaration()); + } + +@@ -985,6 +1081,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + + @Override + public CompletableFuture> documentHighlight(DocumentHighlightParams params) { ++ if (isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ return notebookDocumentServiceDelegator.documentHighlight(params); ++ } ++ return CompletableFuture.completedFuture(Collections.emptyList()); ++ } + class MOHighligther extends MarkOccurrencesHighlighterBase { + @Override + protected void process(CompilationInfo arg0, Document arg1, SchedulerEvent arg2) { +@@ -1028,6 +1130,13 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + + @Override + public CompletableFuture>> documentSymbol(DocumentSymbolParams params) { ++ if (isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ return notebookDocumentServiceDelegator.documentSymbol(params); ++ } ++ return CompletableFuture.completedFuture(Collections.emptyList()); ++ } ++ + final CompletableFuture>> resultFuture = new CompletableFuture<>(); + + BACKGROUND_TASKS.post(() -> { +@@ -1084,6 +1193,13 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + + @Override + public CompletableFuture>> codeAction(CodeActionParams params) { ++ if (isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ return notebookDocumentServiceDelegator.codeAction(params); ++ } ++ return CompletableFuture.completedFuture(Collections.emptyList()); ++ } ++ + lastCodeActions = new ArrayList<>(); + AtomicInteger index = new AtomicInteger(0); + +@@ -1268,6 +1384,12 @@ + if (!client.getNbCodeCapabilities().wantsJavaSupport()) { + return CompletableFuture.completedFuture(Collections.emptyList()); + } ++ if (isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ return notebookDocumentServiceDelegator.codeLens(params); ++ } ++ return CompletableFuture.completedFuture(Collections.emptyList()); ++ } + // shortcut: if the projects are not yet initialized, return empty: + if (server.openedProjects().getNow(null) == null) { + return CompletableFuture.completedFuture(Collections.emptyList()); +@@ -1406,6 +1542,12 @@ + + @Override + public CompletableFuture> formatting(DocumentFormattingParams params) { ++ if (isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ return notebookDocumentServiceDelegator.formatting(params); ++ } ++ return CompletableFuture.completedFuture(Collections.emptyList()); ++ } + String uri = params.getTextDocument().getUri(); + Document doc = server.getOpenedDocuments().getDocument(uri); + return format(doc, 0, doc.getLength()); +@@ -1435,6 +1563,13 @@ + + @Override + public CompletableFuture> rangeFormatting(DocumentRangeFormattingParams params) { ++ if (isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ return notebookDocumentServiceDelegator.rangeFormatting(params); ++ } ++ return CompletableFuture.completedFuture(Collections.emptyList()); ++ } ++ + String uri = params.getTextDocument().getUri(); + Document rawDoc = server.getOpenedDocuments().getDocument(uri); + if (rawDoc instanceof StyledDocument) { +@@ -1611,6 +1611,13 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + + @Override + public CompletableFuture> prepareRename(PrepareRenameParams params) { ++ if (isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ return notebookDocumentServiceDelegator.prepareRename(params); ++ } ++ return CompletableFuture.completedFuture(null); ++ } ++ + // shortcut: if the projects are not yet initialized, return empty: + if (server.openedProjects().getNow(null) == null) { + return CompletableFuture.completedFuture(null); +@@ -1670,6 +1677,13 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + + @Override + public CompletableFuture rename(RenameParams params) { ++ if (isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ return notebookDocumentServiceDelegator.rename(params); ++ } ++ return CompletableFuture.completedFuture(null); ++ } ++ + // shortcut: if the projects are not yet initialized, return empty: + if (server.openedProjects().getNow(null) == null) { + return CompletableFuture.completedFuture(new WorkspaceEdit()); +@@ -1799,6 +1813,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + + @Override + public CompletableFuture> foldingRange(FoldingRangeRequestParams params) { ++ if (isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ return notebookDocumentServiceDelegator.foldingRange(params); ++ } ++ return CompletableFuture.completedFuture(Collections.emptyList()); ++ } + JavaSource source = getJavaSource(params.getTextDocument().getUri()); + if (source == null) { + return CompletableFuture.completedFuture(Collections.emptyList()); +@@ -1964,6 +1971,12 @@ + + @Override + public void didOpen(DidOpenTextDocumentParams params) { ++ if (isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ notebookDocumentServiceDelegator.didOpen(params); ++ } ++ return; ++ } + LOG.log(Level.FINER, "didOpen: {0}", params); + try { + FileObject file = fromURI(params.getTextDocument().getUri(), true); +@@ -2014,6 +2049,12 @@ + + @Override + public void didChange(DidChangeTextDocumentParams params) { ++ if (isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ notebookDocumentServiceDelegator.didChange(params); ++ } ++ return; ++ } + LOG.log(Level.FINER, "didChange: {0}", params); + String uri = params.getTextDocument().getUri(); + Document rawDoc = server.getOpenedDocuments().getDocument(uri); +@@ -2063,6 +2082,12 @@ + + @Override + public void didClose(DidCloseTextDocumentParams params) { ++ if (isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ notebookDocumentServiceDelegator.didClose(params); ++ } ++ return; ++ } + LOG.log(Level.FINER, "didClose: {0}", params); + try { + String uri = params.getTextDocument().getUri(); +@@ -2087,6 +2112,12 @@ + + @Override + public CompletableFuture> willSaveWaitUntil(WillSaveTextDocumentParams params) { ++ if (isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ return notebookDocumentServiceDelegator.willSaveWaitUntil(params); ++ } ++ return CompletableFuture.completedFuture(Collections.emptyList()); ++ } + LOG.log(Level.FINER, "willSaveWaitUntil: {0}", params); + String uri = params.getTextDocument().getUri(); + JavaSource js = getJavaSource(uri); +@@ -2114,6 +2145,13 @@ + + @Override + public void didSave(DidSaveTextDocumentParams savedParams) { ++ if (isRequiredToDelegateToNotebooks(savedParams.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ notebookDocumentServiceDelegator.didSave(savedParams); ++ } ++ return; ++ } ++ + LOG.log(Level.FINE, "didSave: {0}", savedParams.getTextDocument().getUri()); + FileObject file = fromURI(savedParams.getTextDocument().getUri()); + if (file == null) { +@@ -2707,6 +2745,12 @@ + + @Override + public CompletableFuture semanticTokensFull(SemanticTokensParams params) { ++ if (isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ return notebookDocumentServiceDelegator.semanticTokensFull(params); ++ } ++ return CompletableFuture.completedFuture(null); ++ } + JavaSource js = getJavaSource(params.getTextDocument().getUri()); + List result = new ArrayList<>(); + if (js != null) { +@@ -2783,6 +2827,12 @@ + + @Override + public CompletableFuture> prepareCallHierarchy(CallHierarchyPrepareParams params) { ++ if (isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ return notebookDocumentServiceDelegator.prepareCallHierarchy(params); ++ } ++ return CompletableFuture.completedFuture(null); ++ } + FileObject file = fromURI(params.getTextDocument().getUri()); + if (file == null) { + return CompletableFuture.completedFuture(null); + diff --git a/patches/upgrade-lsp4j.diff b/patches/upgrade-lsp4j.diff new file mode 100644 index 0000000..19a331e --- /dev/null +++ b/patches/upgrade-lsp4j.diff @@ -0,0 +1,448 @@ +diff --git a/ide/lsp.client/external/binaries-list b/ide/lsp.client/external/binaries-list +index dd1dd5db2f..9c186d7712 100644 +--- a/ide/lsp.client/external/binaries-list ++++ b/ide/lsp.client/external/binaries-list +@@ -15,11 +15,11 @@ + # specific language governing permissions and limitations + # under the License. + +-E95BD5C2A465A40A66C9DC098BF95C237B80C4EB org.eclipse.lsp4j:org.eclipse.lsp4j:0.13.0 +-82848D7796D399F3E5109B7331A143D7C649B520 org.eclipse.lsp4j:org.eclipse.lsp4j.generator:0.13.0 +-0B50BD2A6AF496803F72176AD066532DAE62574E org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.13.0 +-A491B7AC91B35EC177DE629518D5A68A9C7878FC org.eclipse.lsp4j:org.eclipse.lsp4j.debug:0.13.0 +-68AB471B1FED84FABA25D6CE9005B9369B503A74 org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc.debug:0.13.0 ++10F74DD2E4BB42AFA1DC7966BC4A16F87C8D80A9 org.eclipse.lsp4j:org.eclipse.lsp4j:0.16.0 ++9E162228BF8197A5CCC725FBEA8CEEBFE3065271 org.eclipse.lsp4j:org.eclipse.lsp4j.generator:0.16.0 ++F94BEAD3015B6B915C5AEDD52398A67546BE61F3 org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.16.0 ++D57DF9450B1B51E58772F820B234C15CE65415AF org.eclipse.lsp4j:org.eclipse.lsp4j.debug:0.16.0 ++5CE0D2E3135370CF96A0B299ED41D4DC6111380E org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc.debug:0.16.0 + A57306D5D523A4750FA09B2708062EA4972AFEA2 org.eclipse.xtend:org.eclipse.xtend.lib:2.24.0 + D8F5566BA67748ED9E91856E077EE99F00E86653 org.eclipse.xtend:org.eclipse.xtend.lib.macro:2.24.0 + 53FBD66084B08850258E61C838CC1FB94335E718 org.eclipse.xtext:org.eclipse.xtext.xbase.lib:2.24.0 +diff --git a/ide/lsp.client/external/lsp4j-0.13.0-license.txt b/ide/lsp.client/external/lsp4j-0.13.0-license.txt +deleted file mode 100644 +index 2729106479..0000000000 +--- a/ide/lsp.client/external/lsp4j-0.13.0-license.txt ++++ /dev/null +@@ -1,94 +0,0 @@ +-Name: Eclipse Language Server Protocol Library +-Origin: Eclipse +-Version: 0.13.0 +-License: EPL-v20 +-URL: http://www.eclipse.org/ +-Description: Eclipse Language Server Protocol Library +-Files: org.eclipse.lsp4j-0.13.0.jar org.eclipse.lsp4j.generator-0.13.0.jar org.eclipse.lsp4j.jsonrpc-0.13.0.jar org.eclipse.lsp4j.debug-0.13.0.jar org.eclipse.lsp4j.jsonrpc.debug-0.13.0.jar +- +-Eclipse Public License - v 2.0 +-THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE (“AGREEMENT”). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. +- +-1. Definitions +-“Contribution” means: +- +-a) in the case of the initial Contributor, the initial content Distributed under this Agreement, and +-b) in the case of each subsequent Contributor: +-i) changes to the Program, and +-ii) additions to the Program; where such changes and/or additions to the Program originate from and are Distributed by that particular Contributor. A Contribution “originates” from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include changes or additions to the Program that are not Modified Works. +-“Contributor” means any person or entity that Distributes the Program. +- +-“Licensed Patents” mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. +- +-“Program” means the Contributions Distributed in accordance with this Agreement. +- +-“Recipient” means anyone who receives the Program under this Agreement or any Secondary License (as applicable), including Contributors. +- +-“Derivative Works” shall mean any work, whether in Source Code or other form, that is based on (or derived from) the Program and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. +- +-“Modified Works” shall mean any work in Source Code or other form that results from an addition to, deletion from, or modification of the contents of the Program, including, for purposes of clarity any new file in Source Code form that contains any contents of the Program. Modified Works shall not include works that contain only declarations, interfaces, types, classes, structures, or files of the Program solely in each case in order to link to, bind by name, or subclass the Program or Modified Works thereof. +- +-“Distribute” means the acts of a) distributing or b) making available in any manner that enables the transfer of a copy. +- +-“Source Code” means the form of a Program preferred for making modifications, including but not limited to software source code, documentation source, and configuration files. +- +-“Secondary License” means either the GNU General Public License, Version 2.0, or any later versions of that license, including any exceptions or additional permissions as identified by the initial Contributor. +- +-2. Grant of Rights +-a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, Distribute and sublicense the Contribution of such Contributor, if any, and such Derivative Works. +- +-b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in Source Code or other form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. +- +-c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to Distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. +- +-d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. +- +-e) Notwithstanding the terms of any Secondary License, no Contributor makes additional grants to any Recipient (other than those set forth in this Agreement) as a result of such Recipient's receipt of the Program under the terms of a Secondary License (if permitted under the terms of Section 3). +- +-3. Requirements +-3.1 If a Contributor Distributes the Program in any form, then: +- +-a) the Program must also be made available as Source Code, in accordance with section 3.2, and the Contributor must accompany the Program with a statement that the Source Code for the Program is available under this Agreement, and informs Recipients how to obtain it in a reasonable manner on or through a medium customarily used for software exchange; and +- +-b) the Contributor may Distribute the Program under a license different than this Agreement, provided that such license: +- +-i) effectively disclaims on behalf of all other Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; +-ii) effectively excludes on behalf of all other Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; +-iii) does not attempt to limit or alter the recipients' rights in the Source Code under section 3.2; and +-iv) requires any subsequent distribution of the Program by any party to be under a license that satisfies the requirements of this section 3. +-3.2 When the Program is Distributed as Source Code: +- +-a) it must be made available under this Agreement, or if the Program (i) is combined with other material in a separate file or files made available under a Secondary License, and (ii) the initial Contributor attached to the Source Code the notice described in Exhibit A of this Agreement, then the Program may be made available under the terms of such Secondary Licenses, and +-b) a copy of this Agreement must be included with each copy of the Program. +-3.3 Contributors may not remove or alter any copyright, patent, trademark, attribution notices, disclaimers of warranty, or limitations of liability (“notices”) contained within the Program from any copy of the Program which they Distribute, provided that Contributors may add their own appropriate notices. +- +-4. Commercial Distribution +-Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor (“Commercial Contributor”) hereby agrees to defend and indemnify every other Contributor (“Indemnified Contributor”) against any losses, damages and costs (collectively “Losses”) arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. +- +-For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. +- +-5. No Warranty +-EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. +- +-6. Disclaimer of Liability +-EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +- +-7. General +-If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. +- +-If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. +- +-All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. +- +-Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be Distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to Distribute the Program (including its Contributions) under the new version. +- +-Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. Nothing in this Agreement is intended to be enforceable by any entity that is not a Contributor or Recipient. No third-party beneficiary rights are created under this Agreement. +- +-Exhibit A - Form of Secondary Licenses Notice +-“This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), version(s), and exceptions or additional permissions here}.” +- +-Simply including a copy of this Agreement, including this Exhibit A is not sufficient to license the Source Code under Secondary Licenses. +- +-If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. +- +-You may add additional accurate notices of copyright ownership. +diff --git a/ide/lsp.client/nbproject/project.properties b/ide/lsp.client/nbproject/project.properties +index f73bff5cc6..0988a1f404 100644 +--- a/ide/lsp.client/nbproject/project.properties ++++ b/ide/lsp.client/nbproject/project.properties +@@ -18,11 +18,11 @@ + javac.release=17 + javac.compilerargs=-Xlint -Xlint:-serial + javadoc.arch=${basedir}/arch.xml +-release.external/org.eclipse.lsp4j-0.13.0.jar=modules/ext/org.eclipse.lsp4j-0.13.0.jar +-release.external/org.eclipse.lsp4j.generator-0.13.0.jar=modules/ext/org.eclipse.lsp4j.generator-0.13.0.jar +-release.external/org.eclipse.lsp4j.jsonrpc-0.13.0.jar=modules/ext/org.eclipse.lsp4j.jsonrpc-0.13.0.jar +-release.external/org.eclipse.lsp4j.debug-0.13.0.jar=modules/ext/org.eclipse.lsp4j.debug-0.13.0.jar +-release.external/org.eclipse.lsp4j.jsonrpc.debug-0.13.0.jar=modules/ext/org.eclipse.lsp4j.jsonrpc.debug-0.13.0.jar ++release.external/org.eclipse.lsp4j-0.16.0.jar=modules/ext/org.eclipse.lsp4j-0.16.0.jar ++release.external/org.eclipse.lsp4j.generator-0.16.0.jar=modules/ext/org.eclipse.lsp4j.generator-0.16.0.jar ++release.external/org.eclipse.lsp4j.jsonrpc-0.16.0.jar=modules/ext/org.eclipse.lsp4j.jsonrpc-0.16.0.jar ++release.external/org.eclipse.lsp4j.debug-0.16.0.jar=modules/ext/org.eclipse.lsp4j.debug-0.16.0.jar ++release.external/org.eclipse.lsp4j.jsonrpc.debug-0.16.0.jar=modules/ext/org.eclipse.lsp4j.jsonrpc.debug-0.16.0.jar + release.external/org.eclipse.xtend.lib-2.24.0.jar=modules/ext/org.eclipse.xtend.lib-2.24.0.jar + release.external/org.eclipse.xtend.lib.macro-2.24.0.jar=modules/ext/org.eclipse.xtend.lib.macro-2.24.0.jar + release.external/org.eclipse.xtext.xbase.lib-2.24.0.jar=modules/ext/org.eclipse.xtext.xbase.lib-2.24.0.jar +diff --git a/ide/lsp.client/nbproject/project.xml b/ide/lsp.client/nbproject/project.xml +index 7f8f52def8..1cafcec5e2 100644 +--- a/ide/lsp.client/nbproject/project.xml ++++ b/ide/lsp.client/nbproject/project.xml +@@ -479,16 +479,16 @@ + org.netbeans.modules.lsp.client.spi + + +- ext/org.eclipse.lsp4j-0.13.0.jar +- external/org.eclipse.lsp4j-0.13.0.jar ++ ext/org.eclipse.lsp4j-0.16.0.jar ++ external/org.eclipse.lsp4j-0.16.0.jar + + + ext/org.eclipse.xtend.lib.macro-2.24.0.jar + external/org.eclipse.xtend.lib.macro-2.24.0.jar + + +- ext/org.eclipse.lsp4j.generator-0.13.0.jar +- external/org.eclipse.lsp4j.generator-0.13.0.jar ++ ext/org.eclipse.lsp4j.generator-0.16.0.jar ++ external/org.eclipse.lsp4j.generator-0.16.0.jar + + + ext/org.eclipse.xtend.lib-2.24.0.jar +@@ -499,16 +499,16 @@ + external/org.eclipse.xtext.xbase.lib-2.24.0.jar + + +- ext/org.eclipse.lsp4j.jsonrpc-0.13.0.jar +- external/org.eclipse.lsp4j.jsonrpc-0.13.0.jar ++ ext/org.eclipse.lsp4j.jsonrpc-0.16.0.jar ++ external/org.eclipse.lsp4j.jsonrpc-0.16.0.jar + + +- ext/org.eclipse.lsp4j.debug-0.13.0.jar +- external/org.eclipse.lsp4j.debug-0.13.0.jar ++ ext/org.eclipse.lsp4j.debug-0.16.0.jar ++ external/org.eclipse.lsp4j.debug-0.16.0.jar + + +- ext/org.eclipse.lsp4j.jsonrpc.debug-0.13.0.jar +- external/org.eclipse.lsp4j.jsonrpc.debug-0.13.0.jar ++ ext/org.eclipse.lsp4j.jsonrpc.debug-0.16.0.jar ++ external/org.eclipse.lsp4j.jsonrpc.debug-0.16.0.jar + + + +diff --git a/java/java.lsp.server/external/binaries-list b/java/java.lsp.server/external/binaries-list +index 4bf25a3c3f..45f0336ab7 100644 +--- a/java/java.lsp.server/external/binaries-list ++++ b/java/java.lsp.server/external/binaries-list +@@ -15,11 +15,11 @@ + # specific language governing permissions and limitations + # under the License. + +-E95BD5C2A465A40A66C9DC098BF95C237B80C4EB org.eclipse.lsp4j:org.eclipse.lsp4j:0.13.0 +-A491B7AC91B35EC177DE629518D5A68A9C7878FC org.eclipse.lsp4j:org.eclipse.lsp4j.debug:0.13.0 +-82848D7796D399F3E5109B7331A143D7C649B520 org.eclipse.lsp4j:org.eclipse.lsp4j.generator:0.13.0 +-0B50BD2A6AF496803F72176AD066532DAE62574E org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.13.0 +-68AB471B1FED84FABA25D6CE9005B9369B503A74 org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc.debug:0.13.0 ++10F74DD2E4BB42AFA1DC7966BC4A16F87C8D80A9 org.eclipse.lsp4j:org.eclipse.lsp4j:0.16.0 ++9E162228BF8197A5CCC725FBEA8CEEBFE3065271 org.eclipse.lsp4j:org.eclipse.lsp4j.generator:0.16.0 ++F94BEAD3015B6B915C5AEDD52398A67546BE61F3 org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.16.0 ++D57DF9450B1B51E58772F820B234C15CE65415AF org.eclipse.lsp4j:org.eclipse.lsp4j.debug:0.16.0 ++5CE0D2E3135370CF96A0B299ED41D4DC6111380E org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc.debug:0.16.0 + A57306D5D523A4750FA09B2708062EA4972AFEA2 org.eclipse.xtend:org.eclipse.xtend.lib:2.24.0 + D8F5566BA67748ED9E91856E077EE99F00E86653 org.eclipse.xtend:org.eclipse.xtend.lib.macro:2.24.0 + 53FBD66084B08850258E61C838CC1FB94335E718 org.eclipse.xtext:org.eclipse.xtext.xbase.lib:2.24.0 +diff --git a/java/java.lsp.server/external/lsp4j-0.13.0-license.txt b/java/java.lsp.server/external/lsp4j-0.13.0-license.txt +deleted file mode 100644 +index 0febd7daaf..0000000000 +--- a/java/java.lsp.server/external/lsp4j-0.13.0-license.txt ++++ /dev/null +@@ -1,94 +0,0 @@ +-Name: Eclipse Language Server Protocol Library +-Origin: Eclipse +-Version: 0.13.0 +-License: EPL-v20 +-URL: http://www.eclipse.org/ +-Description: Eclipse Language Server Protocol Library +-Files: org.eclipse.lsp4j-0.13.0.jar org.eclipse.lsp4j.debug-0.13.0.jar org.eclipse.lsp4j.generator-0.13.0.jar org.eclipse.lsp4j.jsonrpc-0.13.0.jar org.eclipse.lsp4j.jsonrpc.debug-0.13.0.jar +- +-Eclipse Public License - v 2.0 +-THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE (“AGREEMENT”). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. +- +-1. Definitions +-“Contribution” means: +- +-a) in the case of the initial Contributor, the initial content Distributed under this Agreement, and +-b) in the case of each subsequent Contributor: +-i) changes to the Program, and +-ii) additions to the Program; where such changes and/or additions to the Program originate from and are Distributed by that particular Contributor. A Contribution “originates” from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include changes or additions to the Program that are not Modified Works. +-“Contributor” means any person or entity that Distributes the Program. +- +-“Licensed Patents” mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. +- +-“Program” means the Contributions Distributed in accordance with this Agreement. +- +-“Recipient” means anyone who receives the Program under this Agreement or any Secondary License (as applicable), including Contributors. +- +-“Derivative Works” shall mean any work, whether in Source Code or other form, that is based on (or derived from) the Program and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. +- +-“Modified Works” shall mean any work in Source Code or other form that results from an addition to, deletion from, or modification of the contents of the Program, including, for purposes of clarity any new file in Source Code form that contains any contents of the Program. Modified Works shall not include works that contain only declarations, interfaces, types, classes, structures, or files of the Program solely in each case in order to link to, bind by name, or subclass the Program or Modified Works thereof. +- +-“Distribute” means the acts of a) distributing or b) making available in any manner that enables the transfer of a copy. +- +-“Source Code” means the form of a Program preferred for making modifications, including but not limited to software source code, documentation source, and configuration files. +- +-“Secondary License” means either the GNU General Public License, Version 2.0, or any later versions of that license, including any exceptions or additional permissions as identified by the initial Contributor. +- +-2. Grant of Rights +-a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, Distribute and sublicense the Contribution of such Contributor, if any, and such Derivative Works. +- +-b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in Source Code or other form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. +- +-c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to Distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. +- +-d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. +- +-e) Notwithstanding the terms of any Secondary License, no Contributor makes additional grants to any Recipient (other than those set forth in this Agreement) as a result of such Recipient's receipt of the Program under the terms of a Secondary License (if permitted under the terms of Section 3). +- +-3. Requirements +-3.1 If a Contributor Distributes the Program in any form, then: +- +-a) the Program must also be made available as Source Code, in accordance with section 3.2, and the Contributor must accompany the Program with a statement that the Source Code for the Program is available under this Agreement, and informs Recipients how to obtain it in a reasonable manner on or through a medium customarily used for software exchange; and +- +-b) the Contributor may Distribute the Program under a license different than this Agreement, provided that such license: +- +-i) effectively disclaims on behalf of all other Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; +-ii) effectively excludes on behalf of all other Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; +-iii) does not attempt to limit or alter the recipients' rights in the Source Code under section 3.2; and +-iv) requires any subsequent distribution of the Program by any party to be under a license that satisfies the requirements of this section 3. +-3.2 When the Program is Distributed as Source Code: +- +-a) it must be made available under this Agreement, or if the Program (i) is combined with other material in a separate file or files made available under a Secondary License, and (ii) the initial Contributor attached to the Source Code the notice described in Exhibit A of this Agreement, then the Program may be made available under the terms of such Secondary Licenses, and +-b) a copy of this Agreement must be included with each copy of the Program. +-3.3 Contributors may not remove or alter any copyright, patent, trademark, attribution notices, disclaimers of warranty, or limitations of liability (“notices”) contained within the Program from any copy of the Program which they Distribute, provided that Contributors may add their own appropriate notices. +- +-4. Commercial Distribution +-Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor (“Commercial Contributor”) hereby agrees to defend and indemnify every other Contributor (“Indemnified Contributor”) against any losses, damages and costs (collectively “Losses”) arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. +- +-For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. +- +-5. No Warranty +-EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. +- +-6. Disclaimer of Liability +-EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +- +-7. General +-If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. +- +-If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. +- +-All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. +- +-Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be Distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to Distribute the Program (including its Contributions) under the new version. +- +-Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. Nothing in this Agreement is intended to be enforceable by any entity that is not a Contributor or Recipient. No third-party beneficiary rights are created under this Agreement. +- +-Exhibit A - Form of Secondary Licenses Notice +-“This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), version(s), and exceptions or additional permissions here}.” +- +-Simply including a copy of this Agreement, including this Exhibit A is not sufficient to license the Source Code under Secondary Licenses. +- +-If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. +- +-You may add additional accurate notices of copyright ownership. +diff --git a/java/java.lsp.server/nbproject/project.properties b/java/java.lsp.server/nbproject/project.properties +index a9e1df8dd8..9d4c326ff3 100644 +--- a/java/java.lsp.server/nbproject/project.properties ++++ b/java/java.lsp.server/nbproject/project.properties +@@ -24,11 +24,11 @@ lsp.build.dir=vscode/nbcode + test.unit.run.cp.extra= + cp.extra=${tools.jar} + +-release.external/org.eclipse.lsp4j-0.13.0.jar=modules/ext/org.eclipse.lsp4j-0.13.0.jar +-release.external/org.eclipse.lsp4j.debug-0.13.0.jar=modules/ext/org.eclipse.lsp4j.debug-0.13.0.jar +-release.external/org.eclipse.lsp4j.generator-0.13.0.jar=modules/ext/org.eclipse.lsp4j.generator-0.13.0.jar +-release.external/org.eclipse.lsp4j.jsonrpc-0.13.0.jar=modules/ext/org.eclipse.lsp4j.jsonrpc-0.13.0.jar +-release.external/org.eclipse.lsp4j.jsonrpc.debug-0.13.0.jar=modules/ext/org.eclipse.lsp4j.jsonrpc.debug-0.13.0.jar ++release.external/org.eclipse.lsp4j-0.16.0.jar=modules/ext/org.eclipse.lsp4j-0.16.0.jar ++release.external/org.eclipse.lsp4j.debug-0.16.0.jar=modules/ext/org.eclipse.lsp4j.debug-0.16.0.jar ++release.external/org.eclipse.lsp4j.generator-0.16.0.jar=modules/ext/org.eclipse.lsp4j.generator-0.16.0.jar ++release.external/org.eclipse.lsp4j.jsonrpc-0.16.0.jar=modules/ext/org.eclipse.lsp4j.jsonrpc-0.16.0.jar ++release.external/org.eclipse.lsp4j.jsonrpc.debug-0.16.0.jar=modules/ext/org.eclipse.lsp4j.jsonrpc.debug-0.16.0.jar + release.external/org.eclipse.xtend.lib-2.24.0.jar=modules/ext/org.eclipse.xtend.lib-2.24.0.jar + release.external/org.eclipse.xtend.lib.macro-2.24.0.jar=modules/ext/org.eclipse.xtend.lib.macro-2.24.0.jar + release.external/org.eclipse.xtext.xbase.lib-2.24.0.jar=modules/ext/org.eclipse.xtext.xbase.lib-2.24.0.jar +diff --git a/java/java.lsp.server/nbproject/project.xml b/java/java.lsp.server/nbproject/project.xml +index fffc512714..3ac9dfe281 100644 +--- a/java/java.lsp.server/nbproject/project.xml ++++ b/java/java.lsp.server/nbproject/project.xml +@@ -842,24 +842,24 @@ + org.netbeans.modules.java.lsp.server.ui + + +- ext/org.eclipse.lsp4j.debug-0.13.0.jar +- external/org.eclipse.lsp4j.debug-0.13.0.jar ++ ext/org.eclipse.lsp4j.debug-0.16.0.jar ++ external/org.eclipse.lsp4j.debug-0.16.0.jar + + + ext/org.eclipse.xtend.lib.macro-2.24.0.jar + external/org.eclipse.xtend.lib.macro-2.24.0.jar + + +- ext/org.eclipse.lsp4j.generator-0.13.0.jar +- external/org.eclipse.lsp4j.generator-0.13.0.jar ++ ext/org.eclipse.lsp4j.generator-0.16.0.jar ++ external/org.eclipse.lsp4j.generator-0.16.0.jar + + +- ext/org.eclipse.lsp4j.jsonrpc-0.13.0.jar +- external/org.eclipse.lsp4j.jsonrpc-0.13.0.jar ++ ext/org.eclipse.lsp4j.jsonrpc-0.16.0.jar ++ external/org.eclipse.lsp4j.jsonrpc-0.16.0.jar + + +- ext/org.eclipse.lsp4j.jsonrpc.debug-0.13.0.jar +- external/org.eclipse.lsp4j.jsonrpc.debug-0.13.0.jar ++ ext/org.eclipse.lsp4j.jsonrpc.debug-0.16.0.jar ++ external/org.eclipse.lsp4j.jsonrpc.debug-0.16.0.jar + + + ext/org.eclipse.xtend.lib-2.24.0.jar +@@ -870,8 +870,8 @@ + external/org.eclipse.xtext.xbase.lib-2.24.0.jar + + +- ext/org.eclipse.lsp4j-0.13.0.jar +- external/org.eclipse.lsp4j-0.13.0.jar ++ ext/org.eclipse.lsp4j-0.16.0.jar ++ external/org.eclipse.lsp4j-0.16.0.jar + + + +diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java +index 19bb48142a..4cd2ebf569 100644 +--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java ++++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java +@@ -151,6 +151,7 @@ import org.eclipse.lsp4j.MessageParams; + import org.eclipse.lsp4j.MessageType; + import org.eclipse.lsp4j.ParameterInformation; + import org.eclipse.lsp4j.Position; ++import org.eclipse.lsp4j.PrepareRenameDefaultBehavior; + import org.eclipse.lsp4j.PrepareRenameParams; + import org.eclipse.lsp4j.PrepareRenameResult; + import org.eclipse.lsp4j.PublishDiagnosticsParams; +@@ -179,6 +180,7 @@ import org.eclipse.lsp4j.VersionedTextDocumentIdentifier; + import org.eclipse.lsp4j.WillSaveTextDocumentParams; + import org.eclipse.lsp4j.WorkspaceEdit; + import org.eclipse.lsp4j.jsonrpc.messages.Either; ++import org.eclipse.lsp4j.jsonrpc.messages.Either3; + import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode; + import org.eclipse.lsp4j.services.LanguageClient; + import org.eclipse.lsp4j.services.LanguageClientAware; +@@ -1467,7 +1469,7 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + } + + @Override +- public CompletableFuture> prepareRename(PrepareRenameParams params) { ++ public CompletableFuture> prepareRename(PrepareRenameParams params) { + // shortcut: if the projects are not yet initialized, return empty: + if (server.openedProjects().getNow(null) == null) { + return CompletableFuture.completedFuture(null); +@@ -1476,7 +1478,7 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + if (source == null) { + return CompletableFuture.completedFuture(null); + } +- CompletableFuture> result = new CompletableFuture<>(); ++ CompletableFuture> result = new CompletableFuture<>(); + try { + source.runUserActionTask(cc -> { + cc.toPhase(JavaSource.Phase.RESOLVED); +@@ -1513,7 +1515,7 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + } + Range r = new Range(Utils.createPosition(cc.getCompilationUnit(), ts.offset()), + Utils.createPosition(cc.getCompilationUnit(), ts.offset() + ts.token().length())); +- result.complete(Either.forRight(new PrepareRenameResult(r, ts.token().text().toString()))); ++ result.complete(Either3.forSecond(new PrepareRenameResult(r, ts.token().text().toString()))); + } else { + result.complete(null); + } +diff --git a/nbbuild/antsrc/org/netbeans/nbbuild/extlibs/ignored-overlaps b/nbbuild/antsrc/org/netbeans/nbbuild/extlibs/ignored-overlaps +index be5c761c90..625a8af8ae 100644 +--- a/nbbuild/antsrc/org/netbeans/nbbuild/extlibs/ignored-overlaps ++++ b/nbbuild/antsrc/org/netbeans/nbbuild/extlibs/ignored-overlaps +@@ -69,11 +69,11 @@ extide/gradle/external/gradle-7.4-bin.zip platform/o.apache.commons.codec/extern + enterprise/javaee.api/external/javax.annotation-api-1.2.jar enterprise/javaee7.api/external/javax.annotation-api-1.2.jar + + # ide/lsp.client and java/java.lsp.server both use LSP libraries, but are independent: +-ide/lsp.client/external/org.eclipse.lsp4j-0.13.0.jar java/java.lsp.server/external/org.eclipse.lsp4j-0.13.0.jar +-ide/lsp.client/external/org.eclipse.lsp4j.debug-0.13.0.jar java/java.lsp.server/external/org.eclipse.lsp4j.debug-0.13.0.jar +-ide/lsp.client/external/org.eclipse.lsp4j.jsonrpc.debug-0.13.0.jar java/java.lsp.server/external/org.eclipse.lsp4j.jsonrpc.debug-0.13.0.jar +-ide/lsp.client/external/org.eclipse.lsp4j.generator-0.13.0.jar java/java.lsp.server/external/org.eclipse.lsp4j.generator-0.13.0.jar +-ide/lsp.client/external/org.eclipse.lsp4j.jsonrpc-0.13.0.jar java/java.lsp.server/external/org.eclipse.lsp4j.jsonrpc-0.13.0.jar ++ide/lsp.client/external/org.eclipse.lsp4j-0.16.0.jar java/java.lsp.server/external/org.eclipse.lsp4j-0.16.0.jar ++ide/lsp.client/external/org.eclipse.lsp4j.debug-0.16.0.jar java/java.lsp.server/external/org.eclipse.lsp4j.debug-0.16.0.jar ++ide/lsp.client/external/org.eclipse.lsp4j.jsonrpc.debug-0.16.0.jar java/java.lsp.server/external/org.eclipse.lsp4j.jsonrpc.debug-0.16.0.jar ++ide/lsp.client/external/org.eclipse.lsp4j.generator-0.16.0.jar java/java.lsp.server/external/org.eclipse.lsp4j.generator-0.16.0.jar ++ide/lsp.client/external/org.eclipse.lsp4j.jsonrpc-0.16.0.jar java/java.lsp.server/external/org.eclipse.lsp4j.jsonrpc-0.16.0.jar + ide/lsp.client/external/org.eclipse.xtend.lib-2.24.0.jar java/java.lsp.server/external/org.eclipse.xtend.lib-2.24.0.jar + ide/lsp.client/external/org.eclipse.xtend.lib.macro-2.24.0.jar java/java.lsp.server/external/org.eclipse.xtend.lib.macro-2.24.0.jar + ide/lsp.client/external/org.eclipse.xtext.xbase.lib-2.24.0.jar java/java.lsp.server/external/org.eclipse.xtext.xbase.lib-2.24.0.jar diff --git a/vscode/package.json b/vscode/package.json index b1fc1fb..d07e18f 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -40,11 +40,23 @@ "workspaceContains:pom.xml", "workspaceContains:build.gradle", "onDebug", - "onDebugDynamicConfigurations" + "onDebugDynamicConfigurations", + "onNotebook:ijnb-notebook" ], "main": "./out/extension.js", "l10n": "./l10n", "contributes": { + "notebooks": [ + { + "type": "ijnb-notebook", + "displayName": "IJNB Notebook", + "selector": [ + { + "filenamePattern": "*.ijnb" + } + ] + } + ], "languages": [ { "id": "java", @@ -273,6 +285,11 @@ "%jdk.configuration.inlay.enabled.enum.var.markdownDescription%" ] } + }, + "jdk.notebook.classpath": { + "type": "string", + "default": "", + "description": "Classpath that needs to be set for notebooks" } } }, @@ -541,6 +558,12 @@ { "command": "jdk.delete.cache", "title": "%jdk.delete.cache%" + }, + { + "command": "jdk.new.notebook", + "title": "Create New Notebook...", + "category": "Java", + "icon": "$(new-file)" } ], "keybindings": [ diff --git a/vscode/src/commands/commands.ts b/vscode/src/commands/commands.ts index cbc8f56..4ccbf7d 100644 --- a/vscode/src/commands/commands.ts +++ b/vscode/src/commands/commands.ts @@ -50,7 +50,8 @@ export const extCommands = { attachDebuggerConnector: appendPrefixToCommand("java.attachDebugger.connector"), attachDebuggerConfigurations: appendPrefixToCommand("java.attachDebugger.configurations"), loadWorkspaceTests: appendPrefixToCommand("load.workspace.tests"), - projectDeleteEntry: appendPrefixToCommand("foundProjects.deleteEntry") + projectDeleteEntry: appendPrefixToCommand("foundProjects.deleteEntry"), + createNotebook: appendPrefixToCommand("new.notebook") } export const builtInCommands = { @@ -83,5 +84,7 @@ export const nbCommands = { cleanWorkspace: appendPrefixToCommand('clean.workspace'), clearProjectCaches: appendPrefixToCommand('clear.project.caches'), javaProjectPackages: appendPrefixToCommand('java.get.project.packages'), - openStackTrace: appendPrefixToCommand('open.stacktrace') + openStackTrace: appendPrefixToCommand('open.stacktrace'), + executeNotebookCell: appendPrefixToCommand("jshell.execute.cell"), + notebookCleanup: appendPrefixToCommand("jdk.jshell.cleanup") } \ No newline at end of file diff --git a/vscode/src/commands/create.ts b/vscode/src/commands/create.ts index 3f41b08..f2b5a48 100644 --- a/vscode/src/commands/create.ts +++ b/vscode/src/commands/create.ts @@ -18,11 +18,13 @@ import { LanguageClient } from "vscode-languageclient/node"; import { nbCommands, builtInCommands, extCommands } from "./commands"; import { l10n } from "../localiser"; import * as os from 'os'; +import * as path from 'path'; import * as fs from 'fs'; import { ICommand } from "./types"; import { getContextUri, isNbCommandRegistered } from "./utils"; -import { isString } from "../utils"; +import { isError, isString } from "../utils"; import { globalState } from "../globalState"; +import { LOGGER } from "../logger"; const newFromTemplate = async (ctx: any, template: any) => { const client: LanguageClient = await globalState.getClientPromise().client; @@ -96,6 +98,71 @@ const newProject = async (ctx: any) => { } }; +const createNewNotebook = async () => { + const userHomeDir = os.homedir(); + + const filePath = await window.showInputBox({ + prompt: "Enter path for new Java notebook (.ijnb)", + value: path.join(userHomeDir, "Untitled.ijnb") + }); + + if (!filePath?.trim()) { + return; + } + + const finalPath = filePath.endsWith('.ijnb') ? filePath : `${filePath}.ijnb`; + + LOGGER.log(`Attempting to create notebook at: ${finalPath}`); + + try { + const exists = await fs.promises.access(finalPath) + .then(() => true) + .catch(() => false); + + if (exists) { + window.showErrorMessage("Notebook already exists, please try creating with some different name"); + return; + } + + const dir = path.dirname(finalPath); + await fs.promises.mkdir(dir, { recursive: true }); + + const emptyNotebook = { + cells: [{ + cell_type: "code", + source: [], + metadata: { + language: "java" + }, + execution_count: null, + outputs: [] + }], + metadata: { + kernelspec: { + name: "java", + language: "java", + display_name: "Java" + }, + language_info: { + name: "java" + } + }, + nbformat: 4, + nbformat_minor: 5 + }; + + await fs.promises.writeFile(finalPath, JSON.stringify(emptyNotebook, null, 2), { encoding: 'utf8' }); + + LOGGER.log(`Created notebook at: ${finalPath}`); + + const notebookUri = Uri.file(finalPath); + const notebookDocument = await workspace.openNotebookDocument(notebookUri); + await window.showNotebookDocument(notebookDocument); + } catch (err) { + console.error(`Detailed error:`, err); + window.showErrorMessage(`Failed to create notebook: ${isError(err) ? err.message : " "}`); + } +}; export const registerCreateCommands: ICommand[] = [ { @@ -104,5 +171,8 @@ export const registerCreateCommands: ICommand[] = [ }, { command: extCommands.newProject, handler: newProject + }, { + command: extCommands.createNotebook, + handler: createNewNotebook } ]; \ No newline at end of file diff --git a/vscode/src/configurations/configuration.ts b/vscode/src/configurations/configuration.ts index fadbe64..66d42b5 100644 --- a/vscode/src/configurations/configuration.ts +++ b/vscode/src/configurations/configuration.ts @@ -32,6 +32,7 @@ export const configKeys = { userdir: 'userdir', revealInActivteProj: "revealActiveInProjects", telemetryEnabled: 'telemetry.enabled', + notebookClasspath: "notebook.classpath" }; export const builtInConfigKeys = { diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts index 41ff804..058d57f 100644 --- a/vscode/src/extension.ts +++ b/vscode/src/extension.ts @@ -19,8 +19,6 @@ * under the License. */ -'use strict'; - import { ExtensionContext } from 'vscode'; import * as launchConfigurations from './launchConfigurations'; import { extConstants } from './constants'; @@ -34,6 +32,7 @@ import { ExtensionContextInfo } from './extensionContextInfo'; import { ClientPromise } from './lsp/clientPromise'; import { globalState } from './globalState'; import { Telemetry } from './telemetry/telemetry'; +import { registerNotebooks } from './notebooks/register'; export function activate(context: ExtensionContext): VSNetBeansAPI { const contextInfo = new ExtensionContextInfo(context); @@ -47,6 +46,7 @@ export function activate(context: ExtensionContext): VSNetBeansAPI { registerDebugger(context); subscribeCommands(context); registerFileProviders(context); + registerNotebooks(context); launchConfigurations.updateLaunchConfig(); diff --git a/vscode/src/lsp/listeners/requests/notebooks.ts b/vscode/src/lsp/listeners/requests/notebooks.ts new file mode 100644 index 0000000..b5a0190 --- /dev/null +++ b/vscode/src/lsp/listeners/requests/notebooks.ts @@ -0,0 +1,27 @@ +/* + Copyright (c) 2023-2025, Oracle and/or its affiliates. + + Licensed 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 + + https://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. +*/ + +import { NotebookCellContentRequest, NotebookCellContentRequestParams } from "../../protocol"; +import { notificationOrRequestListenerType } from "../../types"; + +const notebookCellContentRequestHandler = (params: NotebookCellContentRequestParams) => { + return ""; +} + +export const notebookListeners: notificationOrRequestListenerType[] = [{ + type: NotebookCellContentRequest.type, + handler: notebookCellContentRequestHandler +}]; \ No newline at end of file diff --git a/vscode/src/lsp/listeners/requests/register.ts b/vscode/src/lsp/listeners/requests/register.ts index 116b434..9db1617 100644 --- a/vscode/src/lsp/listeners/requests/register.ts +++ b/vscode/src/lsp/listeners/requests/register.ts @@ -17,12 +17,14 @@ import { NbLanguageClient } from "../../nbLanguageClient" import { notificationOrRequestListenerType } from "../../types" import { requestListeners } from "./handlers" +import { notebookListeners } from "./notebooks"; import { terminalListeners } from "./terminal" const listeners: notificationOrRequestListenerType[] = [ ...requestListeners, - ...terminalListeners + ...terminalListeners, + ...notebookListeners ]; export const registerRequestListeners = (client: NbLanguageClient) => { diff --git a/vscode/src/lsp/nbLanguageClient.ts b/vscode/src/lsp/nbLanguageClient.ts index 05efe6e..14aae56 100644 --- a/vscode/src/lsp/nbLanguageClient.ts +++ b/vscode/src/lsp/nbLanguageClient.ts @@ -65,7 +65,8 @@ export class NbLanguageClient extends LanguageClient { 'wantsTelemetryEnabled': Telemetry.getIsTelemetryFeatureAvailable(), 'commandPrefix': extConstants.COMMAND_PREFIX, 'configurationPrefix': `${extConstants.COMMAND_PREFIX}.`, - 'altConfigurationPrefix': `${extConstants.COMMAND_PREFIX}.` + 'altConfigurationPrefix': `${extConstants.COMMAND_PREFIX}.`, + 'wantsNotebookSupport': true } }, errorHandler: { diff --git a/vscode/src/lsp/protocol.ts b/vscode/src/lsp/protocol.ts index 32cd87e..3b1ddf2 100644 --- a/vscode/src/lsp/protocol.ts +++ b/vscode/src/lsp/protocol.ts @@ -342,4 +342,12 @@ export namespace CloseOutputRequest { export namespace ResetOutputRequest { export const type = new ProtocolRequestType('output/reset'); -} \ No newline at end of file +} + +export interface NotebookCellContentRequestParams { + notebookUri: string; + cellUri: string; +} +export namespace NotebookCellContentRequest { + export const type = new ProtocolRequestType('notebookDocument/cellContentRequest'); +} diff --git a/vscode/src/notebooks/impl.ts b/vscode/src/notebooks/impl.ts new file mode 100644 index 0000000..1f1ea92 --- /dev/null +++ b/vscode/src/notebooks/impl.ts @@ -0,0 +1,345 @@ +import * as vscode from 'vscode'; +import { LanguageClient } from 'vscode-languageclient/node'; +import { isNbCommandRegistered } from '../commands/utils'; +import { nbCommands } from '../commands/commands'; +import { globalState } from '../globalState'; +import { getConfigurationValue } from '../configurations/handlers'; +import { configKeys } from '../configurations/configuration'; + +export class IJNBNotebookSerializer implements vscode.NotebookSerializer { + async deserializeNotebook(content: Uint8Array, _token: vscode.CancellationToken): Promise { + let notebook: any; + const contents = this.decoder.decode(content); + + console.log(`Content Size: ${content.length}`); + console.log("Decoded Content: ", contents); + + if (!contents.trim()) { + return new vscode.NotebookData([ + new vscode.NotebookCellData( + vscode.NotebookCellKind.Markup, + '# Error: Empty Notebook\n\nThe notebook file appears to be empty.', + 'markdown' + ) + ]); + } + + try { + notebook = JSON.parse(contents); + } catch (error) { + console.error('Failed to parse notebook content:', error); + vscode.window.showErrorMessage(`Failed to open notebook: ${(error as Error).message}`); + + return new vscode.NotebookData([ + new vscode.NotebookCellData( + vscode.NotebookCellKind.Markup, + `# Error Opening Notebook\n\nThere was an error parsing the notebook file. The file may be corrupted or in an invalid format.\n\nError details: ${(error as Error).message}`, + 'markdown' + ) + ]); + } + + if (!notebook || !Array.isArray(notebook.cells)) { + console.error('Invalid notebook structure'); + vscode.window.showErrorMessage('The notebook file has an invalid structure.'); + return new vscode.NotebookData([ + new vscode.NotebookCellData( + vscode.NotebookCellKind.Markup, + '# Error: Invalid Notebook Structure\n\nThe notebook file does not have the expected structure.', + 'markdown' + ) + ]); + } + + const cells = notebook.cells.map((cell: any) => { + if (typeof cell.cell_type !== 'string' || !Array.isArray(cell.source)) { + console.warn('Invalid cell structure:', cell); + return new vscode.NotebookCellData( + vscode.NotebookCellKind.Markup, + '# Error: Invalid Cell\n\nThis cell could not be parsed correctly.', + 'markdown' + ); + } + + const cellContent = cell.source.join(''); + const cellData = new vscode.NotebookCellData( + cell.cell_type === 'code' ? vscode.NotebookCellKind.Code : vscode.NotebookCellKind.Markup, + cellContent, + cell.cell_type === 'code' ? 'java' : 'markdown' + ); + + if (cell.outputs && Array.isArray(cell.outputs)) { + const outputs: vscode.NotebookCellOutput[] = []; + + for (const output of cell.outputs) { + if (output.output_type === 'display_data' || output.output_type === 'execute_result') { + const items: vscode.NotebookCellOutputItem[] = []; + + if (output.data) { + // Handle text/plain + if (output.data['text/plain']) { + const text = Array.isArray(output.data['text/plain']) + ? output.data['text/plain'].join('\n') + : output.data['text/plain']; + items.push(vscode.NotebookCellOutputItem.text(text, 'text/plain')); + } + + // Handle image formats + for (const mimeType in output.data) { + if (mimeType.startsWith('image/')) { + const base64Data = Array.isArray(output.data[mimeType]) + ? output.data[mimeType].join('') + : output.data[mimeType]; + + // Convert base64 string to Uint8Array + const imageData = this.base64ToUint8Array(base64Data); + items.push(new vscode.NotebookCellOutputItem(imageData, mimeType)); + } + } + } + + if (items.length > 0) { + outputs.push(new vscode.NotebookCellOutput(items, output.metadata)); + } + } else if (output.output_type === 'stream') { + const text = Array.isArray(output.text) ? output.text.join('') : output.text; + outputs.push(new vscode.NotebookCellOutput([ + vscode.NotebookCellOutputItem.text(text) + ])); + } else if (output.output_type === 'error') { + outputs.push(new vscode.NotebookCellOutput([ + vscode.NotebookCellOutputItem.error({ + name: output.ename || 'Error', + message: output.evalue || 'Unknown error', + stack: output.traceback ? output.traceback.join('\n') : undefined + }) + ])); + } + } + + if (outputs.length > 0) { + cellData.outputs = outputs; + } + } + + if (cell.execution_count !== null && cell.execution_count !== undefined) { + cellData.executionSummary = { + executionOrder: cell.execution_count, + success: true + }; + } + + return cellData; + }); + + return new vscode.NotebookData(cells); + } + + async serializeNotebook(data: vscode.NotebookData, _token: vscode.CancellationToken): Promise { + const notebook = { + cells: data.cells.map(cell => { + const cellData: any = { + cell_type: cell.kind === vscode.NotebookCellKind.Code ? 'code' : 'markdown', + source: [cell.value], + metadata: { + ...cell.metadata, + language: cell.languageId + }, + execution_count: cell.executionSummary?.executionOrder ?? null, + outputs: [] + }; + + if (cell.outputs && cell.outputs.length > 0) { + cellData.outputs = cell.outputs.map(output => { + const outputData: any = { + output_type: 'execute_result', + data: {}, + metadata: output.metadata, + execution_count: cell.executionSummary?.executionOrder ?? null + }; + + for (const item of output.items) { + const errorItem = item.mime === 'application/vnd.code.notebook.error'; + if (errorItem) { + const errorData = JSON.parse(Buffer.from(item.data).toString()); + return { + output_type: 'error', + ename: errorData.name, + evalue: errorData.message, + traceback: errorData.stack ? [errorData.stack] : [] + }; + } + + // Handle text items + if (item.mime === 'text/plain') { + outputData.data['text/plain'] = Buffer.from(item.data).toString(); + } + // Handle image items + else if (item.mime.startsWith('image/')) { + // Convert binary data to base64 string for storage + outputData.data[item.mime] = this.uint8ArrayToBase64(item.data); + } + } + + return outputData; + }); + } + + return cellData; + }), + metadata: { + language_info: { + name: 'java' + }, + orig_nbformat: 4 + }, + nbformat: 4, + nbformat_minor: 2 + }; + + return this.encoder.encode(JSON.stringify(notebook, null, 2)); + } + + private readonly decoder = new TextDecoder(); + private readonly encoder = new TextEncoder(); + + private base64ToUint8Array(base64: string): Uint8Array { + if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') { + return Buffer.from(base64, 'base64'); + } else { + return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); + } + } + + private uint8ArrayToBase64(data: Uint8Array): string { + if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') { + return Buffer.from(data).toString('base64'); + } else { + return btoa(String.fromCharCode.apply(null, Array.from(data))); + } + } +} + +export class IJNBKernel implements vscode.Disposable { + private readonly controller: vscode.NotebookController; + + constructor() { + this.controller = vscode.notebooks.createNotebookController( + 'ijnb-kernel', + 'ijnb-notebook', + 'IJNB Kernel' + ); + + this.controller.supportedLanguages = ['markdown', 'java']; + this.controller.supportsExecutionOrder = true; + this.controller.executeHandler = this.executeCell.bind(this); + + vscode.workspace.onDidCloseNotebookDocument(this.onNotebookClosed.bind(this)); + } + + dispose() { + this.controller.dispose(); + } + + private async onNotebookClosed(notebook: vscode.NotebookDocument): Promise { + try { + await globalState.getClientPromise().client; + if (await isNbCommandRegistered(nbCommands.notebookCleanup)) { + await vscode.commands.executeCommand(nbCommands.notebookCleanup, notebook.uri.toString()); + } + } catch (err) { + console.error(`Error cleaning up JShell for notebook: ${err}`); + } + } + + private async executeCell(cells: vscode.NotebookCell[], notebook: vscode.NotebookDocument, _controller: vscode.NotebookController): Promise { + console.log("Starting execution for cells:", cells); + + const notebookId = notebook.uri.toString(); + const classpath = getConfigurationValue(configKeys.notebookClasspath); + + for (let cell of cells) { + console.log("Executing cell:", cell.document.getText()); + + const execution = this.controller.createNotebookCellExecution(cell); + execution.executionOrder = 1; + execution.start(Date.now()); + + try { + const cellContent = cell.document.getText(); + console.log("Cell content:", cellContent); + + if (cell.document.languageId === 'markdown') { + await execution.replaceOutput([ + new vscode.NotebookCellOutput([ + vscode.NotebookCellOutputItem.text(cellContent) + ]) + ]); + } else { + await globalState.getClientPromise().client; + if (await isNbCommandRegistered(nbCommands.executeNotebookCell)) { + const response: { data: string, mimeType: string }[] = await vscode.commands.executeCommand( + nbCommands.executeNotebookCell, + cellContent, + notebookId, + classpath || null + ); + + console.log("Response:", response); + const outputMap: Map = new Map(); + response.forEach((el) => { + if (!outputMap.has(el.mimeType)) { + outputMap.set(el.mimeType, [el.data]); + } else { + outputMap.set(el.mimeType, [...outputMap.get(el.mimeType)!, el.data]); + } + }); + const output: vscode.NotebookCellOutputItem[] = []; + for (const [mimeType, value] of outputMap) { + if (mimeType.startsWith('image')) { + output.push(createNotebookCellOutput(value[0], mimeType)); + } else { + output.push(createNotebookCellOutput(value.join("\n"), mimeType)); + } + } + await execution.replaceOutput([ + new vscode.NotebookCellOutput(output.length ? output : [vscode.NotebookCellOutputItem.text('')]) + ]); + } else { + throw new Error("Notebook not supported"); + } + } + + execution.end(true, Date.now()); + } catch (err) { + console.error(`Error executing cell: ${err}`); + + await execution.replaceOutput([ + new vscode.NotebookCellOutput([ + vscode.NotebookCellOutputItem.error({ + name: 'Execution Error', + message: `${err}` + }) + ]) + ]); + + execution.end(false, Date.now()); + } + } + } +} + +const createNotebookCellOutput = (data: string, mimeType: string): vscode.NotebookCellOutputItem => { + if (mimeType.startsWith('image')) { + return new vscode.NotebookCellOutputItem(base64ToUint8Array(data), mimeType); + } + return vscode.NotebookCellOutputItem.text(data, mimeType); +} + +export function base64ToUint8Array(base64: string): Uint8Array { + if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') { + return Buffer.from(base64, 'base64'); + } else { + return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); + } +} \ No newline at end of file diff --git a/vscode/src/notebooks/register.ts b/vscode/src/notebooks/register.ts new file mode 100644 index 0000000..d9adb03 --- /dev/null +++ b/vscode/src/notebooks/register.ts @@ -0,0 +1,11 @@ +import { ExtensionContext, workspace } from "vscode"; +import { IJNBKernel, IJNBNotebookSerializer } from "./impl"; + +export const registerNotebooks = (context: ExtensionContext) => { + context.subscriptions.push( + workspace.registerNotebookSerializer( + 'ijnb-notebook', + new IJNBNotebookSerializer()), + new IJNBKernel() + ); +} \ No newline at end of file From 802a89536790da9df59cbadfe4a749d534693b94 Mon Sep 17 00:00:00 2001 From: Shivam Madan Date: Mon, 23 Jun 2025 17:28:01 +0530 Subject: [PATCH 2/9] Changes to support notebook configurations and handle the async tasks --- .../notebook/NotebookCommandsHandler.java | 2 +- .../nbcode/java/notebook/NotebookConfigs.java | 131 ++++++++++++++---- .../NotebookDocumentServiceHandlerImpl.java | 5 +- .../java/notebook/NotebookSessionManager.java | 53 ++++--- vscode/package.json | 36 ++++- vscode/src/configurations/configuration.ts | 12 +- vscode/src/configurations/handlers.ts | 3 + 7 files changed, 191 insertions(+), 51 deletions(-) diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookCommandsHandler.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookCommandsHandler.java index 06f21f8..dd9a965 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookCommandsHandler.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookCommandsHandler.java @@ -45,7 +45,7 @@ public CompletableFuture runCommand(String command, List argumen switch (command) { case NBLS_JSHELL_EXEC: - return CompletableFuture.completedFuture(CodeEval.evaluate(arguments)); + return new CompletableFuture<>().completeAsync(()->CodeEval.evaluate(arguments)); default: return CompletableFuture.failedFuture(new UnsupportedOperationException("Command not supported: " + command)); } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java index 15c071c..5499ffe 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java @@ -15,25 +15,64 @@ */ package org.netbeans.modules.nbcode.java.notebook; +import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; +import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import org.eclipse.lsp4j.ConfigurationItem; import org.eclipse.lsp4j.ConfigurationParams; import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient; - +import org.openide.util.Exceptions; +import java.lang.Void; /** * * @author atalati */ public class NotebookConfigs { - private static final String NOTEBOOK_JDK_HOME = "notebook.jdkhome"; - private String jdkVersion = null; +// private static final String NOTEBOOK_JDK_HOME = "notebook.jdkhome"; +// private String jdkVersion = null; + + private static final String[] NOTEBOOK_CONFIG_LABELS={"notebook.classpath","notebook.modulepath","notebook.addmodules","notebook.enablePreview","notebook.implicitImports"}; private NbCodeLanguageClient client = null; + private String classPath = null; + private String modulePath = null; + private String addModules = null; + private boolean enablePreview = false; + private List implicitImports = null; + private CompletableFuture initialized; + + public CompletableFuture getInitialized() { + return initialized; + } + + + public String getClassPath() { + return classPath; + } + + public String getModulePath() { + return modulePath; + } + + public String getAddModules() { + return addModules; + } + + public boolean isEnablePreview() { + return enablePreview; + } + + public List getImplicitImports() { + return implicitImports; + } + private NotebookConfigs() { + } public static NotebookConfigs getInstance() { @@ -47,36 +86,80 @@ private static class Singleton { public void setLanguageClient(NbCodeLanguageClient client) { this.client = client; + + try { + this.initialized = initializeConfigs(); + } catch (InterruptedException | ExecutionException ex) { + Exceptions.printStackTrace(ex); + } } public NbCodeLanguageClient getLanguageClient() { return client; } - - public String getJdkVersion() throws InterruptedException, ExecutionException { - // Figure out how to get Java major version from the path to jdk home - // Option-1: Run java --version in a different process and get it's output. - // Option-2: Check release file in the home path it might have info about major version. - String notebookJdkVersion = jdkVersion != null ? jdkVersion : null; - if (notebookJdkVersion != null && client != null) { - if (client != null) { - ConfigurationItem configItem = new ConfigurationItem(); - configItem.setSection(client.getNbCodeCapabilities().getConfigurationPrefix() + NOTEBOOK_JDK_HOME); - notebookJdkVersion = client.configuration(new ConfigurationParams(List.of(configItem))).thenApply((c) -> { - if (c != null) { - return ((JsonPrimitive) c.get(0)).getAsString(); - } - return null; - }).get(); - if (notebookJdkVersion != null) { - this.jdkVersion = notebookJdkVersion; - } - } + private List getConfigItems(){ + List items = new ArrayList<>(); + for(String label:NOTEBOOK_CONFIG_LABELS){ + ConfigurationItem item = new ConfigurationItem(); + item.setSection(client.getNbCodeCapabilities().getConfigurationPrefix() + label); + items.add(item); } - return notebookJdkVersion; + return items; + } + private CompletableFuture initializeConfigs() throws InterruptedException, ExecutionException{ + if (client != null) { + + CompletableFuture> configValues = client.configuration(new ConfigurationParams(getConfigItems())); + return configValues.thenAccept((c)->{ + if(c!=null){ + if(c.get(0)!=null){ + classPath = ((JsonPrimitive) c.get(0)).getAsString(); + } + if(c.get(1)!=null){ + modulePath = ((JsonPrimitive) c.get(1)).getAsString(); + } + if(c.get(2)!=null){ + addModules = ((JsonPrimitive) c.get(2)).getAsString(); + } + if(c.get(3)!=null){ + enablePreview = ((JsonPrimitive) c.get(3)).getAsBoolean(); + } + if(c.get(4)!=null){ + implicitImports = ((JsonArray) c.get(4)).asList().stream().map((elem)->elem.getAsString()).toList(); + + } + } + }); + + } + return CompletableFuture.completedFuture(null); + } +// public String getJdkVersion() throws InterruptedException, ExecutionException { +// // Figure out how to get Java major version from the path to jdk home +// // Option-1: Run java --version in a different process and get it's output. +// // Option-2: Check release file in the home path it might have info about major version. +// String notebookJdkVersion = jdkVersion != null ? jdkVersion : null; +// if (notebookJdkVersion != null && client != null) { +// if (client != null) { +// ConfigurationItem configItem = new ConfigurationItem(); +// configItem.setSection(client.getNbCodeCapabilities().getConfigurationPrefix() + NOTEBOOK_JDK_HOME); +// notebookJdkVersion = client.configuration(new ConfigurationParams(List.of(configItem))).thenApply((c) -> { +// if (c != null) { +// return ((JsonPrimitive) c.get(0)).getAsString(); +// } +// return null; +// }).get(); +// if (notebookJdkVersion != null) { +// this.jdkVersion = notebookJdkVersion; +// } +// } +// } +// return notebookJdkVersion; +// } public void notebookConfigsChangeListener(JsonObject settings) { // depends on #8514 PR open in Netbeans + } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java index cb15e45..85e9590 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java @@ -82,7 +82,10 @@ public class NotebookDocumentServiceHandlerImpl implements NotebookDocumentServi @Override public void didOpen(DidOpenNotebookDocumentParams params) { try { - NotebookSessionManager.getInstance().createSession(params.getNotebookDocument()); + new CompletableFuture().completeAsync(()->{ + NotebookSessionManager.getInstance().createSession(params.getNotebookDocument()); + return null; + }); NotebookDocumentStateManager state = new NotebookDocumentStateManager(params.getNotebookDocument(), params.getCellTextDocuments()); params.getNotebookDocument().getCells().forEach(cell -> { notebookCellMap.put(cell.getDocument(), params.getNotebookDocument().getUri()); diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java index 33b61b9..8aedfeb 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java @@ -51,29 +51,40 @@ private static class Singleton { private static final NotebookSessionManager instance = new NotebookSessionManager(); } - + private static boolean checkEmptyString(String input){ + return (input==null || input.trim().isEmpty()); + } private JShell jshellBuilder(PrintStream outPrintStream, PrintStream errPrintStream) throws InterruptedException, ExecutionException { List compilerOptions = new ArrayList<>(); List remoteOptions = new ArrayList<>(); - - boolean isEnablePreview = true; - if (isEnablePreview) { - compilerOptions.add(ENABLE_PREVIEW); - } - - String notebookJdkVersion = NotebookConfigs.getInstance().getJdkVersion(); - if (notebookJdkVersion == null) { - notebookJdkVersion = System.getProperty("java.version").split("\\.")[0]; - } - - compilerOptions.addAll(List.of(SOURCE_FLAG, notebookJdkVersion)); - - return JShell.builder() + NotebookConfigs.getInstance().getInitialized().get();// wait till intialized + String notebookJdkVersion = System.getProperty("java.version").split("\\.")[0]; + String classpath = NotebookConfigs.getInstance().getClassPath(); + if(!checkEmptyString(classpath)){compilerOptions.add("--class-path");compilerOptions.add(classpath);remoteOptions.add("--class-path");remoteOptions.add(classpath);} + String modulePath = NotebookConfigs.getInstance().getModulePath(); + if(!checkEmptyString(modulePath)){compilerOptions.add("--module-path");compilerOptions.add(modulePath);remoteOptions.add("--module-path");remoteOptions.add(modulePath);} + String addModules = NotebookConfigs.getInstance().getAddModules(); + if(!checkEmptyString(addModules)){compilerOptions.add("--add-modules");compilerOptions.add(addModules);remoteOptions.add("--add-modules");remoteOptions.add(addModules);} + boolean isEnablePreview = NotebookConfigs.getInstance().isEnablePreview(); + if(isEnablePreview){compilerOptions.add("--enable-preview");compilerOptions.add("--source");compilerOptions.add(notebookJdkVersion);remoteOptions.add("--enable-preview");} + if(compilerOptions.isEmpty()){ + return JShell.builder() .out(outPrintStream) .err(errPrintStream) - .compilerOptions(compilerOptions.toArray(new String[0])) - .remoteVMOptions(remoteOptions.toArray(new String[0])) + .compilerOptions() + .remoteVMOptions() .build(); + }else{ + return JShell.builder() + .out(outPrintStream) + .err(errPrintStream) + .compilerOptions(compilerOptions.toArray(new String[compilerOptions.size()])) + .remoteVMOptions(remoteOptions.toArray(new String[remoteOptions.size()])) + .build(); + + } + + } public void createSession(NotebookDocument notebookDoc) { @@ -94,8 +105,14 @@ public void createSession(NotebookDocument notebookDoc) { boolean implicitImports = true; if (implicitImports) { - List.of("java.util", "java.io", "java.math") + List packages = NotebookConfigs.getInstance().getImplicitImports(); + if(packages!=null && !packages.isEmpty()){ + packages.forEach(pkg -> CodeEval.runCode(jshell, "import " + pkg)); + }else{ + List.of("java.util", "java.io", "java.math") .forEach(pkg -> CodeEval.runCode(jshell, "import " + pkg + ".*")); + } + } return jshell; diff --git a/vscode/package.json b/vscode/package.json index d07e18f..7213443 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -254,6 +254,35 @@ "default": 10000, "description": "%jdk.debugger.configuration.completion.warning.time.description%" }, + "jdk.notebook.classpath": { + "type": "string", + "default": "", + "description": "Classpath that needs to be set for notebooks" + }, + "jdk.notebook.modulepath": { + "type": "string", + "default": "", + "description": "Modulepath that needs to be set for notebooks" + }, + "jdk.notebook.addmodules": { + "type": "string", + "default": "", + "description": "Modules needs for notebooks" + }, + "jdk.notebook.enablePreview": { + "type": "boolean", + "default": false, + "description": "Flag to enable jdk preview features for notebooks" + }, + "jdk.notebook.implicitImports": { + "type": "array", + "default": [ + "java.util.*", + "java.io.*", + "java.math.*" + ], + "description": "list of packages to import implicitly." + }, "jdk.telemetry.enabled": { "type": "boolean", "description": "%jdk.configuration.telemetry.enabled.description%", @@ -285,11 +314,6 @@ "%jdk.configuration.inlay.enabled.enum.var.markdownDescription%" ] } - }, - "jdk.notebook.classpath": { - "type": "string", - "default": "", - "description": "Classpath that needs to be set for notebooks" } } }, @@ -881,4 +905,4 @@ "brace-expansion": "^2.0.2" } } -} \ No newline at end of file +} diff --git a/vscode/src/configurations/configuration.ts b/vscode/src/configurations/configuration.ts index 66d42b5..66885f3 100644 --- a/vscode/src/configurations/configuration.ts +++ b/vscode/src/configurations/configuration.ts @@ -31,8 +31,13 @@ export const configKeys = { verbose: 'verbose', userdir: 'userdir', revealInActivteProj: "revealActiveInProjects", + notebookClasspath: "notebook.classpath", + notebookModulepath: "notebook.modulepath", + notebookAddModules: "notebook.addmodules", + notebookEnablePreview: "notebook.enablePreview", + notebookImplicitImports: "notebook.implicitImports", telemetryEnabled: 'telemetry.enabled', - notebookClasspath: "notebook.classpath" + }; export const builtInConfigKeys = { @@ -45,6 +50,11 @@ export const userConfigsListened: string[] = [ appendPrefixToCommand(configKeys.lspVmOptions), appendPrefixToCommand(configKeys.disableNbJavac), appendPrefixToCommand(configKeys.disableProjSearchLimit), + appendPrefixToCommand(configKeys.notebookClasspath), + appendPrefixToCommand(configKeys.notebookModulepath), + appendPrefixToCommand(configKeys.notebookAddModules), + appendPrefixToCommand(configKeys.notebookEnablePreview), + appendPrefixToCommand(configKeys.notebookImplicitImports), builtInConfigKeys.vscodeTheme, ]; diff --git a/vscode/src/configurations/handlers.ts b/vscode/src/configurations/handlers.ts index 45e4f5e..f77b1e3 100644 --- a/vscode/src/configurations/handlers.ts +++ b/vscode/src/configurations/handlers.ts @@ -156,4 +156,7 @@ export const isNbJavacDisabledHandler = (): boolean => { export const isNetbeansVerboseEnabled = (): boolean => { return getConfigurationValue(configKeys.verbose, false); +} +export const isEnablePreview = (): boolean =>{ + return getConfigurationValue(configKeys.notebookEnablePreview,false); } \ No newline at end of file From 513179f7b7047742741a4272a8919c0e84866cf9 Mon Sep 17 00:00:00 2001 From: Achal Talati Date: Tue, 24 Jun 2025 12:36:40 +0530 Subject: [PATCH 3/9] Added opening JShell in project context functionality Uses RequestProcessor for async Jshell init and code eval --- nbcode/notebooks/nbproject/project.xml | 26 ++ .../java/notebook/CodeCompletionProvider.java | 8 +- .../nbcode/java/notebook/CodeEval.java | 47 ++- .../notebook/NotebookCommandsHandler.java | 9 +- .../nbcode/java/notebook/NotebookConfigs.java | 99 +++---- .../NotebookDocumentServiceHandlerImpl.java | 7 +- .../NotebookDocumentStateManager.java | 4 - .../java/notebook/NotebookSessionManager.java | 198 +++++++++---- .../nbcode/java/notebook/NotebookUtils.java | 4 + .../nbcode/java/project/CommandHandler.java | 111 +++++++ .../project/ProjectConfigurationUtils.java | 198 +++++++++++++ .../ProjectModulePathConfigurationUtils.java | 277 ++++++++++++++++++ vscode/package.json | 12 +- vscode/src/commands/commands.ts | 5 +- vscode/src/commands/create.ts | 73 +---- vscode/src/commands/notebook.ts | 156 ++++++++++ vscode/src/commands/register.ts | 4 +- vscode/src/constants.ts | 1 + vscode/src/notebooks/impl.ts | 13 - 19 files changed, 1013 insertions(+), 239 deletions(-) create mode 100644 nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/CommandHandler.java create mode 100644 nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectConfigurationUtils.java create mode 100644 nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectModulePathConfigurationUtils.java create mode 100644 vscode/src/commands/notebook.ts diff --git a/nbcode/notebooks/nbproject/project.xml b/nbcode/notebooks/nbproject/project.xml index 04ac8d8..60b1272 100644 --- a/nbcode/notebooks/nbproject/project.xml +++ b/nbcode/notebooks/nbproject/project.xml @@ -29,6 +29,24 @@ 2.11.0 + + org.netbeans.api.java + + + + 1 + 1.94 + + + + org.netbeans.api.java.classpath + + + + 1 + 1.81 + + org.netbeans.api.lsp @@ -115,6 +133,14 @@ 9.38 + + org.openide.modules + + + + 7.75 + + org.openide.util diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeCompletionProvider.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeCompletionProvider.java index f794820..600159e 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeCompletionProvider.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeCompletionProvider.java @@ -20,6 +20,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import java.util.logging.Logger; import jdk.jshell.JShell; import jdk.jshell.SourceCodeAnalysis; import org.eclipse.lsp4j.CompletionItem; @@ -33,6 +35,7 @@ * @author atalati */ public class CodeCompletionProvider { + private static final Logger LOG = Logger.getLogger(CodeCompletionProvider.class.getName()); private CodeCompletionProvider() { } @@ -48,6 +51,9 @@ private static class Singleton { public CompletableFuture, CompletionList>> getCodeCompletions(CompletionParams params, NotebookDocumentStateManager state, JShell instance) { try { + if (instance == null || state == null) { + return CompletableFuture.completedFuture(Either., CompletionList>forLeft(new ArrayList<>())); + } String uri = params.getTextDocument().getUri(); CellState cellState = state.getCell(uri); String content = cellState.getContent(); @@ -89,7 +95,7 @@ public CompletableFuture, CompletionList>> getCodeCo return CompletableFuture.completedFuture(Either., CompletionList>forLeft(completionItems)); } catch (Exception e) { - System.err.println("Error getting code completions: " + e.getMessage()); + LOG.log(Level.WARNING, "Error getting code completions: {0}", e.getMessage()); return CompletableFuture.completedFuture(Either., CompletionList>forLeft(new ArrayList<>())); } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeEval.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeEval.java index da72165..43de88b 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeEval.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeEval.java @@ -21,9 +21,12 @@ import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; import jdk.jshell.SourceCodeAnalysis; +import org.openide.util.RequestProcessor; /** * @@ -32,34 +35,52 @@ public class CodeEval { private static final Logger LOG = Logger.getLogger(CodeEval.class.getName()); + private static final RequestProcessor CODE_EXECUTOR = new RequestProcessor("Jshell Code Evaluator", 1, true, true); - public static List evaluate(List arguments) { + public static CompletableFuture> evaluate(List arguments) { if (arguments != null) { - String sourceCode = null, notebookId = null; + AtomicReference sourceCode = new AtomicReference<>(null); + AtomicReference notebookId = new AtomicReference<>(null); + if (arguments.get(0) != null && arguments.get(0) instanceof JsonPrimitive) { - sourceCode = ((JsonPrimitive) arguments.get(0)).getAsString(); + sourceCode.set(((JsonPrimitive) arguments.get(0)).getAsString()); } if (arguments.size() > 1 && arguments.get(1) != null && arguments.get(1) instanceof JsonPrimitive) { - notebookId = ((JsonPrimitive) arguments.get(1)).getAsString(); + notebookId.set(((JsonPrimitive) arguments.get(1)).getAsString()); } - if (sourceCode != null && notebookId != null) { - JShell jshell = NotebookSessionManager.getInstance().getSession(notebookId); - ByteArrayOutputStream outputStream = NotebookSessionManager.getInstance().getOutputStreamById(notebookId); - ByteArrayOutputStream errorStream = NotebookSessionManager.getInstance().getErrorStreamById(notebookId); + if (sourceCode.get() != null && notebookId.get() != null) { + CompletableFuture future = NotebookSessionManager.getInstance().getSessionFuture(notebookId.get()); - if (jshell == null) { - throw new ExceptionInInitializerError("Error creating session for notebook"); - } + return future.thenCompose(jshell -> { + CompletableFuture> resultFuture = new CompletableFuture<>(); + + CODE_EXECUTOR.submit(() -> { + try { + ByteArrayOutputStream outputStream = NotebookSessionManager.getInstance().getOutputStreamById(notebookId.get()); + ByteArrayOutputStream errorStream = NotebookSessionManager.getInstance().getErrorStreamById(notebookId.get()); - return runCode(jshell, sourceCode, outputStream, errorStream, true); + if (jshell == null) { + resultFuture.completeExceptionally(new ExceptionInInitializerError("Error creating session for notebook")); + return; + } + + List results = runCode(jshell, sourceCode.get(), outputStream, errorStream, true); + resultFuture.complete(results); + } catch (Exception e) { + resultFuture.completeExceptionally(e); + } + }); + + return resultFuture; + }); } LOG.warning("sourceCode or notebookId are not present in code cell evaluation request"); } else { LOG.warning("Empty arguments recevied in code cell evaluate request"); } - return new ArrayList<>(); + return CompletableFuture.completedFuture(new ArrayList<>()); } public static List runCode(JShell jshell, String code) { diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookCommandsHandler.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookCommandsHandler.java index dd9a965..d36fcf4 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookCommandsHandler.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookCommandsHandler.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; +import org.netbeans.modules.nbcode.java.project.CommandHandler; import org.netbeans.spi.lsp.CommandProvider; import org.openide.util.lookup.ServiceProvider; @@ -31,8 +32,8 @@ public class NotebookCommandsHandler implements CommandProvider { private static final String NBLS_JSHELL_EXEC = "nbls.jshell.execute.cell"; - private static final String NBLS_JSHELL_CLOSE = "nbls.jshell.cleanup"; - private static final Set COMMANDS = new HashSet<>(Arrays.asList(NBLS_JSHELL_EXEC, NBLS_JSHELL_CLOSE)); + private static final String NBLS_OPEN_PROJECT_JSHELL = "nbls.jshell.project.open"; + private static final Set COMMANDS = new HashSet<>(Arrays.asList(NBLS_JSHELL_EXEC, NBLS_OPEN_PROJECT_JSHELL)); @Override public Set getCommands() { @@ -45,7 +46,9 @@ public CompletableFuture runCommand(String command, List argumen switch (command) { case NBLS_JSHELL_EXEC: - return new CompletableFuture<>().completeAsync(()->CodeEval.evaluate(arguments)); + return CodeEval.evaluate(arguments).thenApply(list -> (Object)list); + case NBLS_OPEN_PROJECT_JSHELL: + return CommandHandler.openJshellInProjectContext(arguments).thenApply(list -> (Object) list); default: return CompletableFuture.failedFuture(new UnsupportedOperationException("Command not supported: " + command)); } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java index 5499ffe..9dd54c9 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java @@ -26,17 +26,14 @@ import org.eclipse.lsp4j.ConfigurationParams; import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient; import org.openide.util.Exceptions; -import java.lang.Void; + /** * * @author atalati */ public class NotebookConfigs { -// private static final String NOTEBOOK_JDK_HOME = "notebook.jdkhome"; -// private String jdkVersion = null; - - private static final String[] NOTEBOOK_CONFIG_LABELS={"notebook.classpath","notebook.modulepath","notebook.addmodules","notebook.enablePreview","notebook.implicitImports"}; + private static final String[] NOTEBOOK_CONFIG_LABELS = {"notebook.classpath", "notebook.modulepath", "notebook.addmodules", "notebook.enablePreview", "notebook.implicitImports"}; private NbCodeLanguageClient client = null; private String classPath = null; private String modulePath = null; @@ -48,7 +45,6 @@ public class NotebookConfigs { public CompletableFuture getInitialized() { return initialized; } - public String getClassPath() { return classPath; @@ -69,10 +65,9 @@ public boolean isEnablePreview() { public List getImplicitImports() { return implicitImports; } - private NotebookConfigs() { - + } public static NotebookConfigs getInstance() { @@ -86,7 +81,7 @@ private static class Singleton { public void setLanguageClient(NbCodeLanguageClient client) { this.client = client; - + try { this.initialized = initializeConfigs(); } catch (InterruptedException | ExecutionException ex) { @@ -97,69 +92,53 @@ public void setLanguageClient(NbCodeLanguageClient client) { public NbCodeLanguageClient getLanguageClient() { return client; } - private List getConfigItems(){ + + private List getConfigItems() { List items = new ArrayList<>(); - for(String label:NOTEBOOK_CONFIG_LABELS){ + for (String label : NOTEBOOK_CONFIG_LABELS) { ConfigurationItem item = new ConfigurationItem(); item.setSection(client.getNbCodeCapabilities().getConfigurationPrefix() + label); items.add(item); } return items; } - private CompletableFuture initializeConfigs() throws InterruptedException, ExecutionException{ + + private CompletableFuture initializeConfigs() throws InterruptedException, ExecutionException { if (client != null) { - - CompletableFuture> configValues = client.configuration(new ConfigurationParams(getConfigItems())); - return configValues.thenAccept((c)->{ - if(c!=null){ - if(c.get(0)!=null){ - classPath = ((JsonPrimitive) c.get(0)).getAsString(); - } - if(c.get(1)!=null){ - modulePath = ((JsonPrimitive) c.get(1)).getAsString(); - } - if(c.get(2)!=null){ - addModules = ((JsonPrimitive) c.get(2)).getAsString(); - } - if(c.get(3)!=null){ - enablePreview = ((JsonPrimitive) c.get(3)).getAsBoolean(); - } - if(c.get(4)!=null){ - implicitImports = ((JsonArray) c.get(4)).asList().stream().map((elem)->elem.getAsString()).toList(); - - } - } - }); - - } + + CompletableFuture> configValues = client.configuration(new ConfigurationParams(getConfigItems())); + return configValues.thenAccept((c) -> { + if (c != null) { + if (c.get(0) != null) { + classPath = ((JsonPrimitive) c.get(0)).getAsString(); + } + if (c.get(1) != null) { + modulePath = ((JsonPrimitive) c.get(1)).getAsString(); + } + if (c.get(2) != null) { + addModules = ((JsonPrimitive) c.get(2)).getAsString(); + } + if (c.get(3) != null) { + enablePreview = ((JsonPrimitive) c.get(3)).getAsBoolean(); + } + if (c.get(4) != null) { + implicitImports = ((JsonArray) c.get(4)).asList().stream().map((elem) -> elem.getAsString()).toList(); + + } + } + }); + + } return CompletableFuture.completedFuture(null); - + + } + + public String getJdkVersion() { + return System.getProperty("java.version").split("\\.")[0]; } -// public String getJdkVersion() throws InterruptedException, ExecutionException { -// // Figure out how to get Java major version from the path to jdk home -// // Option-1: Run java --version in a different process and get it's output. -// // Option-2: Check release file in the home path it might have info about major version. -// String notebookJdkVersion = jdkVersion != null ? jdkVersion : null; -// if (notebookJdkVersion != null && client != null) { -// if (client != null) { -// ConfigurationItem configItem = new ConfigurationItem(); -// configItem.setSection(client.getNbCodeCapabilities().getConfigurationPrefix() + NOTEBOOK_JDK_HOME); -// notebookJdkVersion = client.configuration(new ConfigurationParams(List.of(configItem))).thenApply((c) -> { -// if (c != null) { -// return ((JsonPrimitive) c.get(0)).getAsString(); -// } -// return null; -// }).get(); -// if (notebookJdkVersion != null) { -// this.jdkVersion = notebookJdkVersion; -// } -// } -// } -// return notebookJdkVersion; -// } public void notebookConfigsChangeListener(JsonObject settings) { // depends on #8514 PR open in Netbeans - + } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java index 85e9590..2672fa1 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java @@ -82,17 +82,14 @@ public class NotebookDocumentServiceHandlerImpl implements NotebookDocumentServi @Override public void didOpen(DidOpenNotebookDocumentParams params) { try { - new CompletableFuture().completeAsync(()->{ - NotebookSessionManager.getInstance().createSession(params.getNotebookDocument()); - return null; - }); + CompletableFuture.runAsync(() -> NotebookSessionManager.getInstance().createSession(params.getNotebookDocument())); + NotebookDocumentStateManager state = new NotebookDocumentStateManager(params.getNotebookDocument(), params.getCellTextDocuments()); params.getNotebookDocument().getCells().forEach(cell -> { notebookCellMap.put(cell.getDocument(), params.getNotebookDocument().getUri()); }); notebookStateMap.put(params.getNotebookDocument().getUri(), state); - } catch (Exception e) { LOG.log(Level.SEVERE, "Error while opening notebook {0}", e.getMessage()); } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java index 5fb6034..d877be9 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java @@ -18,9 +18,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; import org.eclipse.lsp4j.NotebookCell; @@ -33,8 +31,6 @@ import org.eclipse.lsp4j.TextDocumentContentChangeEvent; import org.eclipse.lsp4j.TextDocumentItem; import org.eclipse.lsp4j.VersionedNotebookDocumentIdentifier; -import org.netbeans.modules.java.lsp.server.notebook.NotebookCellContentRequestParams; -import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient; /** * diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java index 8aedfeb..64f719d 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java @@ -20,12 +20,15 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; import jdk.jshell.JShell; import org.eclipse.lsp4j.NotebookDocument; +import static org.netbeans.modules.nbcode.java.notebook.NotebookUtils.checkEmptyString; /** * @@ -33,12 +36,16 @@ */ public class NotebookSessionManager { - private static final Logger LOGGER = Logger.getLogger(NotebookSessionManager.class.getName()); - private final Map sessions = new ConcurrentHashMap<>(); - private final Map outputStreams = new ConcurrentHashMap<>(); - private final Map errorStreams = new ConcurrentHashMap<>(); + private static final Logger LOG = Logger.getLogger(NotebookSessionManager.class.getName()); private static final String SOURCE_FLAG = "--source"; private static final String ENABLE_PREVIEW = "--enable-preview"; + private static final String CLASS_PATH = "--enable-preview"; + private static final String MODULE_PATH = "--enable-preview"; + private static final String ADD_MODULES = "--enable-preview"; + + private final Map> sessions = new ConcurrentHashMap<>(); + private final Map outputStreams = new ConcurrentHashMap<>(); + private final Map errorStreams = new ConcurrentHashMap<>(); private NotebookSessionManager() { } @@ -51,82 +58,144 @@ private static class Singleton { private static final NotebookSessionManager instance = new NotebookSessionManager(); } - private static boolean checkEmptyString(String input){ - return (input==null || input.trim().isEmpty()); + + private CompletableFuture jshellBuilder(PrintStream outPrintStream, PrintStream errPrintStream) { + return CompletableFuture.supplyAsync(() -> { + List compilerOptions = getCompilerOptions(); + List remoteOptions = getRemoteVmOptions(); + try { + NotebookConfigs.getInstance().getInitialized().get(); + } catch (InterruptedException ex) { + LOG.log(Level.WARNING, "InterruptedException occurred while getting notebook configs: {0}", ex.getMessage()); + } catch (ExecutionException ex) { + LOG.log(Level.WARNING, "ExecutionException occurred while getting notebook configs: {0}", ex.getMessage()); + } + + if (compilerOptions.isEmpty()) { + return JShell.builder() + .out(outPrintStream) + .err(errPrintStream) + .compilerOptions() + .remoteVMOptions() + .build(); + } else { + return JShell.builder() + .out(outPrintStream) + .err(errPrintStream) + .compilerOptions(compilerOptions.toArray(new String[0])) + .remoteVMOptions(remoteOptions.toArray(new String[0])) + .build(); + } + }); } - private JShell jshellBuilder(PrintStream outPrintStream, PrintStream errPrintStream) throws InterruptedException, ExecutionException { + + public void createSession(NotebookDocument notebookDoc) { + String notebookId = notebookDoc.getUri(); + + sessions.computeIfAbsent(notebookId, (String id) -> { + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + ByteArrayOutputStream errStream = new ByteArrayOutputStream(); + outputStreams.put(notebookId, outStream); + errorStreams.put(notebookId, errStream); + + PrintStream outPrintStream = new PrintStream(outStream, true); + PrintStream errPrintStream = new PrintStream(errStream, true); + CompletableFuture future = jshellBuilder(outPrintStream, errPrintStream); + + future.thenAccept(jshell -> onJshellInit(notebookId, jshell)) + .exceptionally(ex -> { + LOG.log(Level.SEVERE, "Error creating notebook session: {0}", ex.getMessage()); + throw new IllegalStateException("Error while creating notebook session"); + }); + + return future; + }); + } + + private List getCompilerOptions() { List compilerOptions = new ArrayList<>(); - List remoteOptions = new ArrayList<>(); - NotebookConfigs.getInstance().getInitialized().get();// wait till intialized - String notebookJdkVersion = System.getProperty("java.version").split("\\.")[0]; String classpath = NotebookConfigs.getInstance().getClassPath(); - if(!checkEmptyString(classpath)){compilerOptions.add("--class-path");compilerOptions.add(classpath);remoteOptions.add("--class-path");remoteOptions.add(classpath);} String modulePath = NotebookConfigs.getInstance().getModulePath(); - if(!checkEmptyString(modulePath)){compilerOptions.add("--module-path");compilerOptions.add(modulePath);remoteOptions.add("--module-path");remoteOptions.add(modulePath);} String addModules = NotebookConfigs.getInstance().getAddModules(); - if(!checkEmptyString(addModules)){compilerOptions.add("--add-modules");compilerOptions.add(addModules);remoteOptions.add("--add-modules");remoteOptions.add(addModules);} boolean isEnablePreview = NotebookConfigs.getInstance().isEnablePreview(); - if(isEnablePreview){compilerOptions.add("--enable-preview");compilerOptions.add("--source");compilerOptions.add(notebookJdkVersion);remoteOptions.add("--enable-preview");} - if(compilerOptions.isEmpty()){ - return JShell.builder() - .out(outPrintStream) - .err(errPrintStream) - .compilerOptions() - .remoteVMOptions() - .build(); - }else{ - return JShell.builder() - .out(outPrintStream) - .err(errPrintStream) - .compilerOptions(compilerOptions.toArray(new String[compilerOptions.size()])) - .remoteVMOptions(remoteOptions.toArray(new String[remoteOptions.size()])) - .build(); - + String notebookJdkVersion = NotebookConfigs.getInstance().getJdkVersion(); + + if (!checkEmptyString(classpath)) { + compilerOptions.add(CLASS_PATH); + compilerOptions.add(classpath); + } + if (!checkEmptyString(modulePath)) { + compilerOptions.add(MODULE_PATH); + compilerOptions.add(modulePath); + } + if (!checkEmptyString(addModules)) { + compilerOptions.add(ADD_MODULES); + compilerOptions.add(addModules); } + if (isEnablePreview) { + compilerOptions.add(ENABLE_PREVIEW); + compilerOptions.add(SOURCE_FLAG); + compilerOptions.add(notebookJdkVersion); + } + + return compilerOptions; + } + private List getRemoteVmOptions() { + List remoteOptions = new ArrayList<>(); + String classpath = NotebookConfigs.getInstance().getClassPath(); + String modulePath = NotebookConfigs.getInstance().getModulePath(); + String addModules = NotebookConfigs.getInstance().getAddModules(); + boolean isEnablePreview = NotebookConfigs.getInstance().isEnablePreview(); + + if (!checkEmptyString(classpath)) { + remoteOptions.add(CLASS_PATH); + remoteOptions.add(classpath); + } + if (!checkEmptyString(modulePath)) { + remoteOptions.add(MODULE_PATH); + remoteOptions.add(modulePath); + } + if (!checkEmptyString(addModules)) { + remoteOptions.add(ADD_MODULES); + remoteOptions.add(addModules); + } + if (isEnablePreview) { + remoteOptions.add(ENABLE_PREVIEW); + } + return remoteOptions; } - public void createSession(NotebookDocument notebookDoc) { - String notebookId = notebookDoc.getUri(); + private void onJshellInit(String notebookId, JShell jshell) { + jshell.onShutdown(shell -> closeSession(notebookId)); - sessions.computeIfAbsent(notebookId, (String id) -> { - try { - ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - ByteArrayOutputStream errStream = new ByteArrayOutputStream(); - outputStreams.put(id, outStream); - errorStreams.put(id, errStream); - - PrintStream outPrintStream = new PrintStream(outStream, true); - PrintStream errPrintStream = new PrintStream(errStream, true); - - JShell jshell = jshellBuilder(outPrintStream, errPrintStream); - jshell.onShutdown(shell -> closeSession(notebookId)); - - boolean implicitImports = true; - if (implicitImports) { - List packages = NotebookConfigs.getInstance().getImplicitImports(); - if(packages!=null && !packages.isEmpty()){ - packages.forEach(pkg -> CodeEval.runCode(jshell, "import " + pkg)); - }else{ - List.of("java.util", "java.io", "java.math") - .forEach(pkg -> CodeEval.runCode(jshell, "import " + pkg + ".*")); - } - - } - - return jshell; - } catch (IllegalStateException | InterruptedException | ExecutionException e) { - LOGGER.log(Level.SEVERE, "Error creating notebook session: {0}", e.getMessage()); - throw new IllegalStateException("Error while creating notebook session"); - } - }); + List packages = NotebookConfigs.getInstance().getImplicitImports(); + if (packages != null && !packages.isEmpty()) { + packages.forEach(pkg -> CodeEval.runCode(jshell, "import " + pkg)); + } else { + List.of("java.util", "java.io", "java.math") + .forEach(pkg -> CodeEval.runCode(jshell, "import " + pkg + ".*")); + } } - public JShell getSession(String notebookId) { + public CompletableFuture getSessionFuture(String notebookId) { return sessions.get(notebookId); } + public JShell getSession(String notebookId) { + try { + CompletableFuture future = sessions.get(notebookId); + if (future == null) { + return null; + } + return future.get(); + } catch (InterruptedException | ExecutionException | CancellationException ex) { + LOG.log(Level.WARNING, "Error while fetching session for {0}", notebookId); + } + return null; + } + public ByteArrayOutputStream getOutputStreamById(String notebookId) { return outputStreams.get(notebookId); } @@ -136,7 +205,8 @@ public ByteArrayOutputStream getErrorStreamById(String notebookId) { } public void closeSession(String notebookUri) { - JShell jshell = sessions.remove(notebookUri); + CompletableFuture future = sessions.remove(notebookUri); + JShell jshell = future.getNow(null); if (jshell != null) { jshell.close(); } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtils.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtils.java index 5f41140..bda0a6b 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtils.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtils.java @@ -93,4 +93,8 @@ public static Position getPosition(String content, int offset) { return new Position(line, character); } + + public static boolean checkEmptyString(String input) { + return (input == null || input.trim().isEmpty()); + } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/CommandHandler.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/CommandHandler.java new file mode 100644 index 0000000..07b5560 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/CommandHandler.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed 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 + * + * https://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.netbeans.modules.nbcode.java.project; + +import com.google.gson.JsonPrimitive; +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.netbeans.api.project.FileOwnerQuery; +import org.netbeans.api.project.Project; +import org.netbeans.modules.java.lsp.server.Utils; +import org.openide.filesystems.FileObject; +import org.openide.util.Exceptions; + +/** + * + * @author atalati + */ +public class CommandHandler { + + private static final Logger LOG = Logger.getLogger(CommandHandler.class.getName()); + + public static CompletableFuture> openJshellInProjectContext(List args) { + CompletableFuture> future = new CompletableFuture<>(); + + LOG.log(Level.FINER, "Request received for opening Jshell instance with project context {0}", args); + + final String uri = args != null && args.get(0) != null && args.get(0) instanceof JsonPrimitive + ? ((JsonPrimitive) args.get(0)).getAsString() + : null; + + if (uri == null) { + future.completeExceptionally(new IllegalArgumentException("uri is required. It cannot be null")); + return future; + } + + Project prj = getProject(uri); + if (prj != null) { + return ProjectConfigurationUtils.buildProject(prj) + .thenCompose(isBuildSuccess -> { + if (Boolean.TRUE.equals(isBuildSuccess)) { + List vmOptions = ProjectConfigurationUtils.launchVMOptions(prj); + LOG.log(Level.INFO, "Opened Jshell instance with project context {0}", uri); + return CompletableFuture.completedFuture(vmOptions); + } else { + CompletableFuture> failed = new CompletableFuture<>(); + failed.completeExceptionally(new RuntimeException("Build failed")); + return failed; + } + }); + } + + LOG.log(Level.WARNING, "Cannot open Jshell instance as project is null"); + future.completeExceptionally(new IllegalArgumentException("Project is null for uri: " + uri)); + return future; + } + + public static boolean openNotebookInProjectContext(List args) { + LOG.log(Level.FINER, "Request received for opening Jshell instance with project context {0}", args); + + String uri = null, notebookUri = null; + if (args != null && !args.isEmpty() && args.get(0) != null && args.get(0) instanceof JsonPrimitive) { + uri = ((JsonPrimitive) args.get(0)).getAsString(); + } + if (args != null && args.size() > 1 && args.get(1) != null && args.get(1) instanceof JsonPrimitive) { + notebookUri = ((JsonPrimitive) args.get(1)).getAsString(); + } + Project prj = getProject(uri); + if (prj != null) { + List remoteVmOptions = ProjectConfigurationUtils.launchVMOptions(prj); + List compileOptions = ProjectConfigurationUtils.compileOptions(prj); + + LOG.log(Level.INFO, "Opened Notebook instance with project context {0}", uri); + return true; + } + LOG.log(Level.WARNING, "Cannot open Jshell instance as project is null"); + return false; + } + + public static Project getProject(String uri) { + try { + if (uri == null) { + return null; + } + FileObject file = Utils.fromUri(uri); + Project prj = FileOwnerQuery.getOwner(file); + + return prj; + } catch (MalformedURLException ex) { + Exceptions.printStackTrace(ex); + } + return null; + } +} diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectConfigurationUtils.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectConfigurationUtils.java new file mode 100644 index 0000000..bc79482 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectConfigurationUtils.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed 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 + * + * https://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.netbeans.modules.nbcode.java.project; + +import java.io.File; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import org.eclipse.lsp4j.jsonrpc.validation.NonNull; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.platform.JavaPlatform; +import org.netbeans.api.java.platform.JavaPlatformManager; +import org.netbeans.api.java.platform.Specification; +import org.netbeans.api.java.project.JavaProjectConstants; +import org.netbeans.api.java.queries.UnitTestForSourceQuery; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.SourceGroup; +import org.openide.filesystems.FileObject; +import org.netbeans.api.project.ProjectUtils; +import org.netbeans.spi.project.ActionProgress; +import org.netbeans.spi.project.ActionProvider; +import org.openide.filesystems.FileUtil; +import org.openide.modules.SpecificationVersion; +import org.openide.util.lookup.Lookups; + +/** + * + * @author atalati + */ +public class ProjectConfigurationUtils { + + public final static String CLASS_PATH = "--class-path"; + public final static String MODULE_PATH = "--module-path"; + public final static String ADD_MODULES = "--add-modules"; + public final static String ADD_EXPORTS = "--add-exports"; + + public static boolean isNonTestRoot(SourceGroup sg) { + return UnitTestForSourceQuery.findSources(sg.getRootFolder()).length == 0; + } + + public static boolean isNonTestRoot(FileObject root) { + return UnitTestForSourceQuery.findSources(root).length == 0; + } + + public static String addRoots(String prev, ClassPath cp) { + FileObject[] roots = cp.getRoots(); + StringBuilder sb = new StringBuilder(prev); + + for (FileObject r : roots) { + FileObject ar = FileUtil.getArchiveFile(r); + if (ar == null) { + ar = r; + } + File f = FileUtil.toFile(ar); + if (f != null) { + if (sb.length() > 0) { + sb.append(File.pathSeparatorChar); + } + sb.append(f.getPath()); + } + } + return sb.toString(); + } + + public static Set to2Roots(ClassPath bootCP) { + Set roots = new HashSet<>(); + for (ClassPath.Entry e : bootCP.entries()) { + roots.add(e.getURL()); + } + return roots; + } + + public static List findProjectRoots(Project project) { + List roots = new ArrayList<>(); + if (project == null) { + return roots; + } + for (SourceGroup sg : ProjectUtils.getSources(project).getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA)) { + roots.add(sg.getRootFolder()); + } + return roots; + } + + public static List getNonTestRoots(Project project) { + List roots = findProjectRoots(project); + return roots.stream().filter(root -> isNonTestRoot(root)).toList(); + } + + public static JavaPlatform findPlatform(Project project) { + List ref = findProjectRoots(project); + if (ref.isEmpty()) { + return null; + } + JavaPlatform platform = findPlatform(ClassPath.getClassPath(ref.get(0), ClassPath.BOOT)); + return platform != null ? platform : JavaPlatform.getDefault(); + } + + private static JavaPlatform findPlatform(ClassPath bootCP) { + Set roots = to2Roots(bootCP); + for (JavaPlatform platform : JavaPlatformManager.getDefault().getInstalledPlatforms()) { + Set platformRoots = to2Roots(platform.getBootstrapLibraries()); + if (platformRoots.containsAll(roots)) { + return platform; + } + } + return null; + } + + @NonNull + public static List launchVMOptions(Project project) { + if (project == null) { + return new ArrayList<>(); + } + boolean isModular = ProjectModulePathConfigurationUtils.isModularProject(project); + if (isModular) { + return ProjectModulePathConfigurationUtils.getVmOptions(project); + } + List vmOptions = new ArrayList<>(); + List roots = getNonTestRoots(project); + if (!roots.isEmpty()) { + ClassPath cp = ClassPath.getClassPath(roots.getFirst(), ClassPath.EXECUTE); + vmOptions.addAll(Arrays.asList(CLASS_PATH, addRoots("", cp))); + } + return vmOptions; + } + + @NonNull + public static List compileOptions(Project project) { + if (project == null) { + return new ArrayList<>(); + } + boolean isModular = ProjectModulePathConfigurationUtils.isModularProject(project); + if (isModular) { + return ProjectModulePathConfigurationUtils.getCompileOptions(project); + } + List compileOptions = new ArrayList<>(); + List roots = getNonTestRoots(project); + if (!roots.isEmpty()) { + ClassPath cp = ClassPath.getClassPath(roots.getFirst(), ClassPath.COMPILE); + compileOptions.addAll(Arrays.asList(CLASS_PATH, addRoots("", cp))); + } + return compileOptions; + } + + public static boolean isModularJDK(JavaPlatform pl) { + if (pl != null) { + Specification plSpec = pl.getSpecification(); + SpecificationVersion jvmversion = plSpec.getVersion(); + if (jvmversion.compareTo(new SpecificationVersion("9")) >= 0) { + return true; + } + } + return false; + } + + public static CompletableFuture buildProject(Project project) { + CompletableFuture future = new CompletableFuture<>(); + ActionProvider p = project.getLookup().lookup(ActionProvider.class); + + if (p == null || !p.isActionEnabled(ActionProvider.COMMAND_BUILD, Lookups.singleton(project))) { + future.completeExceptionally(new IllegalStateException("Build action not enabled")); + return future; + } + p.invokeAction(ActionProvider.COMMAND_BUILD, Lookups.fixed(project, new ActionProgress() { + @Override + protected void started() { + // no op + } + + @Override + public void finished(boolean success) { + if (success) { + future.complete(true); + } else { + future.complete(false); + } + } + })); + return future; + } +} diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectModulePathConfigurationUtils.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectModulePathConfigurationUtils.java new file mode 100644 index 0000000..6b8b48d --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectModulePathConfigurationUtils.java @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed 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 + * + * https://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.netbeans.modules.nbcode.java.project; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.classpath.JavaClassPathConstants; +import org.netbeans.api.java.platform.JavaPlatform; +import org.netbeans.api.java.queries.BinaryForSourceQuery; +import org.netbeans.api.java.queries.SourceLevelQuery; +import org.netbeans.api.java.source.ClassIndex; +import org.netbeans.api.java.source.ClasspathInfo; +import org.netbeans.api.java.source.SourceUtils; +import org.netbeans.api.project.Project; +import org.netbeans.spi.java.classpath.support.ClassPathSupport; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.URLMapper; +import org.openide.modules.SpecificationVersion; + +/** + * Methods in this class are taken from the org.netbeans.modules.jshell.support + * module + * + * @author atalati + */ +public class ProjectModulePathConfigurationUtils { + + /** + * Returns set of modules imported by the project. Adds to the passed + * collection if not null. Module names from `required` clause will be + * returned + * + * @param project the project + * @param in optional; the collection + * @return original collection or a new one with imported modules added + */ + private static Collection findProjectImportedModules(Project project, Collection in) { + Collection result = in != null ? in : new HashSet<>(); + if (project == null) { + return result; + } + List roots = ProjectConfigurationUtils.getNonTestRoots(project); + for (FileObject root : roots) { + ClasspathInfo cpi = ClasspathInfo.create(root); + ClassPath mcp = cpi.getClassPath(ClasspathInfo.PathKind.COMPILE); + + for (FileObject r : mcp.getRoots()) { + URL u = URLMapper.findURL(r, URLMapper.INTERNAL); + String modName = SourceUtils.getModuleName(u); + if (modName != null) { + result.add(modName); + } + } + } + + return result; + } + + private static Set findProjectModules(Project project, Set in) { + Set result = in != null ? in : new HashSet<>(); + if (project == null) { + return result; + } + + List roots = ProjectConfigurationUtils.getNonTestRoots(project); + for (FileObject root : roots) { + FileObject fo = root.getFileObject("module-info.java"); + if (fo == null) { + continue; + } + URL u = URLMapper.findURL(root, URLMapper.INTERNAL); + BinaryForSourceQuery.Result r = BinaryForSourceQuery.findBinaryRoots(u); + for (URL u2 : r.getRoots()) { + String modName = SourceUtils.getModuleName(u2, true); + if (modName != null) { + result.add(modName); + } + } + } + return result; + } + + /** + * Collects project modules and packages from them. For each modules, + * provides a list of (non-empty) packages from that module. + * + * @param project + * @return + */ + private static Map> findProjectModulesAndPackages(Project project) { + Map> result = new HashMap<>(); + if (project == null) { + return result; + } + + List roots = ProjectConfigurationUtils.getNonTestRoots(project); + for (FileObject root : roots) { + URL u = URLMapper.findURL(root, URLMapper.INTERNAL); + BinaryForSourceQuery.Result r = BinaryForSourceQuery.findBinaryRoots(u); + for (URL u2 : r.getRoots()) { + String modName = SourceUtils.getModuleName(u2, true); + if (modName != null) { + FileObject rootMod = URLMapper.findFileObject(u); + Collection pkgs = getPackages(rootMod); //new HashSet<>(); + if (!pkgs.isEmpty()) { + Collection oldPkgs = result.get(modName); + if (oldPkgs != null) { + oldPkgs.addAll(pkgs); + } else { + result.put(modName, pkgs); + } + } + } + } + } + return result; + } + + private static Collection getPackages(FileObject root) { + ClasspathInfo cpi = ClasspathInfo.create(root); + // create CPI from just the single source root, to avoid packages from other + // modules + ClasspathInfo rootCpi = new ClasspathInfo.Builder( + cpi.getClassPath(ClasspathInfo.PathKind.BOOT)). + setClassPath(cpi.getClassPath(ClasspathInfo.PathKind.COMPILE)). + setModuleSourcePath(cpi.getClassPath(ClasspathInfo.PathKind.MODULE_SOURCE)). + setModuleCompilePath(cpi.getClassPath(ClasspathInfo.PathKind.MODULE_COMPILE)). + setSourcePath( + ClassPathSupport.createClassPath(root) + ).build(); + + Collection pkgs = new HashSet<>(rootCpi.getClassIndex().getPackageNames("", false, + Collections.singleton(ClassIndex.SearchScope.SOURCE))); + pkgs.remove(""); // NOI18N + return pkgs; + } + + private static ClassPath getRuntimeModulePath(Project project) { + List roots = ProjectConfigurationUtils.getNonTestRoots(project); + if (!roots.isEmpty()) { + return ClassPath.getClassPath(roots.getFirst(), JavaClassPathConstants.MODULE_EXECUTE_PATH); + } + return null; + } + + private static ClassPath getCompileTimeModulePath(Project project) { + List roots = ProjectConfigurationUtils.findProjectRoots(project); + + if (!roots.isEmpty()) { + ClasspathInfo cpi = ClasspathInfo.create(roots.getFirst()); + return cpi.getClassPath(ClasspathInfo.PathKind.COMPILE); + } + return null; + } + + private static List getModuleConfigurations(Project project) { + List exportMods = new ArrayList<>( + ProjectModulePathConfigurationUtils.findProjectImportedModules(project, + ProjectModulePathConfigurationUtils.findProjectModules(project, null)) + ); + + List addReads = new ArrayList<>(); + boolean modular = ProjectModulePathConfigurationUtils.isModularProject(project); + if (exportMods.isEmpty() || !modular) { + return addReads; + } + Collections.sort(exportMods); + addReads.add(ProjectConfigurationUtils.ADD_MODULES); + addReads.add(String.join(",", exportMods)); + + // now export everything from the project: + Map> packages = ProjectModulePathConfigurationUtils.findProjectModulesAndPackages(project); + for (Map.Entry> en : packages.entrySet()) { + String p = en.getKey(); + Collection vals = en.getValue(); + + for (String v : vals) { + addReads.add(ProjectConfigurationUtils.ADD_EXPORTS); + addReads.add(String.format("%s/%s=ALL-UNNAMED", p, v)); + } + } + return addReads; + } + + public static boolean isModularProject(Project project) { + if (project == null) { + return false; + } + JavaPlatform platform = ProjectConfigurationUtils.findPlatform(project); + if (platform == null || !ProjectConfigurationUtils.isModularJDK(platform)) { + return false; + } + + List roots = ProjectConfigurationUtils.getNonTestRoots(project); + for (FileObject root : roots) { + if (root.getFileObject("module-info.java") != null) { + return true; + } + } + return false; + } + + public static List getVmOptions(Project project) { + List vmOptions = new ArrayList<>(); + ClassPath modulePath = getCompileTimeModulePath(project); + ClassPath classPath = getRuntimeModulePath(project); + + if (modulePath != null && classPath != null) { + Set compileModulePath = modulePath.entries().stream().map(entry -> entry.getURL().toString()).collect(Collectors.toSet()); + List entries = new ArrayList<>(); + classPath.entries().stream().forEach(entry -> { + boolean isPresent = compileModulePath.contains(entry.getURL().toString()); + if (!isPresent) { + entries.add(entry.getRoot()); + } + }); + + classPath = ClassPathSupport.createClassPath(entries.toArray(FileObject[]::new)); + if (!classPath.entries().isEmpty()) { + vmOptions.addAll(Arrays.asList( + ProjectConfigurationUtils.CLASS_PATH, + ProjectConfigurationUtils.addRoots("", classPath) + )); + } + vmOptions.addAll(Arrays.asList( + ProjectConfigurationUtils.MODULE_PATH, + ProjectConfigurationUtils.addRoots("", modulePath) + )); + + vmOptions.addAll(getModuleConfigurations(project)); + + } + + return vmOptions; + } + + public static List getCompileOptions(Project project) { + List compileOptions = new ArrayList<>(); + ClassPath modulePath = getCompileTimeModulePath(project); + ClassPath classPath = getRuntimeModulePath(project); + + if (modulePath != null && classPath != null) { + compileOptions.addAll(Arrays.asList( + ProjectConfigurationUtils.MODULE_PATH, + ProjectConfigurationUtils.addRoots("", modulePath) + )); + + compileOptions.addAll(getModuleConfigurations(project)); + } + + return compileOptions; + } + +} diff --git a/vscode/package.json b/vscode/package.json index 7213443..b28064b 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -480,6 +480,11 @@ "category": "Java", "icon": "$(new-folder)" }, + { + "command": "jdk.jshell.project", + "title": "Open JShell in context of a Project", + "category": "Java" + }, { "command": "jdk.java.goto.super.implementation", "title": "%jdk.java.goto.super.implementation%", @@ -584,7 +589,7 @@ "title": "%jdk.delete.cache%" }, { - "command": "jdk.new.notebook", + "command": "jdk.notebook.new", "title": "Create New Notebook...", "category": "Java", "icon": "$(new-file)" @@ -638,6 +643,11 @@ "when": "nbJdkReady && explorerResourceIsFolder", "group": "navigation@3" }, + { + "command": "jdk.notebook.new", + "when": "nbJdkReady && explorerResourceIsFolder", + "group": "navigation@3" + }, { "command": "jdk.open.test", "when": "nbJdkReady && resourceExtname == .java", diff --git a/vscode/src/commands/commands.ts b/vscode/src/commands/commands.ts index 4ccbf7d..ec9699e 100644 --- a/vscode/src/commands/commands.ts +++ b/vscode/src/commands/commands.ts @@ -51,7 +51,8 @@ export const extCommands = { attachDebuggerConfigurations: appendPrefixToCommand("java.attachDebugger.configurations"), loadWorkspaceTests: appendPrefixToCommand("load.workspace.tests"), projectDeleteEntry: appendPrefixToCommand("foundProjects.deleteEntry"), - createNotebook: appendPrefixToCommand("new.notebook") + createNotebook: appendPrefixToCommand("notebook.new"), + openJshellInProject: appendPrefixToCommand("jshell.project") } export const builtInCommands = { @@ -86,5 +87,5 @@ export const nbCommands = { javaProjectPackages: appendPrefixToCommand('java.get.project.packages'), openStackTrace: appendPrefixToCommand('open.stacktrace'), executeNotebookCell: appendPrefixToCommand("jshell.execute.cell"), - notebookCleanup: appendPrefixToCommand("jdk.jshell.cleanup") + openJshellInProject: appendPrefixToCommand("jshell.project.open"), } \ No newline at end of file diff --git a/vscode/src/commands/create.ts b/vscode/src/commands/create.ts index f2b5a48..f5a10d5 100644 --- a/vscode/src/commands/create.ts +++ b/vscode/src/commands/create.ts @@ -18,13 +18,11 @@ import { LanguageClient } from "vscode-languageclient/node"; import { nbCommands, builtInCommands, extCommands } from "./commands"; import { l10n } from "../localiser"; import * as os from 'os'; -import * as path from 'path'; import * as fs from 'fs'; import { ICommand } from "./types"; import { getContextUri, isNbCommandRegistered } from "./utils"; -import { isError, isString } from "../utils"; +import { isString } from "../utils"; import { globalState } from "../globalState"; -import { LOGGER } from "../logger"; const newFromTemplate = async (ctx: any, template: any) => { const client: LanguageClient = await globalState.getClientPromise().client; @@ -98,72 +96,6 @@ const newProject = async (ctx: any) => { } }; -const createNewNotebook = async () => { - const userHomeDir = os.homedir(); - - const filePath = await window.showInputBox({ - prompt: "Enter path for new Java notebook (.ijnb)", - value: path.join(userHomeDir, "Untitled.ijnb") - }); - - if (!filePath?.trim()) { - return; - } - - const finalPath = filePath.endsWith('.ijnb') ? filePath : `${filePath}.ijnb`; - - LOGGER.log(`Attempting to create notebook at: ${finalPath}`); - - try { - const exists = await fs.promises.access(finalPath) - .then(() => true) - .catch(() => false); - - if (exists) { - window.showErrorMessage("Notebook already exists, please try creating with some different name"); - return; - } - - const dir = path.dirname(finalPath); - await fs.promises.mkdir(dir, { recursive: true }); - - const emptyNotebook = { - cells: [{ - cell_type: "code", - source: [], - metadata: { - language: "java" - }, - execution_count: null, - outputs: [] - }], - metadata: { - kernelspec: { - name: "java", - language: "java", - display_name: "Java" - }, - language_info: { - name: "java" - } - }, - nbformat: 4, - nbformat_minor: 5 - }; - - await fs.promises.writeFile(finalPath, JSON.stringify(emptyNotebook, null, 2), { encoding: 'utf8' }); - - LOGGER.log(`Created notebook at: ${finalPath}`); - - const notebookUri = Uri.file(finalPath); - const notebookDocument = await workspace.openNotebookDocument(notebookUri); - await window.showNotebookDocument(notebookDocument); - } catch (err) { - console.error(`Detailed error:`, err); - window.showErrorMessage(`Failed to create notebook: ${isError(err) ? err.message : " "}`); - } -}; - export const registerCreateCommands: ICommand[] = [ { command: extCommands.newFromTemplate, @@ -171,8 +103,5 @@ export const registerCreateCommands: ICommand[] = [ }, { command: extCommands.newProject, handler: newProject - }, { - command: extCommands.createNotebook, - handler: createNewNotebook } ]; \ No newline at end of file diff --git a/vscode/src/commands/notebook.ts b/vscode/src/commands/notebook.ts new file mode 100644 index 0000000..bbe44ea --- /dev/null +++ b/vscode/src/commands/notebook.ts @@ -0,0 +1,156 @@ +import * as os from 'os'; +import * as path from 'path'; +import * as fs from 'fs'; +import { LOGGER } from '../logger'; +import { commands, Uri, window, workspace } from 'vscode'; +import { isError } from '../utils'; +import { extCommands, nbCommands } from './commands'; +import { ICommand } from './types'; +import { LanguageClient } from 'vscode-languageclient/node'; +import { globalState } from '../globalState'; +import { getContextUri, isNbCommandRegistered } from './utils'; +import { l10n } from '../localiser'; +import { extConstants } from '../constants'; + +const createNewNotebook = async (ctx?: any) => { + try { + let notebookDir: Uri | null = null; + + if (!ctx) { + let defaultUri: Uri | null = null; + const activeFilePath = window.activeTextEditor?.document.uri; + + if (activeFilePath) { + const parentDir = Uri.parse(path.dirname(activeFilePath.fsPath)); + if (workspace.getWorkspaceFolder(parentDir)) { + defaultUri = parentDir; + } + } + if (defaultUri == null) { + const workspaceFolders = workspace.workspaceFolders; + defaultUri = workspaceFolders?.length === 1 ? workspaceFolders[0].uri : null; + if (defaultUri == null) { + if (workspaceFolders && workspaceFolders.length > 1) { + const userPref = await window.showWorkspaceFolderPick({ + placeHolder: "Select workspace folder in which notebook needs to be created", + ignoreFocusOut: true + }); + if (userPref) { + defaultUri = userPref.uri; + } + } + } + if (defaultUri == null) { + defaultUri = Uri.parse(os.homedir()); + } + } + + const nbFolderPath = await window.showOpenDialog({ + canSelectFolders: true, + canSelectFiles: false, + canSelectMany: false, + defaultUri, + openLabel: "Select Notebook creation folder", + title: "Select folder in which notebook needs to be created" + }); + + if (nbFolderPath) { + notebookDir = nbFolderPath[0]; + } + } else { + notebookDir = getContextUri(ctx) || null; + } + if (notebookDir == null) { + window.showErrorMessage("Path not selected for creating new notebook"); + return; + } + + const notebookName = await window.showInputBox({ + prompt: `Enter new Java notebook (${extConstants.NOTEBOOK_FILE_EXTENSION}) or (.ipynb) file name`, + value: `Untitled.${extConstants.NOTEBOOK_FILE_EXTENSION}` + }); + + if (!notebookName?.trim()) { + window.showErrorMessage("Invalid notebook file name"); + return; + } + const notebookNameWithExt = notebookName.endsWith(extConstants.NOTEBOOK_FILE_EXTENSION) || notebookName.endsWith('.ipynb') ? + notebookName : `${notebookName}.${extConstants.NOTEBOOK_FILE_EXTENSION}`; + + const finalNotebookPath = path.join(notebookDir.fsPath, notebookNameWithExt); + + LOGGER.log(`Attempting to create notebook at: ${finalNotebookPath}`); + + if (fs.existsSync(finalNotebookPath)) { + window.showErrorMessage("Notebook already exists, please try creating with some different name"); + return; + } + + const emptyNotebook = { + cells: [{ + cell_type: "code", + source: [], + metadata: { + language: "java" + }, + execution_count: null, + outputs: [] + }], + metadata: { + kernelspec: { + name: "java", + language: "java", + display_name: "Java" + }, + language_info: { + name: "java" + } + }, + nbformat: 4, + nbformat_minor: 5 + }; + + await fs.promises.writeFile(finalNotebookPath, JSON.stringify(emptyNotebook, null, 2), { encoding: 'utf8' }); + + LOGGER.log(`Created notebook at: ${finalNotebookPath}`); + + const notebookUri = Uri.file(finalNotebookPath); + const notebookDocument = await workspace.openNotebookDocument(notebookUri); + await window.showNotebookDocument(notebookDocument); + } catch (error) { + LOGGER.error(`Error occurred while creating new notebook: ${isError(error) ? error.message : error}`); + + window.showErrorMessage(`Failed to create new notebook`); + } +}; + +const openJshellInContextOfProject = async (ctx: any) => { + try { + let client: LanguageClient = await globalState.getClientPromise().client; + if (await isNbCommandRegistered(nbCommands.openJshellInProject)) { + const res: string[] = await commands.executeCommand(nbCommands.openJshellInProject, getContextUri(ctx)?.toString()); + + const args = res.join(" "); + const terminal = window.createTerminal("Jshell instance"); + terminal.show(); + terminal.sendText(`jshell ${args}`, true); + } else { + throw l10n.value("jdk.extension.error_msg.doesntSupportGoToTest", { client }); + } + } catch (error) { + window.showErrorMessage("Some error occurred while launching jshell"); + LOGGER.error(`Error occurred while launching jshell in project context : ${isError(error) ? error.message : error}`); + } +} + + +export const registerNotebookCommands: ICommand[] = [ + { + command: extCommands.createNotebook, + handler: createNewNotebook + }, + { + command: extCommands.openJshellInProject, + handler: openJshellInContextOfProject + } +]; \ No newline at end of file diff --git a/vscode/src/commands/register.ts b/vscode/src/commands/register.ts index 7e2e5c3..ef35778 100644 --- a/vscode/src/commands/register.ts +++ b/vscode/src/commands/register.ts @@ -24,6 +24,7 @@ import { registerRefactorCommands } from "./refactor"; import { registerUtilCommands } from "./utilCommands"; import { registerDebugCommands } from "./debug"; import { registerRunConfigurationCommands } from "./runConfiguration"; +import { registerNotebookCommands } from "./notebook"; type ICommandModules = Record; @@ -36,7 +37,8 @@ const commandModules: ICommandModules = { refactor: registerRefactorCommands, util: registerUtilCommands, debug: registerDebugCommands, - runConfiguration: registerRunConfigurationCommands + runConfiguration: registerRunConfigurationCommands, + notebook: registerNotebookCommands } export const subscribeCommands = (context: ExtensionContext) => { diff --git a/vscode/src/constants.ts b/vscode/src/constants.ts index 0e1789c..956fce3 100644 --- a/vscode/src/constants.ts +++ b/vscode/src/constants.ts @@ -23,6 +23,7 @@ export namespace extConstants { export const LANGUAGE_ID: string = "java"; export const ORACLE_VSCODE_EXTENSION_ID = 'oracle.oracle-java'; export const COMMAND_PREFIX = 'jdk'; + export const NOTEBOOK_FILE_EXTENSION = 'ijnb'; } export namespace jdkDownloaderConstants { diff --git a/vscode/src/notebooks/impl.ts b/vscode/src/notebooks/impl.ts index 1f1ea92..f336dff 100644 --- a/vscode/src/notebooks/impl.ts +++ b/vscode/src/notebooks/impl.ts @@ -233,25 +233,12 @@ export class IJNBKernel implements vscode.Disposable { this.controller.supportedLanguages = ['markdown', 'java']; this.controller.supportsExecutionOrder = true; this.controller.executeHandler = this.executeCell.bind(this); - - vscode.workspace.onDidCloseNotebookDocument(this.onNotebookClosed.bind(this)); } dispose() { this.controller.dispose(); } - private async onNotebookClosed(notebook: vscode.NotebookDocument): Promise { - try { - await globalState.getClientPromise().client; - if (await isNbCommandRegistered(nbCommands.notebookCleanup)) { - await vscode.commands.executeCommand(nbCommands.notebookCleanup, notebook.uri.toString()); - } - } catch (err) { - console.error(`Error cleaning up JShell for notebook: ${err}`); - } - } - private async executeCell(cells: vscode.NotebookCell[], notebook: vscode.NotebookDocument, _controller: vscode.NotebookController): Promise { console.log("Starting execution for cells:", cells); From 875b2190ca858e4d73f7807eff7328ac362bff52 Mon Sep 17 00:00:00 2001 From: Shivam Madan Date: Tue, 1 Jul 2025 14:17:04 +0530 Subject: [PATCH 4/9] Added notification for initializing java notebook kernel and minor bug fixes. --- .../NotebookDocumentServiceHandlerImpl.java | 15 +++++++++++++-- .../java/notebook/NotebookSessionManager.java | 15 +++++++-------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java index 2672fa1..97bfda1 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java @@ -50,6 +50,8 @@ import org.eclipse.lsp4j.ImplementationParams; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.LocationLink; +import org.eclipse.lsp4j.MessageParams; +import org.eclipse.lsp4j.MessageType; import org.eclipse.lsp4j.PrepareRenameDefaultBehavior; import org.eclipse.lsp4j.PrepareRenameParams; import org.eclipse.lsp4j.PrepareRenameResult; @@ -64,6 +66,7 @@ import org.eclipse.lsp4j.WillSaveTextDocumentParams; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.lsp4j.jsonrpc.messages.Either3; +import org.netbeans.modules.java.lsp.server.protocol.ShowStatusMessageParams; import org.openide.util.lookup.ServiceProvider; /** @@ -82,8 +85,16 @@ public class NotebookDocumentServiceHandlerImpl implements NotebookDocumentServi @Override public void didOpen(DidOpenNotebookDocumentParams params) { try { - CompletableFuture.runAsync(() -> NotebookSessionManager.getInstance().createSession(params.getNotebookDocument())); - + client.showStatusBarMessage(new ShowStatusMessageParams(MessageType.Info,"Intializing Java kernel for notebook.")); + NotebookSessionManager.getInstance().createSession(params.getNotebookDocument()).whenComplete((JShell jshell,Throwable t) -> { + if (t == null) { + client.showStatusBarMessage(new ShowStatusMessageParams(MessageType.Info, "Java kernel initialized successfully")); + } else { + // if package import fails user is not informed ? + client.showMessage(new MessageParams(MessageType.Error,"Error could not initialize Java kernel for the notebook.")); + LOG.log(Level.SEVERE, "Error could not initialize Java kernel for the notebook. : {0}", t.getMessage()); + } + }); NotebookDocumentStateManager state = new NotebookDocumentStateManager(params.getNotebookDocument(), params.getCellTextDocuments()); params.getNotebookDocument().getCells().forEach(cell -> { notebookCellMap.put(cell.getDocument(), params.getNotebookDocument().getUri()); diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java index 64f719d..a8c90cd 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java @@ -39,9 +39,9 @@ public class NotebookSessionManager { private static final Logger LOG = Logger.getLogger(NotebookSessionManager.class.getName()); private static final String SOURCE_FLAG = "--source"; private static final String ENABLE_PREVIEW = "--enable-preview"; - private static final String CLASS_PATH = "--enable-preview"; - private static final String MODULE_PATH = "--enable-preview"; - private static final String ADD_MODULES = "--enable-preview"; + private static final String CLASS_PATH = "--class-path"; + private static final String MODULE_PATH = "--module-path"; + private static final String ADD_MODULES = "--add-modules"; private final Map> sessions = new ConcurrentHashMap<>(); private final Map outputStreams = new ConcurrentHashMap<>(); @@ -61,8 +61,6 @@ private static class Singleton { private CompletableFuture jshellBuilder(PrintStream outPrintStream, PrintStream errPrintStream) { return CompletableFuture.supplyAsync(() -> { - List compilerOptions = getCompilerOptions(); - List remoteOptions = getRemoteVmOptions(); try { NotebookConfigs.getInstance().getInitialized().get(); } catch (InterruptedException ex) { @@ -70,7 +68,8 @@ private CompletableFuture jshellBuilder(PrintStream outPrintStream, Prin } catch (ExecutionException ex) { LOG.log(Level.WARNING, "ExecutionException occurred while getting notebook configs: {0}", ex.getMessage()); } - + List compilerOptions = getCompilerOptions(); + List remoteOptions = getRemoteVmOptions(); if (compilerOptions.isEmpty()) { return JShell.builder() .out(outPrintStream) @@ -89,10 +88,10 @@ private CompletableFuture jshellBuilder(PrintStream outPrintStream, Prin }); } - public void createSession(NotebookDocument notebookDoc) { + public CompletableFuture createSession(NotebookDocument notebookDoc) { String notebookId = notebookDoc.getUri(); - sessions.computeIfAbsent(notebookId, (String id) -> { + return sessions.computeIfAbsent(notebookId, (String id) -> { ByteArrayOutputStream outStream = new ByteArrayOutputStream(); ByteArrayOutputStream errStream = new ByteArrayOutputStream(); outputStreams.put(notebookId, outStream); From 30f9301d21f977f7fe1d08aa8933a7b2086189be Mon Sep 17 00:00:00 2001 From: Achal Talati Date: Mon, 2 Jun 2025 16:43:54 +0530 Subject: [PATCH 5/9] Refactoring notebook frontend code Added notebook structure validation - Structure validation using nbformat schema - Updated licenses and copyright - Added proper id to each cell - Added proper execution count - Added validation in serialization step - Added logs - added input output stream support - added streaming support of cell execution, enhanced output stream - added protocol for listening to cell execution notifications - added streaming support in the frontend for notebooks and improved kernel - added interrupt handler - added cellId to be passed in the cell execution request - added jshell context in the editor context - fixed issue of command truncation - improved code completion provider - fixed some build issues and bugs, e.g. init of configurations Improvements in notebook cell execution display - Corrected execution counter - Introduced mimeTypeHandler and executionSummary and refactored code - Added new commands to localisation english file - Added tests for mimeType and executionSummary of notebook code - New and unexecuted cells will not display success or failure --- THIRD_PARTY_LICENSES.txt | 86 +- nbcode/notebooks/nbproject/project.xml | 17 - .../nbcode/java/notebook/CellState.java | 88 +- .../java/notebook/CodeCompletionProvider.java | 110 +- .../nbcode/java/notebook/CodeEval.java | 401 +++- .../java/notebook/CustomInputStream.java | 81 + .../java/notebook/JshellStreamsHandler.java | 109 ++ .../java/notebook/LanguageClientInstance.java | 47 + .../notebook/NotebookCommandsHandler.java | 7 +- .../nbcode/java/notebook/NotebookConfigs.java | 18 +- .../NotebookDocumentServiceHandlerImpl.java | 12 +- .../NotebookDocumentStateManager.java | 27 +- .../java/notebook/NotebookSessionManager.java | 47 +- .../nbcode/java/notebook/NotebookUtils.java | 63 +- .../nbcode/java/notebook/ResultEval.java | 61 - .../java/notebook/StreamingOutputStream.java | 114 ++ .../nbcode/java/project/CommandHandler.java | 25 +- .../project/ProjectConfigurationUtils.java | 2 +- .../ProjectModulePathConfigurationUtils.java | 1 - .../nbcode/java/notebook/CellStateTest.java | 222 +++ .../NotebookDocumentStateManagerTest.java | 244 +++ patches/java-notebooks.diff | 630 +++++- patches/upgrade-lsp4j.diff | 101 + vscode/l10n/bundle.l10n.en.json | 1 + vscode/l10n/bundle.l10n.ja.json | 1 + vscode/l10n/bundle.l10n.zh-cn.json | 1 + vscode/package-lock.json | 1732 +++++++++-------- vscode/package.json | 19 +- vscode/package.nls.json | 2 + vscode/src/commands/commands.ts | 1 + vscode/src/commands/notebook.ts | 58 +- vscode/src/extension.ts | 4 +- .../lsp/listeners/notifications/handlers.ts | 12 +- vscode/src/lsp/listeners/requests/handlers.ts | 20 +- vscode/src/lsp/listeners/requests/register.ts | 4 +- vscode/src/lsp/protocol.ts | 43 +- vscode/src/notebooks/codeCellExecution.ts | 122 ++ vscode/src/notebooks/constants.ts | 23 + vscode/src/notebooks/executionSummary.ts | 51 + vscode/src/notebooks/impl.ts | 332 ---- vscode/src/notebooks/kernel.ts | 185 ++ vscode/src/notebooks/mimeTypeHandler.ts | 71 + .../src/notebooks/nbformat.v4.d7.schema.json | 594 ++++++ vscode/src/notebooks/notebook.ts | 90 + vscode/src/notebooks/register.ts | 34 +- vscode/src/notebooks/serializer.ts | 84 + vscode/src/notebooks/types.ts | 105 + vscode/src/notebooks/utils.ts | 215 ++ .../src/test/unit/mocks/vscode/mockVscode.ts | 2 + .../mocks/vscode/notebookCellOutputItem.ts} | 28 +- .../notebooks/executionSummary.unit.test.ts | 108 + .../notebooks/mimeTypeHandler.unit.test.ts | 186 ++ vscode/tsconfig.json | 3 +- 53 files changed, 5012 insertions(+), 1632 deletions(-) create mode 100644 nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CustomInputStream.java create mode 100644 nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/JshellStreamsHandler.java create mode 100644 nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/LanguageClientInstance.java delete mode 100644 nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/ResultEval.java create mode 100644 nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/StreamingOutputStream.java create mode 100644 nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/CellStateTest.java create mode 100644 nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManagerTest.java create mode 100644 vscode/src/notebooks/codeCellExecution.ts create mode 100644 vscode/src/notebooks/constants.ts create mode 100644 vscode/src/notebooks/executionSummary.ts delete mode 100644 vscode/src/notebooks/impl.ts create mode 100644 vscode/src/notebooks/kernel.ts create mode 100644 vscode/src/notebooks/mimeTypeHandler.ts create mode 100644 vscode/src/notebooks/nbformat.v4.d7.schema.json create mode 100644 vscode/src/notebooks/notebook.ts create mode 100644 vscode/src/notebooks/serializer.ts create mode 100644 vscode/src/notebooks/types.ts create mode 100644 vscode/src/notebooks/utils.ts rename vscode/src/{lsp/listeners/requests/notebooks.ts => test/unit/mocks/vscode/notebookCellOutputItem.ts} (51%) create mode 100644 vscode/src/test/unit/notebooks/executionSummary.unit.test.ts create mode 100644 vscode/src/test/unit/notebooks/mimeTypeHandler.unit.test.ts diff --git a/THIRD_PARTY_LICENSES.txt b/THIRD_PARTY_LICENSES.txt index b374349..5f27e0a 100644 --- a/THIRD_PARTY_LICENSES.txt +++ b/THIRD_PARTY_LICENSES.txt @@ -329,11 +329,11 @@ ide/modules/ext/junixsocket-native-common-2.5.1.jar Apache-2.0 ide/modules/ext/launcher-common-24.0.0.jar UPL ide/modules/ext/lucene-core-3.6.2.jar Apache-2.0-lucene ide/modules/ext/nativeimage-24.0.0.jar UPL -ide/modules/ext/org.eclipse.lsp4j-0.13.0.jar EPL-v20 -ide/modules/ext/org.eclipse.lsp4j.debug-0.13.0.jar EPL-v20 -ide/modules/ext/org.eclipse.lsp4j.generator-0.13.0.jar EPL-v20 -ide/modules/ext/org.eclipse.lsp4j.jsonrpc-0.13.0.jar EPL-v20 -ide/modules/ext/org.eclipse.lsp4j.jsonrpc.debug-0.13.0.jar EPL-v20 +ide/modules/ext/org.eclipse.lsp4j-0.16.0.jar EPL-v20 +ide/modules/ext/org.eclipse.lsp4j.debug-0.16.0.jar EPL-v20 +ide/modules/ext/org.eclipse.lsp4j.generator-0.16.0.jar EPL-v20 +ide/modules/ext/org.eclipse.lsp4j.jsonrpc-0.16.0.jar EPL-v20 +ide/modules/ext/org.eclipse.lsp4j.jsonrpc.debug-0.16.0.jar EPL-v20 ide/modules/ext/org.eclipse.tm4e.core-0.14.1.jar EPL-v20 ide/modules/ext/org.eclipse.xtend.lib-2.24.0.jar EPL-v20 ide/modules/ext/org.eclipse.xtend.lib.macro-2.24.0.jar EPL-v20 @@ -481,11 +481,11 @@ java/modules/ext/maven/search-api-7.1.5.jar Apache-2.0 java/modules/ext/maven/search-backend-smo-7.1.5.jar Apache-2.0 java/modules/ext/nb-javac-jdk-25-31.1-api.jar GPL-2-CP java/modules/ext/nb-javac-jdk-25-31.1.jar GPL-2-CP -java/modules/ext/org.eclipse.lsp4j-0.13.0.jar EPL-v20 -java/modules/ext/org.eclipse.lsp4j.debug-0.13.0.jar EPL-v20 -java/modules/ext/org.eclipse.lsp4j.generator-0.13.0.jar EPL-v20 -java/modules/ext/org.eclipse.lsp4j.jsonrpc-0.13.0.jar EPL-v20 -java/modules/ext/org.eclipse.lsp4j.jsonrpc.debug-0.13.0.jar EPL-v20 +java/modules/ext/org.eclipse.lsp4j-0.16.0.jar EPL-v20 +java/modules/ext/org.eclipse.lsp4j.debug-0.16.0.jar EPL-v20 +java/modules/ext/org.eclipse.lsp4j.generator-0.16.0.jar EPL-v20 +java/modules/ext/org.eclipse.lsp4j.jsonrpc-0.16.0.jar EPL-v20 +java/modules/ext/org.eclipse.lsp4j.jsonrpc.debug-0.16.0.jar EPL-v20 java/modules/ext/org.eclipse.xtend.lib-2.24.0.jar EPL-v20 java/modules/ext/org.eclipse.xtend.lib.macro-2.24.0.jar EPL-v20 java/modules/ext/org.eclipse.xtext.xbase.lib-2.24.0.jar EPL-v20 @@ -9805,4 +9805,70 @@ Developer of the Original Software was Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun Microsystems, Inc. +------------------ END OF DEPENDENCY LICENSE -------------------- + +Dependency: ajv +=============== + +------------------ START OF DEPENDENCY LICENSE -------------------- +The MIT License (MIT) + +Copyright (c) 2015-2021 Evgeny Poberezkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +------------------ END OF DEPENDENCY LICENSE -------------------- + +Dependancy: nbformat +==================== + +------------------ START OF DEPENDENCY LICENSE -------------------- +BSD 3-Clause License + +- Copyright (c) 2001-2015, IPython Development Team +- Copyright (c) 2015-, Jupyter Development Team + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ------------------ END OF DEPENDENCY LICENSE -------------------- diff --git a/nbcode/notebooks/nbproject/project.xml b/nbcode/notebooks/nbproject/project.xml index 60b1272..c3bdeaa 100644 --- a/nbcode/notebooks/nbproject/project.xml +++ b/nbcode/notebooks/nbproject/project.xml @@ -56,23 +56,6 @@ 1.28 - - org.netbeans.libs.javacapi - - - - - - - - org.netbeans.modules.editor.mimelookup - - - - 1 - 1.65 - - org.netbeans.modules.java.lsp.server diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CellState.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CellState.java index 9264167..3e57226 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CellState.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CellState.java @@ -15,11 +15,17 @@ */ package org.netbeans.modules.nbcode.java.notebook; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Logger; import org.eclipse.lsp4j.ExecutionSummary; import org.eclipse.lsp4j.NotebookCell; import org.eclipse.lsp4j.NotebookCellKind; import org.eclipse.lsp4j.TextDocumentItem; +import org.netbeans.modules.java.lsp.server.notebook.CellStateResponse; +import org.netbeans.modules.java.lsp.server.notebook.NotebookCellStateParams; +import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient; /** * @@ -29,13 +35,18 @@ public class CellState { private final NotebookCellKind type; private final String language; + private final String cellUri; + private final String notebookUri; private final AtomicReference metadata; - private final AtomicReference content; + private final AtomicReference content; private final AtomicReference executionSummary; + private static final Logger LOG = Logger.getLogger(CellState.class.getName()); - CellState(NotebookCell notebookCell, TextDocumentItem details) { + CellState(NotebookCell notebookCell, TextDocumentItem details, String notebookUri) { String normalizedText = NotebookUtils.normalizeLineEndings(details.getText()); - this.content = new AtomicReference<>(new VersionAwareCotent(normalizedText, details.getVersion())); + this.cellUri = details.getUri(); + this.notebookUri = notebookUri; + this.content = new AtomicReference<>(new VersionAwareContent(normalizedText, details.getVersion())); this.language = details.getLanguageId(); this.type = notebookCell.getKind(); this.metadata = new AtomicReference<>(notebookCell.getMetadata()); @@ -62,19 +73,57 @@ public ExecutionSummary getExecutionSummary() { return executionSummary.get(); } - public void setContent(String newContent, int newVersion) { + public String getCellUri() { + return cellUri; + } + + public String getNotebookUri() { + return notebookUri; + } + + public void setContent(String newContent, int newVersion) throws InterruptedException, ExecutionException { String normalizedContent = NotebookUtils.normalizeLineEndings(newContent); - VersionAwareCotent currentContent = content.get(); + VersionAwareContent currentContent = content.get(); if (currentContent.getVersion() != newVersion - 1) { - throw new IllegalStateException("Version mismatch: expected " + (newVersion - 1) + ", got " + currentContent.getVersion()); + if (currentContent.getVersion() >= newVersion) { + LOG.warning("Current version is higher or equal than the new version request received, so ignoring it."); + return; + } + CompletableFuture response = requestLatestCellState(); + if (response == null) { + throw new IllegalStateException("Unable to send notebook cell state request to the client"); + } + + CellStateResponse newCellState = response.get(); + int receivedVersion = newCellState.getVersion(); + + if (receivedVersion > currentContent.getVersion()) { + VersionAwareContent newVersionContent = new VersionAwareContent(newCellState.getText(), receivedVersion); + content.set(newVersionContent); + } else { + throw new IllegalStateException("Version mismatch: Received version to be greater than current version, received version: " + (receivedVersion) + ", current version: " + currentContent.getVersion()); + } + } else { + VersionAwareContent newVersionContent = new VersionAwareContent(normalizedContent, newVersion); + + if (!content.compareAndSet(currentContent, newVersionContent)) { + throw new IllegalStateException("Concurrent modification detected. Version expected: " + (newVersion - 1) + ", current: " + content.get().getVersion()); + } } + } - VersionAwareCotent newVersionContent = new VersionAwareCotent(normalizedContent, newVersion); - - if (!content.compareAndSet(currentContent, newVersionContent)) { - throw new IllegalStateException("Concurrent modification detected. Version expected: " + (newVersion - 1) + ", current: " + content.get().getVersion()); + public void requestContentAndSet() throws InterruptedException, ExecutionException { + CompletableFuture response = requestLatestCellState(); + if (response == null) { + throw new IllegalStateException("Unable to send notebook cell state request to the client"); } + CellStateResponse newCellState = response.get(); + if (newCellState.getVersion() <= 0) { + throw new IllegalStateException("Received incorrect version number: " + newCellState.getVersion()); + } + VersionAwareContent newVersionContent = new VersionAwareContent(newCellState.getText(), newCellState.getVersion()); + content.set(newVersionContent); } public void setExecutionSummary(ExecutionSummary executionSummary) { @@ -85,12 +134,27 @@ public void setMetadata(Object metadata) { this.metadata.set(metadata); } - private class VersionAwareCotent { + // protected methods for ease of unit testing + protected CompletableFuture requestLatestCellState() { + NbCodeLanguageClient client = LanguageClientInstance.getInstance().getClient(); + + if (client == null) { + LOG.warning("Client is null"); + return null; + } + return client.getNotebookCellState(new NotebookCellStateParams(notebookUri, cellUri)); + } + + protected VersionAwareContent getVersionAwareContent(){ + return this.content.get(); + } + + protected class VersionAwareContent { private String content; private int version; - public VersionAwareCotent(String content, int version) { + public VersionAwareContent(String content, int version) { this.content = content; this.version = version; } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeCompletionProvider.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeCompletionProvider.java index 600159e..a0f86a5 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeCompletionProvider.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeCompletionProvider.java @@ -24,6 +24,7 @@ import java.util.logging.Logger; import jdk.jshell.JShell; import jdk.jshell.SourceCodeAnalysis; +import jdk.jshell.SourceCodeAnalysis.Suggestion; import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.CompletionList; import org.eclipse.lsp4j.CompletionParams; @@ -35,6 +36,7 @@ * @author atalati */ public class CodeCompletionProvider { + private static final Logger LOG = Logger.getLogger(CodeCompletionProvider.class.getName()); private CodeCompletionProvider() { @@ -49,72 +51,66 @@ private static class Singleton { private static final CodeCompletionProvider instance = new CodeCompletionProvider(); } - public CompletableFuture, CompletionList>> getCodeCompletions(CompletionParams params, NotebookDocumentStateManager state, JShell instance) { - try { - if (instance == null || state == null) { - return CompletableFuture.completedFuture(Either., CompletionList>forLeft(new ArrayList<>())); - } - String uri = params.getTextDocument().getUri(); - CellState cellState = state.getCell(uri); - String content = cellState.getContent(); - - Position position = params.getPosition(); - int cursorOffset = NotebookUtils.getOffset(content, position); - - String inputText = content.substring(0, cursorOffset); - - SourceCodeAnalysis sourceAnalysis = instance.sourceCodeAnalysis(); - int[] anchor = new int[1]; - - sourceAnalysis.analyzeCompletion(inputText); - // Need to get snippets because JShell doesn't provide suggestions sometimes if import statement is there in the cell - String finalString = getSnippets(sourceAnalysis, inputText).getLast(); - finalString = finalString.charAt(finalString.length() - 1) == ';' ? finalString.substring(0, finalString.length() - 1) : finalString; - - List suggestions = sourceAnalysis.completionSuggestions( - finalString, finalString.length(), anchor); + public CompletableFuture, CompletionList>> getCodeCompletions( + CompletionParams params, + NotebookDocumentStateManager state, + JShell instance) { - List completionItems = new ArrayList<>(); - Map visited = new HashMap<>(); - - for (SourceCodeAnalysis.Suggestion suggestion : suggestions) { - if (visited.containsKey(suggestion.continuation())) { - continue; + return CompletableFuture.supplyAsync(() -> { + try { + if (instance == null || state == null) { + return Either., CompletionList>forLeft(new ArrayList<>()); } - CompletionItem item = new CompletionItem(); - item.setLabel(suggestion.continuation()); - - if (!suggestion.continuation().isEmpty()) { - item.setDocumentation(suggestion.continuation()); + SourceCodeAnalysis sourceCodeAnalysis = instance.sourceCodeAnalysis(); + String textToComplete = getTextToComplete( + params.getTextDocument().getUri(), + params.getPosition(), + state, + sourceCodeAnalysis + ); + + List suggestions = sourceCodeAnalysis.completionSuggestions( + textToComplete, + textToComplete.length(), + new int[1] + ); + + List completionItems = new ArrayList<>(); + Map visited = new HashMap<>(); + + for (Suggestion suggestion : suggestions) { + String continuation = suggestion.continuation(); + if (!visited.containsKey(continuation)) { + completionItems.add(createCompletionItem(continuation)); + visited.put(continuation, Boolean.TRUE); + } } - completionItems.add(item); - visited.put(suggestion.continuation(), Boolean.TRUE); + return Either., CompletionList>forLeft(completionItems); + + } catch (Exception e) { + LOG.log(Level.WARNING, "Error getting code completions: {0}", e.getMessage()); + return Either., CompletionList>forLeft(new ArrayList<>()); } + }); + } - return CompletableFuture.completedFuture(Either., CompletionList>forLeft(completionItems)); - } catch (Exception e) { - LOG.log(Level.WARNING, "Error getting code completions: {0}", e.getMessage()); - return CompletableFuture.completedFuture(Either., CompletionList>forLeft(new ArrayList<>())); - } + private CompletionItem createCompletionItem(String label) { + CompletionItem item = new CompletionItem(); + item.setLabel(label); + + return item; } - private List getSnippets(SourceCodeAnalysis analysis, String code) { - String codeRemaining = code.trim(); - - List codeSnippets = new ArrayList<>(); - while (!codeRemaining.isEmpty()) { - SourceCodeAnalysis.CompletionInfo info = analysis.analyzeCompletion(codeRemaining); - if (info.completeness().isComplete()) { - codeSnippets.add(info.source()); - } else { - codeSnippets.add(codeRemaining); - break; - } - codeRemaining = info.remaining().trim(); - } + private String getTextToComplete(String uri, Position position, NotebookDocumentStateManager state, SourceCodeAnalysis sourceCodeAnalysis) { + CellState cellState = state.getCell(uri); + String content = cellState.getContent(); + int cursorOffset = NotebookUtils.getOffset(content, position); + + String offsetText = content.substring(0, cursorOffset); + List snippets = NotebookUtils.getCodeSnippets(sourceCodeAnalysis, offsetText); - return codeSnippets; + return snippets.isEmpty() ? "" : snippets.getLast(); } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeEval.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeEval.java index 43de88b..e2c7346 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeEval.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeEval.java @@ -17,15 +17,22 @@ import jdk.jshell.JShell; import jdk.jshell.SnippetEvent; -import com.google.gson.JsonPrimitive; -import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import jdk.jshell.Diag; import jdk.jshell.SourceCodeAnalysis; +import org.netbeans.modules.java.lsp.server.notebook.CellExecutionResult; +import org.netbeans.modules.java.lsp.server.notebook.NotebookCellExecutionProgressResultParams; +import org.netbeans.modules.java.lsp.server.notebook.NotebookCellExecutionProgressResultParams.EXECUTION_STATUS; +import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient; import org.openide.util.RequestProcessor; /** @@ -35,113 +42,341 @@ public class CodeEval { private static final Logger LOG = Logger.getLogger(CodeEval.class.getName()); - private static final RequestProcessor CODE_EXECUTOR = new RequestProcessor("Jshell Code Evaluator", 1, true, true); + private static final String CODE_EXEC_INTERRUPT_SUCCESS_MESSAGE = "Code execution stopped successfully"; + private static final String CODE_EXEC_INTERRUPTED_MESSAGE = "Code execution was interrupted"; + private static final Pattern LINEBREAK = Pattern.compile("\\R"); - public static CompletableFuture> evaluate(List arguments) { - if (arguments != null) { - AtomicReference sourceCode = new AtomicReference<>(null); - AtomicReference notebookId = new AtomicReference<>(null); + private final Map codeExecMap = new ConcurrentHashMap<>(); + private final Map>> pendingTasks = new ConcurrentHashMap<>(); + private final Map activeCellExecutionMapping = new ConcurrentHashMap<>(); - if (arguments.get(0) != null && arguments.get(0) instanceof JsonPrimitive) { - sourceCode.set(((JsonPrimitive) arguments.get(0)).getAsString()); - } - if (arguments.size() > 1 && arguments.get(1) != null && arguments.get(1) instanceof JsonPrimitive) { - notebookId.set(((JsonPrimitive) arguments.get(1)).getAsString()); - } + public static CodeEval getInstance() { + return Singleton.instance; + } - if (sourceCode.get() != null && notebookId.get() != null) { - CompletableFuture future = NotebookSessionManager.getInstance().getSessionFuture(notebookId.get()); + private static class Singleton { - return future.thenCompose(jshell -> { - CompletableFuture> resultFuture = new CompletableFuture<>(); - - CODE_EXECUTOR.submit(() -> { - try { - ByteArrayOutputStream outputStream = NotebookSessionManager.getInstance().getOutputStreamById(notebookId.get()); - ByteArrayOutputStream errorStream = NotebookSessionManager.getInstance().getErrorStreamById(notebookId.get()); + private static final CodeEval instance = new CodeEval(); + } - if (jshell == null) { - resultFuture.completeExceptionally(new ExceptionInInitializerError("Error creating session for notebook")); - return; - } + public BiConsumer outStreamFlushCb = (notebookId, msg) -> { + sendNotification(notebookId, msg, EXECUTION_STATUS.EXECUTING, false); + }; - List results = runCode(jshell, sourceCode.get(), outputStream, errorStream, true); - resultFuture.complete(results); - } catch (Exception e) { - resultFuture.completeExceptionally(e); - } - }); - - return resultFuture; + public BiConsumer errStreamFlushCb = (notebookId, msg) -> { + sendNotification(notebookId, msg, EXECUTION_STATUS.EXECUTING, true); + }; + + public String interrupt(List arguments) { + if (arguments == null) { + LOG.warning("Received null in interrupt execution request"); + return "Arguments list is null"; + } + + String notebookId = NotebookUtils.getArgument(arguments, 0, String.class); + + if (notebookId == null) { + LOG.warning("Received empty notebookId in interrupt execution request"); + return "Empty notebookId received"; + } + + return interruptCodeExecution(notebookId); + } + + private String interruptCodeExecution(String notebookId) { + try { + JShell jshell = NotebookSessionManager.getInstance().getSession(notebookId); + String cellId = activeCellExecutionMapping.get(notebookId); + if (cellId != null) { + sendNotification(notebookId, cellId, EXECUTION_STATUS.INTERRUPTED); + } + flushStreams(notebookId); + List> tasks = pendingTasks.get(notebookId); + if (tasks != null) { + tasks.forEach(task -> { + if (!task.isDone()) { + task.completeExceptionally(new InterruptedException(CODE_EXEC_INTERRUPTED_MESSAGE)); + } }); + tasks.clear(); } - LOG.warning("sourceCode or notebookId are not present in code cell evaluation request"); - } else { - LOG.warning("Empty arguments recevied in code cell evaluate request"); + if (jshell != null) { + jshell.stop(); + } + RequestProcessor executor = codeExecMap.get(notebookId); + if (executor != null) { + executor.shutdownNow(); + codeExecMap.remove(notebookId); + } + activeCellExecutionMapping.remove(notebookId); + + return CODE_EXEC_INTERRUPT_SUCCESS_MESSAGE; + } catch (Exception ex) { + LOG.log(Level.WARNING, "Error during interrupt operation", ex); + return "Error during interrupt: " + ex.getMessage(); + } + } + + public CompletableFuture evaluate(List arguments) { + if (arguments == null) { + LOG.warning("Empty arguments received in code cell evaluate request"); + return CompletableFuture.completedFuture(false); } - return CompletableFuture.completedFuture(new ArrayList<>()); + String notebookId = NotebookUtils.getArgument(arguments, 0, String.class); + String cellId = NotebookUtils.getArgument(arguments, 1, String.class); + String sourceCode = NotebookUtils.getArgument(arguments, 2, String.class); + + if (sourceCode == null || notebookId == null || cellId == null) { + LOG.warning("sourceCode or notebookId or cellId are not present in code cell evaluation request"); + return CompletableFuture.completedFuture(false); + } + + CompletableFuture sessionFuture = NotebookSessionManager.getInstance().getSessionFuture(notebookId); + if (sessionFuture == null) { + LOG.warning("notebook session not found"); + return CompletableFuture.completedFuture(false); + } + + CompletableFuture resultFuture = new CompletableFuture<>(); + pendingTasks.computeIfAbsent(notebookId, k -> new ArrayList<>()).add(resultFuture); + + return sessionFuture.thenCompose(jshell -> { + sendNotification(notebookId, cellId, EXECUTION_STATUS.QUEUED); + getCodeExec(notebookId).submit(() -> codeEvalTaskRunnable(resultFuture, jshell, notebookId, cellId, sourceCode)); + return resultFuture; + }); } - public static List runCode(JShell jshell, String code) { - List results = new ArrayList<>(); + private void codeEvalTaskRunnable(CompletableFuture future, JShell jshell, String notebookId, String cellId, String sourceCode) { try { - List events = jshell.eval(code); - events.forEach(event -> { - if (event.value() != null && !event.value().isEmpty()) { - results.add(ResultEval.text(event.value())); - } - if (event.exception() != null) { - results.add(ResultEval.text(event.exception().getMessage())); - } - }); + activeCellExecutionMapping.put(notebookId, cellId); + sendNotification(notebookId, EXECUTION_STATUS.EXECUTING); + + if (jshell == null) { + future.completeExceptionally(new ExceptionInInitializerError("notebook session not found or closed")); + return; + } + + runCode(jshell, sourceCode, notebookId); + flushStreams(notebookId); + sendNotification(notebookId, EXECUTION_STATUS.SUCCESS); + + future.complete(true); } catch (Exception e) { - LOG.log(Level.SEVERE, "Error while executing code in JShell instance {0}", e.getMessage()); + LOG.log(Level.WARNING, "Exception occurred while code evaluation: " + e.getMessage(), e); + sendNotification(notebookId, EXECUTION_STATUS.FAILURE); + future.completeExceptionally(e); + } finally { + List> tasks = pendingTasks.get(notebookId); + if (tasks != null) { + tasks.remove(future); + } + activeCellExecutionMapping.remove(notebookId); } - return results; } - public static List runCode(JShell jshell, String code, ByteArrayOutputStream outStream, ByteArrayOutputStream errStream, boolean getVariableValues) { - List results = new ArrayList<>(); - try { - String codeLeftToEval = code.trim(); - while (!codeLeftToEval.isEmpty()) { - SourceCodeAnalysis analysis = jshell.sourceCodeAnalysis(); - SourceCodeAnalysis.CompletionInfo info = analysis.analyzeCompletion(codeLeftToEval); - if (info.completeness().isComplete()) { - for (SnippetEvent event : jshell.eval(info.source())) { - if (event.exception() != null) { - results.add(ResultEval.text(event.exception().getMessage())); - } else if (event.value() != null && getVariableValues) { - results.add(ResultEval.text(event.value())); - } + public void runCode(JShell jshell, String code) { + runCode(jshell, code, null); + } - jshell.diagnostics(event.snippet()).forEach(diag - -> results.add(ResultEval.text(diag.getMessage(null))) - ); + public void runCode(JShell jshell, String code, String notebookId) { + try { + SourceCodeAnalysis analysis = jshell.sourceCodeAnalysis(); + List snippets = NotebookUtils.getCodeSnippets(analysis, code); - flushStreams(results, outStream, errStream); + for (String snippet : snippets) { + for (SnippetEvent event : jshell.eval(snippet)) { + if (notebookId != null) { + sendNotification(notebookId, getRuntimeErrors(event), EXECUTION_STATUS.EXECUTING, true); + sendNotification(notebookId, getCompilationErrors(jshell, event), EXECUTION_STATUS.EXECUTING, true); + // TODO: Discuss if diagnostics needs to be given to client as part of excutionResult + if (false) { + sendNotification(notebookId, getSnippetValue(event), EXECUTION_STATUS.EXECUTING, false); + } } - } else { - results.add(ResultEval.text("Code snippet is incomplete")); } - codeLeftToEval = info.remaining(); } - } catch (Exception e) { + } catch (IllegalStateException e) { LOG.log(Level.SEVERE, "Error while evaluation of the code : {0}", e.getMessage()); - results.add(ResultEval.text(("Evaluation error: " + e.getMessage()))); + throw new IllegalStateException(e); + } + } + + private RequestProcessor getCodeExec(String notebookId) { + return codeExecMap.computeIfAbsent(notebookId, (id) -> { + return new RequestProcessor("Jshell Code Evaluator for notebookId: " + id, 1, true, true); + }); + } + + private List getCompilationErrors(JShell jshell, SnippetEvent event) { + List compilationErrors = new ArrayList<>(); + jshell.diagnostics(event.snippet()).forEach(diag -> { + compilationErrors.addAll(displayableDiagnostic(event.snippet().source(), diag)); + }); + + return compilationErrors; + } + + private List getRuntimeErrors(SnippetEvent event) { + List runtimeErrors = new ArrayList<>(); + if (event.exception() != null) { + if (!event.exception().getMessage().isBlank()) { + runtimeErrors.add(event.exception().getMessage()); + } + if (!event.exception().fillInStackTrace().toString().isBlank()) { + runtimeErrors.add(event.exception().fillInStackTrace().toString()); + } } - return results; + + return runtimeErrors; } - private static void flushStreams(List results, ByteArrayOutputStream out, ByteArrayOutputStream err) { - if (out.size() > 0) { - results.add(ResultEval.text(out.toString())); - out.reset(); + private List getSnippetValue(SnippetEvent event) { + List snippetValues = new ArrayList<>(); + if (event.value() != null) { + snippetValues.add(event.value()); } - if (err.size() > 0) { - results.add(ResultEval.text(err.toString())); - err.reset(); + + return snippetValues; + } + + private void flushStreams(String notebookId) { + JshellStreamsHandler streamHandler = NotebookSessionManager.getInstance().getJshellStreamsHandler(notebookId); + if (streamHandler != null) { + streamHandler.flushOutputStreams(); + } + } + + // This method is directly taken from JShell tool implementation in jdk with some minor modifications + private List displayableDiagnostic(String source, Diag diag) { + List toDisplay = new ArrayList<>(); + + for (String line : diag.getMessage(null).split("\\r?\\n")) { + if (!line.trim().startsWith("location:")) { + toDisplay.add(line); + } + } + + int pstart = (int) diag.getStartPosition(); + int pend = (int) diag.getEndPosition(); + if (pstart < 0 || pend < 0) { + pstart = 0; + pend = source.length(); + } + Matcher m = LINEBREAK.matcher(source); + int pstartl = 0; + int pendl = -2; + while (m.find(pstartl)) { + pendl = m.start(); + if (pendl >= pstart) { + break; + } else { + pstartl = m.end(); + } + } + if (pendl < pstartl) { + pendl = source.length(); + } + toDisplay.add(source.substring(pstartl, pendl)); + + StringBuilder sb = new StringBuilder(); + int start = pstart - pstartl; + for (int i = 0; i < start; ++i) { + sb.append(' '); + } + sb.append('^'); + boolean multiline = pend > pendl; + int end = (multiline ? pendl : pend) - pstartl - 1; + if (end > start) { + for (int i = start + 1; i < end; ++i) { + sb.append('-'); + } + if (multiline) { + sb.append("-..."); + } else { + sb.append('^'); + } + } + + toDisplay.add(sb.toString()); + return toDisplay; + } + + private void sendNotification(String notebookId, EXECUTION_STATUS status) { + sendNotification(notebookId, null, null, null, null, status, false); + } + + private void sendNotification(String notebookId, String cellId, EXECUTION_STATUS status) { + sendNotification(notebookId, cellId, null, null, null, status, false); + } + + private void sendNotification(String notebookId, byte[] msg, EXECUTION_STATUS status, boolean isError) { + sendNotification(notebookId, null, msg, null, null, status, isError); + } + + private void sendNotification(String notebookId, List diags, EXECUTION_STATUS status, boolean isError) { + if (diags.isEmpty()) { + return; + } + if (isError) { + sendNotification(notebookId, null, null, null, diags, status, false); + } else { + sendNotification(notebookId, null, null, diags, null, status, false); + } + } + + private void sendNotification(String notebookId, String cellId, byte[] msg, List diags, List errorDiags, EXECUTION_STATUS status, boolean isError) { + try { + if (cellId == null) { + cellId = activeCellExecutionMapping.get(notebookId); + if (cellId == null) { + throw new Exception("Active cell Id not found"); + } + } + + NotebookCellExecutionProgressResultParams params; + if (msg == null) { + if (diags != null) { + params = NotebookCellExecutionProgressResultParams + .builder(notebookId, cellId) + .diagnostics(diags) + .status(status) + .build(); + } else if (errorDiags != null) { + params = NotebookCellExecutionProgressResultParams + .builder(notebookId, cellId) + .errorDiagnostics(errorDiags) + .status(status) + .build(); + } else { + params = NotebookCellExecutionProgressResultParams + .builder(notebookId, cellId) + .status(status) + .build(); + } + } else { + if (isError) { + params = NotebookCellExecutionProgressResultParams + .builder(notebookId, cellId) + .status(status) + .errorStream(CellExecutionResult.text(msg)) + .build(); + } else { + params = NotebookCellExecutionProgressResultParams + .builder(notebookId, cellId) + .status(status) + .outputStream(CellExecutionResult.text(msg)) + .build(); + } + } + + NbCodeLanguageClient client = LanguageClientInstance.getInstance().getClient(); + if (client != null) { + client.notifyNotebookCellExecutionProgress(params); + } + } catch (Exception ex) { + LOG.log(Level.SEVERE, "Some error ocurred while sending code eval notification to the client {0}", ex.getMessage()); } } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CustomInputStream.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CustomInputStream.java new file mode 100644 index 0000000..967aba7 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CustomInputStream.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed 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 + * + * https://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.netbeans.modules.nbcode.java.notebook; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.WeakReference; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.netbeans.modules.java.lsp.server.input.ShowInputBoxParams; +import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient; + +/** + * + * @author atalati + */ +public class CustomInputStream extends InputStream { + + private static final Logger LOG = Logger.getLogger(CustomInputStream.class.getName()); + private ByteArrayInputStream currentStream; + WeakReference client; + private static final String USER_PROMPT_REQUEST = "Please provide scanner input here"; + + public CustomInputStream(NbCodeLanguageClient client) { + this.client = new WeakReference<>(client); + } + + @Override + public synchronized int read(byte[] b, int off, int len) throws IOException { + try { + if (client == null || client.get() == null) { + LOG.log(Level.WARNING, "client is null"); + return -1; + } + + if (currentStream == null || currentStream.available() == 0) { + CompletableFuture future = client.get().showInputBox(new ShowInputBoxParams(USER_PROMPT_REQUEST, "", true)); + String userInput = future.get(); + + if (userInput == null) { + LOG.log(Level.WARNING, "User input is null"); + return -1; + } + + byte[] inputBytes = (userInput + System.lineSeparator()).getBytes(StandardCharsets.UTF_8); + currentStream = new ByteArrayInputStream(inputBytes); + } + + return currentStream.read(b, off, len); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + throw new IOException("Interrupted while waiting for user input", ex); + } catch (ExecutionException ex) { + throw new IOException("Failed to get user input", ex.getCause()); + } + } + + @Override + public int read() throws IOException { + byte[] oneByte = new byte[1]; + int n = read(oneByte, 0, 1); + return (n == -1) ? -1 : oneByte[0] & 0xFF; + } +} diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/JshellStreamsHandler.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/JshellStreamsHandler.java new file mode 100644 index 0000000..7529d55 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/JshellStreamsHandler.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed 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 + * + * https://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.netbeans.modules.nbcode.java.notebook; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Handles JShell output and error streams for notebook execution. + * + * @author atalati + */ +public class JshellStreamsHandler implements AutoCloseable { + private static final Logger LOG = Logger.getLogger(JshellStreamsHandler.class.getName()); + private final String notebookId; + private final StreamingOutputStream outStream; + private final StreamingOutputStream errStream; + private final PrintStream printOutStream; + private final PrintStream printErrStream; + private final InputStream inputStream; + + public JshellStreamsHandler(String notebookId, BiConsumer streamCallback) { + this(notebookId, streamCallback, streamCallback); + } + + public JshellStreamsHandler(String notebookId, + BiConsumer outStreamCallback, + BiConsumer errStreamCallback) { + if (notebookId == null || notebookId.trim().isEmpty()) { + throw new IllegalArgumentException("Notebook Id cannot be null or empty"); + } + + this.notebookId = notebookId; + this.outStream = new StreamingOutputStream(createCallback(outStreamCallback)); + this.errStream = new StreamingOutputStream(createCallback(errStreamCallback)); + this.printOutStream = new PrintStream(outStream); + this.printErrStream = new PrintStream(errStream); + this.inputStream = new CustomInputStream(LanguageClientInstance.getInstance().getClient()); + } + + private Consumer createCallback(BiConsumer callback) { + return callback != null ? output -> callback.accept(notebookId, output) : null; + } + + public void setOutStreamCallback(BiConsumer callback) { + outStream.setCallback(createCallback(callback)); + } + + public void setErrStreamCallback(BiConsumer callback) { + errStream.setCallback(createCallback(callback)); + } + + public PrintStream getPrintOutStream() { + return printOutStream; + } + + public PrintStream getPrintErrStream() { + return printErrStream; + } + + public InputStream getInputStream() { + return inputStream; + } + + public String getNotebookId() { + return notebookId; + } + + public void flushOutputStreams() { + try { + outStream.flush(); + errStream.flush(); + } catch (IOException ignored) { + // nothing can be done + } + } + + @Override + public void close() { + try { + printOutStream.close(); + printErrStream.close(); + outStream.close(); + errStream.close(); + inputStream.close(); + } catch (IOException ex) { + LOG.log(Level.WARNING, "IOException occurred while closing the streams {0}", ex.getMessage()); + } + } +} \ No newline at end of file diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/LanguageClientInstance.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/LanguageClientInstance.java new file mode 100644 index 0000000..e60945f --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/LanguageClientInstance.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed 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 + * + * https://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.netbeans.modules.nbcode.java.notebook; + +import java.lang.ref.WeakReference; +import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient; + +/** + * + * @author atalati + */ +public class LanguageClientInstance { + private WeakReference client = null; + + private LanguageClientInstance() { + } + + public static LanguageClientInstance getInstance() { + return LanguageClientInstance.Singleton.instance; + } + + private static class Singleton { + + private static final LanguageClientInstance instance = new LanguageClientInstance(); + } + + public NbCodeLanguageClient getClient(){ + return this.client == null ? null: this.client.get(); + } + + public void setClient(NbCodeLanguageClient client){ + this.client = new WeakReference<>(client); + } +} diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookCommandsHandler.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookCommandsHandler.java index d36fcf4..757bec8 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookCommandsHandler.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookCommandsHandler.java @@ -32,8 +32,9 @@ public class NotebookCommandsHandler implements CommandProvider { private static final String NBLS_JSHELL_EXEC = "nbls.jshell.execute.cell"; + private static final String NBLS_JSHELL_INTERRUPT = "nbls.jshell.interrupt.cell"; private static final String NBLS_OPEN_PROJECT_JSHELL = "nbls.jshell.project.open"; - private static final Set COMMANDS = new HashSet<>(Arrays.asList(NBLS_JSHELL_EXEC, NBLS_OPEN_PROJECT_JSHELL)); + private static final Set COMMANDS = new HashSet<>(Arrays.asList(NBLS_JSHELL_EXEC, NBLS_OPEN_PROJECT_JSHELL, NBLS_JSHELL_INTERRUPT)); @Override public Set getCommands() { @@ -46,7 +47,9 @@ public CompletableFuture runCommand(String command, List argumen switch (command) { case NBLS_JSHELL_EXEC: - return CodeEval.evaluate(arguments).thenApply(list -> (Object)list); + return CodeEval.getInstance().evaluate(arguments).thenApply(list -> (Object)list); + case NBLS_JSHELL_INTERRUPT: + return CompletableFuture.completedFuture(CodeEval.getInstance().interrupt(arguments)); case NBLS_OPEN_PROJECT_JSHELL: return CommandHandler.openJshellInProjectContext(arguments).thenApply(list -> (Object) list); default: diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java index 9dd54c9..61a0327 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java @@ -34,7 +34,6 @@ public class NotebookConfigs { private static final String[] NOTEBOOK_CONFIG_LABELS = {"notebook.classpath", "notebook.modulepath", "notebook.addmodules", "notebook.enablePreview", "notebook.implicitImports"}; - private NbCodeLanguageClient client = null; private String classPath = null; private String modulePath = null; private String addModules = null; @@ -79,9 +78,7 @@ private static class Singleton { private static final NotebookConfigs instance = new NotebookConfigs(); } - public void setLanguageClient(NbCodeLanguageClient client) { - this.client = client; - + public void initConfigs() { try { this.initialized = initializeConfigs(); } catch (InterruptedException | ExecutionException ex) { @@ -89,23 +86,22 @@ public void setLanguageClient(NbCodeLanguageClient client) { } } - public NbCodeLanguageClient getLanguageClient() { - return client; - } - private List getConfigItems() { List items = new ArrayList<>(); for (String label : NOTEBOOK_CONFIG_LABELS) { ConfigurationItem item = new ConfigurationItem(); - item.setSection(client.getNbCodeCapabilities().getConfigurationPrefix() + label); - items.add(item); + NbCodeLanguageClient client = LanguageClientInstance.getInstance().getClient(); + if (client != null) { + item.setSection(client.getNbCodeCapabilities().getConfigurationPrefix() + label); + items.add(item); + } } return items; } private CompletableFuture initializeConfigs() throws InterruptedException, ExecutionException { + NbCodeLanguageClient client = LanguageClientInstance.getInstance().getClient(); if (client != null) { - CompletableFuture> configValues = client.configuration(new ConfigurationParams(getConfigItems())); return configValues.thenAccept((c) -> { if (c != null) { diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java index 97bfda1..79062a1 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java @@ -77,7 +77,6 @@ public class NotebookDocumentServiceHandlerImpl implements NotebookDocumentServiceHandler { private static final Logger LOG = Logger.getLogger(NotebookDocumentServiceHandler.class.getName()); - private NbCodeLanguageClient client; private final Map notebookStateMap = new ConcurrentHashMap<>(); // Below map is required because completion request doesn't send notebook uri in the params private final Map notebookCellMap = new ConcurrentHashMap<>(); @@ -85,6 +84,10 @@ public class NotebookDocumentServiceHandlerImpl implements NotebookDocumentServi @Override public void didOpen(DidOpenNotebookDocumentParams params) { try { + NbCodeLanguageClient client = LanguageClientInstance.getInstance().getClient(); + if(client == null){ + return; + } client.showStatusBarMessage(new ShowStatusMessageParams(MessageType.Info,"Intializing Java kernel for notebook.")); NotebookSessionManager.getInstance().createSession(params.getNotebookDocument()).whenComplete((JShell jshell,Throwable t) -> { if (t == null) { @@ -139,12 +142,9 @@ public CompletableFuture, CompletionList>> completio @Override public void connect(LanguageClient client) { - this.client = (NbCodeLanguageClient) client; - NotebookConfigs.getInstance().setLanguageClient((NbCodeLanguageClient) client); - } + LanguageClientInstance.getInstance().setClient((NbCodeLanguageClient) client); + NotebookConfigs.getInstance().initConfigs(); - public NbCodeLanguageClient getNbCodeLanguageClient() { - return this.client; } @Override diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java index d877be9..a492915 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java @@ -150,14 +150,17 @@ private void updateNotebookCellContent(NotebookDocumentChangeEventCellTextConten } int newVersion = contentChange.getDocument().getVersion(); String currentContent = cellState.getContent(); - if (!contentChange.getChanges().isEmpty()) { + + try { + String updatedContent = applyContentChanges(currentContent, contentChange.getChanges()); + cellState.setContent(updatedContent, newVersion); + LOG.log(Level.FINE, "Updated content for cell: {0}, version: {1}", new Object[]{uri, newVersion}); + } catch (Exception e) { + LOG.log(Level.WARNING, "applyContentChanges failed, requesting full content: " + uri, e); try { - String updatedContent = applyContentChanges(currentContent, contentChange.getChanges()); - cellState.setContent(updatedContent, newVersion); - LOG.log(Level.FINE, "Updated content for cell: {0}, version: {1}", new Object[]{uri, newVersion}); - } catch (Exception e) { - LOG.log(Level.SEVERE, "Failed to apply content changes to cell: " + uri, e); - throw new RuntimeException("Failed to apply content changes", e); + cellState.requestContentAndSet(); + } catch (Exception ex) { + LOG.log(Level.SEVERE, "Failed to refresh content for cell: " + uri, ex); } } } @@ -217,15 +220,15 @@ private String applyRangeChange(String content, TextDocumentContentChangeEvent c return result.toString(); } - - private void addNewCellState(NotebookCell cell, TextDocumentItem item) { + // protected methods for ease of unit testing + protected void addNewCellState(NotebookCell cell, TextDocumentItem item) { if (cell == null || item == null) { LOG.log(Level.WARNING, "Attempted to add null cell or item"); return; } try { - CellState cellState = new CellState(cell, item); + CellState cellState = new CellState(cell, item, notebookDoc.getUri()); cellsMap.put(item.getUri(), cellState); LOG.log(Level.FINE, "Added new cell state: {0}", item.getUri()); } catch (Exception e) { @@ -233,4 +236,8 @@ private void addNewCellState(NotebookCell cell, TextDocumentItem item) { throw new RuntimeException("Failed to create cell state", e); } } + + protected Map getCellsMap(){ + return cellsMap; + } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java index a8c90cd..15a47ed 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java @@ -15,8 +15,6 @@ */ package org.netbeans.modules.nbcode.java.notebook; -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -44,8 +42,7 @@ public class NotebookSessionManager { private static final String ADD_MODULES = "--add-modules"; private final Map> sessions = new ConcurrentHashMap<>(); - private final Map outputStreams = new ConcurrentHashMap<>(); - private final Map errorStreams = new ConcurrentHashMap<>(); + private final Map jshellStreamsMap = new ConcurrentHashMap<>(); private NotebookSessionManager() { } @@ -59,7 +56,7 @@ private static class Singleton { private static final NotebookSessionManager instance = new NotebookSessionManager(); } - private CompletableFuture jshellBuilder(PrintStream outPrintStream, PrintStream errPrintStream) { + private CompletableFuture jshellBuilder(JshellStreamsHandler streamsHandler) { return CompletableFuture.supplyAsync(() -> { try { NotebookConfigs.getInstance().getInitialized().get(); @@ -72,15 +69,17 @@ private CompletableFuture jshellBuilder(PrintStream outPrintStream, Prin List remoteOptions = getRemoteVmOptions(); if (compilerOptions.isEmpty()) { return JShell.builder() - .out(outPrintStream) - .err(errPrintStream) + .out(streamsHandler.getPrintOutStream()) + .err(streamsHandler.getPrintErrStream()) + .in(streamsHandler.getInputStream()) .compilerOptions() .remoteVMOptions() .build(); } else { return JShell.builder() - .out(outPrintStream) - .err(errPrintStream) + .out(streamsHandler.getPrintOutStream()) + .err(streamsHandler.getPrintErrStream()) + .in(streamsHandler.getInputStream()) .compilerOptions(compilerOptions.toArray(new String[0])) .remoteVMOptions(remoteOptions.toArray(new String[0])) .build(); @@ -91,15 +90,11 @@ private CompletableFuture jshellBuilder(PrintStream outPrintStream, Prin public CompletableFuture createSession(NotebookDocument notebookDoc) { String notebookId = notebookDoc.getUri(); - return sessions.computeIfAbsent(notebookId, (String id) -> { - ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - ByteArrayOutputStream errStream = new ByteArrayOutputStream(); - outputStreams.put(notebookId, outStream); - errorStreams.put(notebookId, errStream); + return sessions.computeIfAbsent(notebookId, id -> { + JshellStreamsHandler handler = new JshellStreamsHandler(id, CodeEval.getInstance().outStreamFlushCb, CodeEval.getInstance().errStreamFlushCb); + jshellStreamsMap.put(id, handler); - PrintStream outPrintStream = new PrintStream(outStream, true); - PrintStream errPrintStream = new PrintStream(errStream, true); - CompletableFuture future = jshellBuilder(outPrintStream, errPrintStream); + CompletableFuture future = jshellBuilder(handler); future.thenAccept(jshell -> onJshellInit(notebookId, jshell)) .exceptionally(ex -> { @@ -171,10 +166,10 @@ private void onJshellInit(String notebookId, JShell jshell) { List packages = NotebookConfigs.getInstance().getImplicitImports(); if (packages != null && !packages.isEmpty()) { - packages.forEach(pkg -> CodeEval.runCode(jshell, "import " + pkg)); + packages.forEach(pkg -> CodeEval.getInstance().runCode(jshell, "import " + pkg)); } else { List.of("java.util", "java.io", "java.math") - .forEach(pkg -> CodeEval.runCode(jshell, "import " + pkg + ".*")); + .forEach(pkg -> CodeEval.getInstance().runCode(jshell, "import " + pkg + ".*")); } } @@ -195,12 +190,8 @@ public JShell getSession(String notebookId) { return null; } - public ByteArrayOutputStream getOutputStreamById(String notebookId) { - return outputStreams.get(notebookId); - } - - public ByteArrayOutputStream getErrorStreamById(String notebookId) { - return errorStreams.get(notebookId); + public JshellStreamsHandler getJshellStreamsHandler(String notebookId) { + return jshellStreamsMap.get(notebookId); } public void closeSession(String notebookUri) { @@ -209,7 +200,9 @@ public void closeSession(String notebookUri) { if (jshell != null) { jshell.close(); } - outputStreams.remove(notebookUri); - errorStreams.remove(notebookUri); + JshellStreamsHandler handler = jshellStreamsMap.remove(notebookUri); + if (handler != null) { + handler.close(); + } } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtils.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtils.java index bda0a6b..48a8263 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtils.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtils.java @@ -15,6 +15,14 @@ */ package org.netbeans.modules.nbcode.java.notebook; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import java.util.ArrayList; +import java.util.List; +import jdk.jshell.SourceCodeAnalysis; import org.eclipse.lsp4j.Position; /** @@ -93,8 +101,61 @@ public static Position getPosition(String content, int offset) { return new Position(line, character); } - + public static boolean checkEmptyString(String input) { return (input == null || input.trim().isEmpty()); } + + @SuppressWarnings("unchecked") + public static T getArgument(List arguments, int index, Class type) { + if (arguments != null && arguments.size() > index && arguments.get(index) != null) { + Object arg = arguments.get(index); + + if (arg instanceof JsonElement) { + JsonElement jsonElement = (JsonElement) arg; + if (jsonElement.isJsonNull()) { + return null; + } + + if (type == String.class && jsonElement.isJsonPrimitive() && jsonElement.getAsJsonPrimitive().isString()) { + return (T) jsonElement.getAsString(); + } else if (type == Number.class && jsonElement.isJsonPrimitive() && jsonElement.getAsJsonPrimitive().isNumber()) { + return (T) jsonElement.getAsNumber(); + } else if (type == Boolean.class && jsonElement.isJsonPrimitive() && jsonElement.getAsJsonPrimitive().isBoolean()) { + return (T) Boolean.valueOf(jsonElement.getAsBoolean()); + } else if (type == JsonObject.class && jsonElement.isJsonObject()) { + return (T) jsonElement.getAsJsonObject(); + } else if (type == JsonArray.class && jsonElement.isJsonArray()) { + return (T) jsonElement.getAsJsonArray(); + } else if (type == JsonPrimitive.class && jsonElement.isJsonPrimitive()) { + return (T) jsonElement.getAsJsonPrimitive(); + } else if (type == JsonNull.class && jsonElement.isJsonNull()) { + return (T) JsonNull.INSTANCE; + } + } + + if (type.isInstance(arg)) { + return type.cast(arg); + } + } + return null; + } + + public static List getCodeSnippets(SourceCodeAnalysis analysis, String code) { + String codeRemaining = code.trim(); + + List codeSnippets = new ArrayList<>(); + while (!codeRemaining.isEmpty()) { + SourceCodeAnalysis.CompletionInfo info = analysis.analyzeCompletion(codeRemaining); + if (info.completeness().isComplete()) { + codeSnippets.add(info.source()); + } else { + codeSnippets.add(codeRemaining); + break; + } + codeRemaining = info.remaining().trim(); + } + + return codeSnippets; + } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/ResultEval.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/ResultEval.java deleted file mode 100644 index 3000959..0000000 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/ResultEval.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2025, Oracle and/or its affiliates. - * - * Licensed 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 - * - * https://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.netbeans.modules.nbcode.java.notebook; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.net.URLConnection; - -/** - * - * @author atalati - */ -public class ResultEval { - - private final String data; - private final String mimeType; - - public ResultEval(String rawData, String mimeType) { - this.data = rawData; - this.mimeType = mimeType; - } - - public String getData() { - return data; - } - - public String getMimeType() { - return mimeType; - } - - public static ResultEval text(String data) { - return new ResultEval(data, "text/plain"); - } - - private String detectMime(byte[] data) { - try (ByteArrayInputStream in = new ByteArrayInputStream(data)) { - String detected = URLConnection.guessContentTypeFromStream(in); - return detected != null ? detected : "text/plain"; - } catch (IOException ex) { - return "text/plain"; - } - } - - private String detectMime(ByteArrayOutputStream data) { - return detectMime(data.toByteArray()); - } -} diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/StreamingOutputStream.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/StreamingOutputStream.java new file mode 100644 index 0000000..09dbd13 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/StreamingOutputStream.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed 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 + * + * https://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.netbeans.modules.nbcode.java.notebook; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import org.openide.util.RequestProcessor; +import org.openide.util.RequestProcessor.Task; + +/** + * + * @author atalati + */ +public class StreamingOutputStream extends OutputStream { + + private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + private Consumer callback; + private static final int MAX_BUFFER_SIZE = 1024; + private final AtomicBoolean isPeriodicFlushOutputStream; + + static RequestProcessor getRequestProcessor() { + return RPSingleton.instance; + } + + private static int getRequestPeriodicTime() { + return RPSingleton.PERIODIC_TIME; + } + + private static final class RPSingleton { + + private static final RequestProcessor instance = new RequestProcessor(StreamingOutputStream.class.getName(), 1, true, false); + private static final int PERIODIC_TIME = 100; + } + + public StreamingOutputStream(Consumer callback) { + this.callback = callback; + this.isPeriodicFlushOutputStream = new AtomicBoolean(true); + createAndScheduleTask(); + } + + @Override + public synchronized void write(int b) throws IOException { + buffer.write(b); + ifBufferOverflowFlush(); + } + + @Override + public synchronized void write(byte[] b, int off, int len) throws IOException { + buffer.write(b, off, len); + ifBufferOverflowFlush(); + } + + @Override + public synchronized void flush() throws IOException { + flushToCallback(); + } + + @Override + public synchronized void write(byte[] b) throws IOException { + buffer.write(b); + ifBufferOverflowFlush(); + } + + @Override + public synchronized void close() throws IOException { + flushToCallback(); + isPeriodicFlushOutputStream.set(false); + super.close(); + } + + public void setCallback(Consumer cb) { + this.callback = cb; + } + + private void ifBufferOverflowFlush() { + if (buffer.size() > MAX_BUFFER_SIZE) { + flushToCallback(); + } + } + + private synchronized void flushToCallback() { + if (buffer.size() > 0) { + byte[] output = buffer.toByteArray(); + buffer.reset(); + callback.accept(output); + } + } + + private void createAndScheduleTask() { + if (isPeriodicFlushOutputStream.get()) { + Task task = getRequestProcessor().create(() -> { + flushToCallback(); + createAndScheduleTask(); + }); + task.schedule(getRequestPeriodicTime()); + } + } +} diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/CommandHandler.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/CommandHandler.java index 07b5560..f88cf95 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/CommandHandler.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/CommandHandler.java @@ -17,15 +17,14 @@ import com.google.gson.JsonPrimitive; import java.net.MalformedURLException; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; -import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.Project; import org.netbeans.modules.java.lsp.server.Utils; +import org.netbeans.modules.nbcode.java.notebook.NotebookUtils; import org.openide.filesystems.FileObject; import org.openide.util.Exceptions; @@ -41,16 +40,14 @@ public static CompletableFuture> openJshellInProjectContext(List> future = new CompletableFuture<>(); LOG.log(Level.FINER, "Request received for opening Jshell instance with project context {0}", args); - - final String uri = args != null && args.get(0) != null && args.get(0) instanceof JsonPrimitive - ? ((JsonPrimitive) args.get(0)).getAsString() - : null; + String uri = NotebookUtils.getArgument(args, 0, String.class); + if (uri == null) { future.completeExceptionally(new IllegalArgumentException("uri is required. It cannot be null")); return future; } - + Project prj = getProject(uri); if (prj != null) { return ProjectConfigurationUtils.buildProject(prj) @@ -73,19 +70,15 @@ public static CompletableFuture> openJshellInProjectContext(List args) { - LOG.log(Level.FINER, "Request received for opening Jshell instance with project context {0}", args); + LOG.log(Level.FINER, "Request received for opening notebook with project context {0}", args); - String uri = null, notebookUri = null; - if (args != null && !args.isEmpty() && args.get(0) != null && args.get(0) instanceof JsonPrimitive) { - uri = ((JsonPrimitive) args.get(0)).getAsString(); - } - if (args != null && args.size() > 1 && args.get(1) != null && args.get(1) instanceof JsonPrimitive) { - notebookUri = ((JsonPrimitive) args.get(1)).getAsString(); - } + String uri = NotebookUtils.getArgument(args, 0, String.class); + String notebookUri = NotebookUtils.getArgument(args, 1, String.class); + Project prj = getProject(uri); if (prj != null) { List remoteVmOptions = ProjectConfigurationUtils.launchVMOptions(prj); - List compileOptions = ProjectConfigurationUtils.compileOptions(prj); + List compileOptions = ProjectConfigurationUtils.compilerOptions(prj); LOG.log(Level.INFO, "Opened Notebook instance with project context {0}", uri); return true; diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectConfigurationUtils.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectConfigurationUtils.java index bc79482..db223ee 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectConfigurationUtils.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectConfigurationUtils.java @@ -142,7 +142,7 @@ public static List launchVMOptions(Project project) { } @NonNull - public static List compileOptions(Project project) { + public static List compilerOptions(Project project) { if (project == null) { return new ArrayList<>(); } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectModulePathConfigurationUtils.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectModulePathConfigurationUtils.java index 6b8b48d..14bde96 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectModulePathConfigurationUtils.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectModulePathConfigurationUtils.java @@ -251,7 +251,6 @@ public static List getVmOptions(Project project) { )); vmOptions.addAll(getModuleConfigurations(project)); - } return vmOptions; diff --git a/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/CellStateTest.java b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/CellStateTest.java new file mode 100644 index 0000000..57c4b52 --- /dev/null +++ b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/CellStateTest.java @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed 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 + * + * https://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.netbeans.modules.nbcode.java.notebook; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import org.eclipse.lsp4j.ExecutionSummary; +import org.eclipse.lsp4j.NotebookCell; +import org.eclipse.lsp4j.NotebookCellKind; +import org.eclipse.lsp4j.TextDocumentItem; +import org.netbeans.modules.java.lsp.server.notebook.CellStateResponse; +import org.junit.Before; +import org.junit.Test; +import static org.junit.Assert.*; +import org.netbeans.junit.NbTestCase; + +public class CellStateTest extends NbTestCase{ + + private static final String NOTEBOOK_URI = "file:///path/to/notebook.ijnb"; + private static final String CELL_URI = "notebook-cell:/path/to/notebook.ijnb#ch0,cell0"; + private static final String INITIAL_CONTENT = "initial content"; + private static final String LANGUAGE_ID = "java"; + + private NotebookCell notebookCell; + private TextDocumentItem textDocumentItem; + + public CellStateTest(String name) { + super(name); + } + + @Before + @Override + public void setUp() { + ExecutionSummary summary = new ExecutionSummary(); + summary.setExecutionOrder(1); + + notebookCell = new NotebookCell(NotebookCellKind.Code, CELL_URI); + notebookCell.setExecutionSummary(summary); + notebookCell.setMetadata("initial metadata"); + + textDocumentItem = new TextDocumentItem(CELL_URI, LANGUAGE_ID, 1, INITIAL_CONTENT); + } + + /** + * Test that the constructor correctly initializes all fields of the + * CellState. + */ + @Test + public void testConstructorInitialization() { + CellState cellState = new CellState(notebookCell, textDocumentItem, NOTEBOOK_URI); + + assertEquals("Cell type should be CODE", NotebookCellKind.Code, cellState.getType()); + assertEquals("Language ID should be 'java'", LANGUAGE_ID, cellState.getLanguage()); + assertEquals("Cell URI should match", CELL_URI, cellState.getCellUri()); + assertEquals("Notebook URI should match", NOTEBOOK_URI, cellState.getNotebookUri()); + assertEquals("Content should be initialized", INITIAL_CONTENT, cellState.getContent()); + assertEquals("Initial version should be 1", 1, cellState.getVersionAwareContent().getVersion()); + assertEquals("Metadata should be initialized", "initial metadata", cellState.getMetadata()); + assertNotNull("Execution summary should not be null", cellState.getExecutionSummary()); + assertEquals("Execution order should be 1", 1, cellState.getExecutionSummary().getExecutionOrder()); + } + + /** + * Test a simple, successful content update where the new version is + * sequential. + */ + @Test + public void testSetContentSequentialUpdate() throws InterruptedException, ExecutionException { + CellState cellState = new CellState(notebookCell, textDocumentItem, NOTEBOOK_URI); + String newContent = "updated content"; + int newVersion = 2; + + cellState.setContent(newContent, newVersion); + + assertEquals("Content should be updated", newContent, cellState.getContent()); + assertEquals("Version should be updated", newVersion, cellState.getVersionAwareContent().getVersion()); + } + + /** + * Test that an attempt to update with a stale (older or same) version is + * ignored. + */ + @Test + public void testSetContentStaleUpdateIsIgnored() throws InterruptedException, ExecutionException { + CellState cellState = new CellState(notebookCell, textDocumentItem, NOTEBOOK_URI); + + cellState.setContent("stale content v1", 1); + assertEquals("Content should not change for same version", INITIAL_CONTENT, cellState.getContent()); + assertEquals("Version should not change for same version", 1, cellState.getVersionAwareContent().getVersion()); + + cellState.setContent("stale content v0", 0); + assertEquals("Content should not change for older version", INITIAL_CONTENT, cellState.getContent()); + assertEquals("Version should not change for older version", 1, cellState.getVersionAwareContent().getVersion()); + } + + /** + * Test the scenario where there is a version gap, triggering a successful + * fetch from the client to synchronize the state. + */ + @Test + public void testSetContentWithVersionGapSuccess() throws InterruptedException, ExecutionException { + String latestContentFromServer = "latest content from server"; + int latestVersionFromServer = 5; + + TestableCellState cellState = new TestableCellState( + notebookCell, textDocumentItem, NOTEBOOK_URI, + latestContentFromServer, latestVersionFromServer + ); + + cellState.setContent("a newer content", 3); + + assertEquals("Content should be synchronized from client", latestContentFromServer, cellState.getContent()); + assertEquals("Version should be synchronized from client", latestVersionFromServer, cellState.getVersionAwareContent().getVersion()); + } + + /** + * Test the scenario where a version gap triggers a client fetch, but the + * client returns a version that is not newer than the current one, causing + * an exception. + */ + @Test + public void testSetContentWithVersionGapMismatchOnFetch() throws InterruptedException { + String staleContentFromServer = "stale content from server"; + int staleVersionFromServer = 1; + + TestableCellState cellState = new TestableCellState( + notebookCell, textDocumentItem, NOTEBOOK_URI, + staleContentFromServer, staleVersionFromServer + ); + + assertThrows(IllegalStateException.class, () -> cellState.setContent("a newer content", 3)); + assertEquals("Content should remain unchanged after failed fetch", INITIAL_CONTENT, cellState.getContent()); + assertEquals("Version should remain unchanged after failed fetch", 1, cellState.getVersionAwareContent().getVersion()); + } + + /** + * Test that a direct request to fetch and set content works correctly. + */ + @Test + public void testRequestContentAndSetSuccess() throws InterruptedException, ExecutionException { + String latestContentFromServer = "content from direct request"; + int latestVersionFromServer = 10; + + TestableCellState cellState = new TestableCellState( + notebookCell, textDocumentItem, NOTEBOOK_URI, + latestContentFromServer, latestVersionFromServer + ); + + cellState.requestContentAndSet(); + + assertEquals("Content should be updated from direct request", latestContentFromServer, cellState.getContent()); + assertEquals("Version should be updated from direct request", latestVersionFromServer, cellState.getVersionAwareContent().getVersion()); + } + + /** + * Test that requestContentAndSet throws an exception if the server provides + * an invalid version number (e.g., 0 or negative). + */ + @Test + public void testRequestContentAndSetWithInvalidVersionFromServer() throws InterruptedException { + String content = "some content"; + int invalidVersion = 0; + + TestableCellState cellState = new TestableCellState( + notebookCell, textDocumentItem, NOTEBOOK_URI, + content, invalidVersion + ); + + assertThrows(IllegalStateException.class, () -> cellState.requestContentAndSet()); + assertEquals("Content should remain unchanged after invalid fetch", INITIAL_CONTENT, cellState.getContent()); + assertEquals("Version should remain unchanged after invalid fetch", 1, cellState.getVersionAwareContent().getVersion()); + } + + /** + * Tests simple setters for metadata and execution summary. + */ + @Test + public void testSettersForMetadataAndExecutionSummary() { + CellState cellState = new CellState(notebookCell, textDocumentItem, NOTEBOOK_URI); + + String newMetadata = "updated metadata"; + cellState.setMetadata(newMetadata); + assertEquals("Metadata should be updated", newMetadata, cellState.getMetadata()); + + ExecutionSummary newSummary = new ExecutionSummary(); + newSummary.setExecutionOrder(100); + cellState.setExecutionSummary(newSummary); + assertEquals("ExecutionSummary should be updated", 100, cellState.getExecutionSummary().getExecutionOrder()); + } + + private static class TestableCellState extends CellState { + + private final String mockResponseText; + private final int mockResponseVersion; + + TestableCellState(NotebookCell notebookCell, TextDocumentItem details, String notebookUri, + String mockResponseText, int mockResponseVersion) { + super(notebookCell, details, notebookUri); + this.mockResponseText = mockResponseText; + this.mockResponseVersion = mockResponseVersion; + } + + @Override + protected CompletableFuture requestLatestCellState() { + CellStateResponse mockResponse = new CellStateResponse(mockResponseText, mockResponseVersion); + return CompletableFuture.completedFuture(mockResponse); + } + } +} diff --git a/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManagerTest.java b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManagerTest.java new file mode 100644 index 0000000..74ad8e6 --- /dev/null +++ b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManagerTest.java @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may 'ou may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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.netbeans.modules.nbcode.java.notebook; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import org.eclipse.lsp4j.ExecutionSummary; +import org.eclipse.lsp4j.NotebookCell; +import org.eclipse.lsp4j.NotebookCellArrayChange; +import org.eclipse.lsp4j.NotebookCellKind; +import org.eclipse.lsp4j.NotebookDocument; +import org.eclipse.lsp4j.NotebookDocumentChangeEvent; +import org.eclipse.lsp4j.NotebookDocumentChangeEventCells; +import org.eclipse.lsp4j.NotebookDocumentChangeEventCellStructure; +import org.eclipse.lsp4j.NotebookDocumentChangeEventCellTextContent; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.TextDocumentContentChangeEvent; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.eclipse.lsp4j.TextDocumentItem; +import org.eclipse.lsp4j.VersionedNotebookDocumentIdentifier; +import org.eclipse.lsp4j.VersionedTextDocumentIdentifier; +import org.junit.Before; +import org.junit.Test; +import org.netbeans.junit.NbTestCase; +import org.netbeans.modules.java.lsp.server.notebook.CellStateResponse; + +public class NotebookDocumentStateManagerTest extends NbTestCase { + + private static final String NOTEBOOK_URI = "file:///path/to/notebook.ipynb"; + private NotebookDocument notebookDoc; + private List initialCells; + private NotebookDocumentStateManager manager; + + public NotebookDocumentStateManagerTest(String name) { + super(name); + } + + @Before + @Override + public void setUp() { + notebookDoc = new NotebookDocument(NOTEBOOK_URI, "java-notebook", 1, new ArrayList<>()); + initialCells = new ArrayList<>(); + + addCell("cell1_uri", "System.out.println(\"Hello\");", 1, notebookDoc, initialCells); + addCell("cell2_uri", "int x = 10;", 1, notebookDoc, initialCells); + + manager = new NotebookDocumentStateManager(notebookDoc, initialCells); + } + + private void addCell(String uri, String content, int version, NotebookDocument doc, List items) { + NotebookCell cell = new NotebookCell(NotebookCellKind.Code, uri); + cell.setDocument(uri); + doc.getCells().add(cell); + items.add(new TextDocumentItem(uri, "java", version, content)); + } + + private NotebookCell getCell(String uri) { + NotebookCell cell = new NotebookCell(NotebookCellKind.Code, uri); + cell.setDocument(uri); + return cell; + } + + @Test + public void testConstructorInitialization() { + assertNotNull("Manager should not be null", manager); + assertEquals("Should have 2 cells initially", 2, manager.getNotebookDocument().getCells().size()); + assertNotNull("Cell 1 should exist", manager.getCell("cell1_uri")); + assertNotNull("Cell 2 should exist", manager.getCell("cell2_uri")); + assertEquals("Cell 1 content should match", "System.out.println(\"Hello\");", manager.getCell("cell1_uri").getContent()); + } + + @Test + public void testSyncState_AddCell() { + NotebookDocumentChangeEvent event = new NotebookDocumentChangeEvent(); + NotebookDocumentChangeEventCells cellsChange = new NotebookDocumentChangeEventCells(); + NotebookDocumentChangeEventCellStructure structureChange = new NotebookDocumentChangeEventCellStructure(); + cellsChange.setStructure(structureChange); + event.setCells(cellsChange); + + NotebookCell newNotebookCell = new NotebookCell(NotebookCellKind.Code, "cell3_uri"); + newNotebookCell.setDocument("cell3_uri"); + TextDocumentItem newTextItem = new TextDocumentItem("cell3_uri", "java", 1, "new cell content"); + + NotebookCellArrayChange arrayChange = new NotebookCellArrayChange(1, 0, Collections.singletonList(newNotebookCell)); + structureChange.setArray(arrayChange); + structureChange.setDidOpen(Collections.singletonList(newTextItem)); + + manager.syncState(new VersionedNotebookDocumentIdentifier(2, NOTEBOOK_URI), event, new HashMap<>()); + + assertEquals("Should have 3 cells after adding", 3, manager.getCellsMap().size()); + assertNotNull("New cell should exist", manager.getCell("cell3_uri")); + assertEquals("New cell content should match", "new cell content", manager.getCell("cell3_uri").getContent()); + } + + @Test + public void testSyncState_RemoveCell() { + NotebookDocumentChangeEvent event = new NotebookDocumentChangeEvent(); + NotebookDocumentChangeEventCells cellsChange = new NotebookDocumentChangeEventCells(); + NotebookDocumentChangeEventCellStructure structureChange = new NotebookDocumentChangeEventCellStructure(); + cellsChange.setStructure(structureChange); + event.setCells(cellsChange); + + NotebookCellArrayChange arrayChange = new NotebookCellArrayChange(0, 1, List.of()); + structureChange.setArray(arrayChange); + structureChange.setDidClose(Collections.singletonList(new TextDocumentIdentifier("cell1_uri"))); + structureChange.setDidOpen(List.of()); + + manager.syncState(new VersionedNotebookDocumentIdentifier(2, NOTEBOOK_URI), event, new HashMap<>()); + + assertNull("Cell 1 should be removed", manager.getCell("cell1_uri")); + assertNotNull("Cell 2 should still exist", manager.getCell("cell2_uri")); + } + + @Test + public void testSyncState_UpdateCellData() { + NotebookDocumentChangeEvent event = new NotebookDocumentChangeEvent(); + NotebookCell cellToUpdate = new NotebookCell(NotebookCellKind.Code, "cell1_uri"); + cellToUpdate.setDocument("cell1_uri"); + cellToUpdate.setMetadata("new metadata"); + ExecutionSummary summary = new ExecutionSummary(); + summary.setExecutionOrder(10); + cellToUpdate.setExecutionSummary(summary); + + NotebookDocumentChangeEventCells cellsChange = new NotebookDocumentChangeEventCells(); + cellsChange.setData(Collections.singletonList(cellToUpdate)); + event.setCells(cellsChange); + + manager.syncState(new VersionedNotebookDocumentIdentifier(2, NOTEBOOK_URI), event, new HashMap<>()); + + CellState cellState = manager.getCell("cell1_uri"); + assertEquals("Metadata should be updated", "new metadata", cellState.getMetadata()); + assertEquals("Execution order should be updated", 10, cellState.getExecutionSummary().getExecutionOrder()); + } + + @Test + public void testSyncState_UpdateCellContent_Incremental() { + NotebookDocumentChangeEvent event = new NotebookDocumentChangeEvent(); + NotebookDocumentChangeEventCellTextContent textChange = new NotebookDocumentChangeEventCellTextContent(); + textChange.setDocument(new VersionedTextDocumentIdentifier("cell2_uri", 2)); + + TextDocumentContentChangeEvent contentChange = new TextDocumentContentChangeEvent( + new Range(new Position(0, 0), new Position(0, 0)), + "final " + ); + textChange.setChanges(Collections.singletonList(contentChange)); + + NotebookDocumentChangeEventCells cellsChange = new NotebookDocumentChangeEventCells(); + cellsChange.setTextContent(Collections.singletonList(textChange)); + event.setCells(cellsChange); + + manager.syncState(new VersionedNotebookDocumentIdentifier(2, NOTEBOOK_URI), event, new HashMap<>()); + + assertEquals("Content should be updated incrementally", "final int x = 10;", manager.getCell("cell2_uri").getContent()); + } + + @Test + public void testSyncState_UpdateCellContent_FailureAndFallback() throws InterruptedException, ExecutionException { + TestableNotebookDocumentStateManager testManager = new TestableNotebookDocumentStateManager(notebookDoc, initialCells); + + String fallbackContent = "content from fallback"; + TestableCellState.setMockResponseForUri("cell1_uri", fallbackContent, 5); + + NotebookDocumentChangeEvent event = new NotebookDocumentChangeEvent(); + NotebookDocumentChangeEventCellTextContent textChange = new NotebookDocumentChangeEventCellTextContent(); + textChange.setDocument(new VersionedTextDocumentIdentifier("cell1_uri", 2)); + TextDocumentContentChangeEvent contentChange = new TextDocumentContentChangeEvent( + new Range(new Position(99, 0), new Position(99, 0)), // Invalid line number + "this will fail" + ); + textChange.setChanges(Collections.singletonList(contentChange)); + + NotebookDocumentChangeEventCells cellsChange = new NotebookDocumentChangeEventCells(); + cellsChange.setTextContent(Collections.singletonList(textChange)); + event.setCells(cellsChange); + + testManager.syncState(new VersionedNotebookDocumentIdentifier(2, NOTEBOOK_URI), event, new HashMap<>()); + + assertEquals("Content should be updated from fallback", fallbackContent, testManager.getCell("cell1_uri").getContent()); + + TestableCellState.clearMockResponses(); + } + + private static class TestableNotebookDocumentStateManager extends NotebookDocumentStateManager { + + public TestableNotebookDocumentStateManager(NotebookDocument notebookDoc, List cells) { + super(notebookDoc, cells); + } + + @Override + protected void addNewCellState(NotebookCell cell, TextDocumentItem item) { + if (cell == null || item == null) { + return; + } + CellState cellState = new TestableCellState(cell, item, getNotebookDocument().getUri()); + getCellsMap().put(item.getUri(), cellState); + } + } + + private static class TestableCellState extends CellState { + + private static final Map mockResponses = new HashMap<>(); + + public static void setMockResponseForUri(String uri, String text, int version) { + mockResponses.put(uri, new CellStateResponse(text, version)); + } + + public static void clearMockResponses() { + mockResponses.clear(); + } + + TestableCellState(NotebookCell notebookCell, TextDocumentItem details, String notebookUri) { + super(notebookCell, details, notebookUri); + } + + @Override + protected CompletableFuture requestLatestCellState() { + CellStateResponse mockResponse = mockResponses.get(this.getCellUri()); + if (mockResponse != null) { + return CompletableFuture.completedFuture(mockResponse); + } + return CompletableFuture.completedFuture(new CellStateResponse("", -1)); + } + } +} diff --git a/patches/java-notebooks.diff b/patches/java-notebooks.diff index 2db3f5a..97a9cde 100644 --- a/patches/java-notebooks.diff +++ b/patches/java-notebooks.diff @@ -1,6 +1,6 @@ --- /dev/null -+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/notebook/NotebookCellContentRequestParams.java -@@ -0,0 +1,121 @@ ++++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/notebook/CellExecutionResult.java +@@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file @@ -20,38 +20,220 @@ + * under the License. + */ +package org.netbeans.modules.java.lsp.server.notebook; ++ ++import java.io.ByteArrayInputStream; ++import java.io.IOException; ++import java.net.URLConnection; ++import java.util.Arrays; ++ ++/** ++ * ++ * @author atalati ++ */ ++public class CellExecutionResult { ++ private final byte[] data; ++ private final String mimeType; ++ ++ public CellExecutionResult(byte[] rawData, String mimeType) { ++ this.data = rawData; ++ this.mimeType = mimeType; ++ } ++ ++ public byte[] getData() { ++ return Arrays.copyOf(data, data.length); ++ } ++ ++ public String getMimeType() { ++ return mimeType; ++ } ++ ++ public static CellExecutionResult text(byte[] data) { ++ return new CellExecutionResult(data, "text/plain"); ++ } ++ ++ public static CellExecutionResult detectMimeAndGetResult(byte[] data) { ++ return new CellExecutionResult(data, detectMime(data)); ++ } ++ ++ private static String detectMime(byte[] data) { ++ try (ByteArrayInputStream in = new ByteArrayInputStream(data)) { ++ String detected = URLConnection.guessContentTypeFromStream(in); ++ return detected != null ? detected : "text/plain"; ++ } catch (IOException ex) { ++ return "text/plain"; ++ } ++ } ++} +--- /dev/null ++++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/notebook/NotebookCellExecutionProgressResultParams.java +@@ -0,0 +1,301 @@ ++/* ++ * 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.netbeans.modules.java.lsp.server.notebook; ++ ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.List; +import java.util.Objects; +import org.eclipse.lsp4j.jsonrpc.validation.NonNull; +import org.eclipse.lsp4j.util.Preconditions; +import org.eclipse.xtext.xbase.lib.Pure; +import org.eclipse.xtext.xbase.lib.util.ToStringBuilder; ++ +/** + * + * @author atalati + */ -+@SuppressWarnings("all") -+public class NotebookCellContentRequestParams { ++public class NotebookCellExecutionProgressResultParams { ++ ++ public static enum EXECUTION_STATUS { ++ QUEUED, ++ EXECUTING, ++ SUCCESS, ++ FAILURE, ++ INTERRUPTED ++ } + /** + * URI of the notebook. + */ + @NonNull -+ private String notebookUri; -+ ++ private final String notebookUri; ++ + /** + * URI of the cell. + */ + @NonNull -+ private String cellUri; -+ -+ public NotebookCellContentRequestParams() { -+ this("", ""); -+ } -+ -+ public NotebookCellContentRequestParams(@NonNull final String notebookUri, @NonNull final String cellUri) { ++ private final String cellUri; ++ ++ /** ++ * cell execution status ++ */ ++ private String status; ++ ++ /** ++ * outputStream of the cell. ++ */ ++ private CellExecutionResult outputStream; ++ ++ /** ++ * errorStream of the cell. ++ */ ++ private CellExecutionResult errorStream; ++ ++ /** ++ * Diagnostics of the snippet ran. ++ */ ++ private List diagnostics; ++ ++ /** ++ * Errors occurred while running or compilation of the snippet. ++ */ ++ private List errorDiagnostics; ++ ++ /** ++ * add metadata about the execution. ++ */ ++ private Object metadata; ++ ++ private NotebookCellExecutionProgressResultParams( ++ @NonNull final String notebookUri, ++ @NonNull final String cellUri, ++ final EXECUTION_STATUS status, ++ final CellExecutionResult outputStream, ++ final CellExecutionResult errorStream, ++ final List diagnostics, ++ final List errorDiagnostics, ++ final Object metadata) { + this.notebookUri = Preconditions.checkNotNull(notebookUri, "notebookUri"); + this.cellUri = Preconditions.checkNotNull(cellUri, "cellUri"); ++ this.status = status != null ? status.name() : null; ++ this.outputStream = outputStream; ++ this.errorStream = errorStream; ++ this.diagnostics = diagnostics; ++ this.errorDiagnostics = errorDiagnostics; ++ this.metadata = metadata; + } -+ ++ ++ public static Builder builder(@NonNull String notebookUri, @NonNull String cellUri) { ++ return new Builder(notebookUri, cellUri); ++ } ++ ++ public static class Builder { ++ ++ @NonNull ++ private final String notebookUri; ++ @NonNull ++ private final String cellUri; ++ private EXECUTION_STATUS status; ++ private CellExecutionResult outputStream; ++ private CellExecutionResult errorStream; ++ private List diagnostics; ++ private List errorDiagnostics; ++ private Object metadata; ++ ++ private Builder(@NonNull String notebookUri, @NonNull String cellUri) { ++ this.notebookUri = notebookUri; ++ this.cellUri = cellUri; ++ } ++ ++ public Builder status(EXECUTION_STATUS status) { ++ this.status = status; ++ return this; ++ } ++ ++ public Builder outputStream(CellExecutionResult outputStream) { ++ this.outputStream = outputStream; ++ return this; ++ } ++ ++ public Builder errorStream(CellExecutionResult errorStream) { ++ this.errorStream = errorStream; ++ return this; ++ } ++ ++ public Builder diagnostics(List diagnostics) { ++ this.diagnostics = diagnostics != null ? new ArrayList<>(diagnostics) : null; ++ return this; ++ } ++ ++ public Builder errorDiagnostics(List errorDiagnostics) { ++ this.errorDiagnostics = errorDiagnostics != null ? new ArrayList<>(errorDiagnostics) : null; ++ return this; ++ } ++ ++ public Builder metadata(Object metadata) { ++ this.metadata = metadata; ++ return this; ++ } ++ ++ public NotebookCellExecutionProgressResultParams build() { ++ return new NotebookCellExecutionProgressResultParams(notebookUri, ++ cellUri, ++ status, ++ outputStream, ++ errorStream, ++ diagnostics, ++ errorDiagnostics, ++ metadata); ++ } ++ } ++ + /** + * URI of the notebook. + */ @@ -60,14 +242,7 @@ + public String getNotebookUri() { + return notebookUri; + } -+ -+ /** -+ * URI of the notebook. -+ */ -+ public void setNotebookUri(@NonNull final String notebookUri) { -+ this.notebookUri = Preconditions.checkNotNull(notebookUri, "notebookUri"); -+ } -+ ++ + /** + * URI of the cell. + */ @@ -76,50 +251,267 @@ + public String getCellUri() { + return cellUri; + } -+ ++ + /** -+ * URI of the cell. ++ * @param status new execution stage (must not be null) + */ -+ public void setCellUri(@NonNull final String cellUri) { -+ this.cellUri = Preconditions.checkNotNull(cellUri, "cellUri"); ++ public void setStatus(@NonNull EXECUTION_STATUS status) { ++ this.status = Preconditions.checkNotNull(status.name(), "status"); + } -+ ++ ++ /** ++ * @return captured outputStream (or null) ++ */ ++ @Pure ++ public CellExecutionResult getOutputStream() { ++ return outputStream; ++ } ++ ++ /** ++ * @param outputStream new outputStream (may be null) ++ */ ++ public void setOutputStream(CellExecutionResult outputStream) { ++ this.outputStream = outputStream; ++ } ++ ++ /** ++ * @return captured errorStream (or null) ++ */ ++ @Pure ++ public CellExecutionResult getErrorStream() { ++ return errorStream; ++ } ++ ++ /** ++ * @param errorStream new errorStream (may be null) ++ */ ++ public void setErrorStream(CellExecutionResult errorStream) { ++ this.errorStream = errorStream; ++ } ++ ++ public List getDiagnostics() { ++ return Collections.unmodifiableList(diagnostics); ++ } ++ ++ public void setDiagnostics(List diagnostics) { ++ this.diagnostics = diagnostics != null ? new ArrayList<>(diagnostics) : null; ++ } ++ ++ public List getErrorDiagnostics() { ++ return Collections.unmodifiableList(errorDiagnostics); ++ } ++ ++ public void setErrorDiagnostics(List errorDiagnostics) { ++ this.errorDiagnostics = errorDiagnostics != null ? new ArrayList<>(errorDiagnostics) : null; ++ } ++ ++ /** ++ * @return execution metadata (or null) ++ */ ++ @Pure ++ public Object getMetadata() { ++ return metadata; ++ } ++ ++ /** ++ * @param metadata new metadata (may be null) ++ */ ++ public void setMetadata(Object metadata) { ++ this.metadata = metadata; ++ } ++ + @Override + @Pure + public String toString() { + ToStringBuilder b = new ToStringBuilder(this); + b.add("notebookUri", notebookUri); + b.add("cellUri", cellUri); ++ b.add("status", status); ++ b.add("outputStream", outputStream); ++ b.add("errorStream", errorStream); ++ b.add("diagnostics", diagnostics); ++ b.add("errorDiagnostics", errorDiagnostics); ++ b.add("metadata", metadata); + return b.toString(); + } -+ ++ + @Override + public int hashCode() { -+ int hash = 5; -+ hash = 43 * hash + Objects.hashCode(this.notebookUri); -+ hash = 43 * hash + Objects.hashCode(this.cellUri); -+ return hash; ++ return Objects.hash( ++ notebookUri, ++ cellUri, ++ status, ++ outputStream, ++ errorStream, ++ diagnostics, ++ errorDiagnostics, ++ metadata ++ ); + } -+ ++ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } -+ if (obj == null) { -+ return false; -+ } -+ if (getClass() != obj.getClass()) { ++ if (!(obj instanceof NotebookCellExecutionProgressResultParams)) { + return false; + } -+ final NotebookCellContentRequestParams other = (NotebookCellContentRequestParams) obj; -+ if (!Objects.equals(this.notebookUri, other.notebookUri)) { -+ return false; ++ NotebookCellExecutionProgressResultParams other = (NotebookCellExecutionProgressResultParams) obj; ++ return Objects.equals(this.notebookUri, other.notebookUri) ++ && Objects.equals(this.cellUri, other.cellUri) ++ && Objects.equals(this.status, other.status) ++ && Objects.equals(this.outputStream, other.outputStream) ++ && Objects.equals(this.errorStream, other.errorStream) ++ && Objects.equals(this.diagnostics, other.diagnostics) ++ && Objects.equals(this.errorDiagnostics, other.errorDiagnostics) ++ && Objects.equals(this.metadata, other.metadata); ++ } ++} +--- /dev/null ++++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/notebook/CellStateResponse.java +@@ -0,0 +1,41 @@ ++/* ++ * 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.netbeans.modules.java.lsp.server.notebook; ++ ++/** ++ * ++ * @author atalati ++ */ ++public class CellStateResponse { ++ private final String text; ++ private final int version; ++ ++ public CellStateResponse(String text, int version) { ++ this.text = text; ++ this.version = version; ++ } ++ ++ public String getText() { ++ return text; ++ } ++ ++ public int getVersion() { ++ return version; ++ } ++} +--- /dev/null ++++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/notebook/NotebookCellStateParams.java +@@ -0,0 +1,99 @@ ++/* ++ * 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.netbeans.modules.java.lsp.server.notebook; ++ ++import java.util.Objects; ++import org.eclipse.lsp4j.jsonrpc.validation.NonNull; ++import org.eclipse.lsp4j.util.Preconditions; ++import org.eclipse.xtext.xbase.lib.Pure; ++import org.eclipse.xtext.xbase.lib.util.ToStringBuilder; ++ ++/** ++ * ++ * @author atalati ++ */ ++public class NotebookCellStateParams { ++ ++ /** ++ * URI of the notebook. ++ */ ++ @NonNull ++ private final String notebookUri; ++ ++ /** ++ * URI of the cell. ++ */ ++ @NonNull ++ private final String cellUri; ++ ++ public NotebookCellStateParams( ++ @NonNull final String notebookUri, ++ @NonNull final String cellUri) { ++ this.notebookUri = Preconditions.checkNotNull(notebookUri, "notebookUri"); ++ this.cellUri = Preconditions.checkNotNull(cellUri, "cellUri"); ++ } ++ ++ /** ++ * URI of the notebook. ++ */ ++ @Pure ++ @NonNull ++ public String getNotebookUri() { ++ return notebookUri; ++ } ++ ++ /** ++ * URI of the cell. ++ */ ++ @Pure ++ @NonNull ++ public String getCellUri() { ++ return cellUri; ++ } ++ ++ @Override ++ @Pure ++ public String toString() { ++ ToStringBuilder b = new ToStringBuilder(this); ++ b.add("notebookUri", notebookUri); ++ b.add("cellUri", cellUri); ++ return b.toString(); ++ } ++ ++ @Override ++ public int hashCode() { ++ return Objects.hash( ++ notebookUri, ++ cellUri ++ ); ++ } ++ ++ @Override ++ public boolean equals(Object obj) { ++ if (this == obj) { ++ return true; + } -+ if (!Objects.equals(this.cellUri, other.cellUri)) { ++ if (!(obj instanceof NotebookCellStateParams)) { + return false; + } -+ return true; ++ NotebookCellStateParams other = (NotebookCellStateParams) obj; ++ return Objects.equals(this.notebookUri, other.notebookUri) ++ && Objects.equals(this.cellUri, other.cellUri); + } +} --- /dev/null @@ -186,39 +578,51 @@ +} --- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeLanguageClient.java +++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeLanguageClient.java -@@ -32,6 +32,7 @@ +@@ -32,6 +32,9 @@ import org.netbeans.modules.java.lsp.server.input.ShowMutliStepInputParams; import org.netbeans.modules.java.lsp.server.input.ShowInputBoxParams; import org.netbeans.modules.java.lsp.server.explorer.api.NodeChangedParams; -+import org.netbeans.modules.java.lsp.server.notebook.NotebookCellContentRequestParams; ++import org.netbeans.modules.java.lsp.server.notebook.CellStateResponse; ++import org.netbeans.modules.java.lsp.server.notebook.NotebookCellExecutionProgressResultParams; ++import org.netbeans.modules.java.lsp.server.notebook.NotebookCellStateParams; /** * An extension to the standard LanguageClient that adds several messages missing -@@ -166,4 +167,6 @@ +@@ -166,4 +169,9 @@ @JsonRequest("output/reset") public CompletableFuture resetOutput(String outputName); -+ @JsonRequest("notebookDocument/cellContentRequest") -+ public CompletableFuture notebookDocumentCellContentRequest(@NonNull NotebookCellContentRequestParams params); ++ @JsonNotification("notebook/execution/progress") ++ public void notifyNotebookCellExecutionProgress(@NonNull NotebookCellExecutionProgressResultParams params); ++ ++ @JsonRequest("notebook/cell/state") ++ public CompletableFuture getNotebookCellState(@NonNull NotebookCellStateParams params); } --- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientWrapper.java +++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientWrapper.java -@@ -44,6 +44,7 @@ +@@ -44,6 +44,9 @@ import org.netbeans.modules.java.lsp.server.input.ShowMutliStepInputParams; import org.netbeans.modules.java.lsp.server.input.ShowInputBoxParams; import org.netbeans.modules.java.lsp.server.explorer.api.NodeChangedParams; -+import org.netbeans.modules.java.lsp.server.notebook.NotebookCellContentRequestParams; ++import org.netbeans.modules.java.lsp.server.notebook.CellStateResponse; ++import org.netbeans.modules.java.lsp.server.notebook.NotebookCellExecutionProgressResultParams; ++import org.netbeans.modules.java.lsp.server.notebook.NotebookCellStateParams; /** * Convenience wrapper that binds language client's remote proxy together with -@@ -244,4 +245,8 @@ +@@ -244,4 +247,13 @@ return remote.resetOutput(outputName); } + @Override -+ public CompletableFuture notebookDocumentCellContentRequest(NotebookCellContentRequestParams params) { -+ return remote.notebookDocumentCellContentRequest(params); ++ public void notifyNotebookCellExecutionProgress(NotebookCellExecutionProgressResultParams params) { ++ remote.notifyNotebookCellExecutionProgress(params); } ++ ++ @Override ++ public CompletableFuture getNotebookCellState(NotebookCellStateParams params) { ++ return remote.getNotebookCellState(params); ++ } +} --- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientCapabilities.java +++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientCapabilities.java @@ -247,65 +651,155 @@ caps = new ClientCapabilities(); --- a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/TestCodeLanguageClient.java +++ b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/TestCodeLanguageClient.java -@@ -43,6 +43,7 @@ +@@ -43,6 +43,9 @@ import org.netbeans.modules.java.lsp.server.input.ShowInputBoxParams; import org.netbeans.modules.java.lsp.server.input.ShowMutliStepInputParams; import org.netbeans.modules.java.lsp.server.input.ShowQuickPickParams; -+import org.netbeans.modules.java.lsp.server.notebook.NotebookCellContentRequestParams; ++import org.netbeans.modules.java.lsp.server.notebook.CellStateResponse; ++import org.netbeans.modules.java.lsp.server.notebook.NotebookCellExecutionProgressResultParams; ++import org.netbeans.modules.java.lsp.server.notebook.NotebookCellStateParams; import org.netbeans.modules.java.lsp.server.protocol.OutputMessage; import org.netbeans.modules.java.lsp.server.protocol.SaveDocumentRequestParams; import org.netbeans.modules.java.lsp.server.protocol.ShowStatusMessageParams; -@@ -182,4 +183,9 @@ +@@ -182,4 +185,14 @@ public CompletableFuture resetOutput(String outputName) { return CompletableFuture.completedFuture(null); } + + @Override -+ public CompletableFuture notebookDocumentCellContentRequest(NotebookCellContentRequestParams params) { -+ return CompletableFuture.completedFuture(null); ++ public void notifyNotebookCellExecutionProgress(NotebookCellExecutionProgressResultParams params){ ++ throw new UnsupportedOperationException(); } ++ ++ @Override ++ public CompletableFuture getNotebookCellState(NotebookCellStateParams params) { ++ return CompletableFuture.completedFuture(null); ++ } +} --- a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/explorer/ProjectViewTest.java +++ b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/explorer/ProjectViewTest.java -@@ -77,6 +77,7 @@ +@@ -77,6 +77,9 @@ import org.netbeans.modules.java.lsp.server.input.ShowInputBoxParams; import org.netbeans.modules.java.lsp.server.input.ShowMutliStepInputParams; import org.netbeans.modules.java.lsp.server.input.ShowQuickPickParams; -+import org.netbeans.modules.java.lsp.server.notebook.NotebookCellContentRequestParams; ++import org.netbeans.modules.java.lsp.server.notebook.CellStateResponse; ++import org.netbeans.modules.java.lsp.server.notebook.NotebookCellExecutionProgressResultParams; ++import org.netbeans.modules.java.lsp.server.notebook.NotebookCellStateParams; import org.netbeans.modules.java.lsp.server.protocol.OutputMessage; import org.netbeans.modules.java.lsp.server.protocol.SaveDocumentRequestParams; import org.netbeans.modules.java.lsp.server.protocol.SetTextEditorDecorationParams; -@@ -306,7 +307,12 @@ +@@ -306,8 +309,18 @@ public CompletableFuture resetOutput(String outputName) { return CompletableFuture.completedFuture(null); } + + @Override -+ public CompletableFuture notebookDocumentCellContentRequest(NotebookCellContentRequestParams params) { -+ return CompletableFuture.completedFuture(null); ++ public void notifyNotebookCellExecutionProgress(NotebookCellExecutionProgressResultParams params) { } -+ } ++ @Override ++ public CompletableFuture getNotebookCellState(NotebookCellStateParams params) { ++ return CompletableFuture.completedFuture(null); ++ } ++ ++ } ++ private static Launcher createLauncher(NbCodeLanguageClient client, InputStream in, OutputStream out, Function processor) { + return new LSPLauncher.Builder() +--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/input/ShowInputBoxParams.java ++++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/input/ShowInputBoxParams.java +@@ -53,6 +53,11 @@ + */ + private boolean password = false; + ++ /** ++ * Controls if focus is changed from the input box whether it should close or not. ++ */ ++ private boolean ignoreFocusOut = false; ++ + public ShowInputBoxParams() { + this("", ""); + } +@@ -68,6 +73,11 @@ + this.password = password; + } + ++ public ShowInputBoxParams(@NonNull final String prompt, @NonNull final String value, final boolean ignoreFocusOut) { ++ this(prompt, value); ++ this.ignoreFocusOut = ignoreFocusOut; ++ } ++ + /** + * An optional title of the input box. + */ +@@ -131,6 +141,22 @@ + this.password = password; + } + ++ /** ++ * Controls if focus is changed from the input box whether it should close or not. ++ */ ++ @Pure ++ @NonNull ++ public boolean isIgnoreFocusOut() { ++ return ignoreFocusOut; ++ } ++ ++ /** ++ * Controls if focus is changed from the input box whether it should close or not. ++ */ ++ public void setIgnoreFocusOut(boolean ignoreFocusOut) { ++ this.ignoreFocusOut = ignoreFocusOut; ++ } ++ + @Override + @Pure + public String toString() { +@@ -139,6 +165,7 @@ + b.add("prompt", prompt); + b.add("value", value); + b.add("password" , password); ++ b.add("ignoreFocusOut", ignoreFocusOut); + return b.toString(); + } + +@@ -169,6 +196,9 @@ + if (this.password != other.password) { + return false; + } ++ if (this.ignoreFocusOut != other.ignoreFocusOut) { ++ return false; ++ } + if (!Objects.equals(this.title, other.title)) { + return false; + } +diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java +index 747d151600..bbf1f263d1 100644 --- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java +++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java -@@ -134,6 +134,7 @@ import org.netbeans.modules.java.lsp.server.input.QuickPickItem; +@@ -134,6 +134,9 @@ import org.netbeans.modules.java.lsp.server.input.QuickPickItem; import org.netbeans.modules.java.lsp.server.input.ShowQuickPickParams; import org.netbeans.modules.java.lsp.server.input.ShowMutliStepInputParams; import org.netbeans.modules.java.lsp.server.input.ShowInputBoxParams; -+import org.netbeans.modules.java.lsp.server.notebook.NotebookCellContentRequestParams; ++import org.netbeans.modules.java.lsp.server.notebook.CellStateResponse; ++import org.netbeans.modules.java.lsp.server.notebook.NotebookCellExecutionProgressResultParams; ++import org.netbeans.modules.java.lsp.server.notebook.NotebookCellStateParams; import org.netbeans.modules.java.lsp.server.progress.OperationContext; import org.netbeans.modules.parsing.spi.indexing.Context; import org.netbeans.modules.parsing.spi.indexing.CustomIndexer; -@@ -1383,6 +1384,12 @@ public final class Server { +@@ -1383,6 +1386,16 @@ public final class Server { logWarning("Reset output: " + outputName); //NOI18N return CompletableFuture.completedFuture(null); } + + @Override -+ public CompletableFuture notebookDocumentCellContentRequest(NotebookCellContentRequestParams params) { -+ logWarning("notebook document cell content request: " + params.getNotebookUri() + " cell uri: " + params.getCellUri()); //NOI18N ++ public void notifyNotebookCellExecutionProgress(NotebookCellExecutionProgressResultParams params) { ++ logWarning("notebook document cell execution result: " + params.getNotebookUri() + " cell uri: " + params.getCellUri()); //NOI18N ++ } ++ ++ @Override ++ public CompletableFuture getNotebookCellState(NotebookCellStateParams params) { + return CompletableFuture.completedFuture(null); + } }; diff --git a/patches/upgrade-lsp4j.diff b/patches/upgrade-lsp4j.diff index 19a331e..375f914 100644 --- a/patches/upgrade-lsp4j.diff +++ b/patches/upgrade-lsp4j.diff @@ -19,6 +19,107 @@ index dd1dd5db2f..9c186d7712 100644 A57306D5D523A4750FA09B2708062EA4972AFEA2 org.eclipse.xtend:org.eclipse.xtend.lib:2.24.0 D8F5566BA67748ED9E91856E077EE99F00E86653 org.eclipse.xtend:org.eclipse.xtend.lib.macro:2.24.0 53FBD66084B08850258E61C838CC1FB94335E718 org.eclipse.xtext:org.eclipse.xtext.xbase.lib:2.24.0 +diff --git a/ide/lsp.client/external/lsp4j-0.16.0-license.txt b/ide/lsp.client/external/lsp4j-0.16.0-license.txt +new file mode 100644 +index 0000000000..a3b9b23ad7 +--- /dev/null ++++ b/ide/lsp.client/external/lsp4j-0.16.0-license.txt +@@ -0,0 +1,94 @@ ++Name: Eclipse Language Server Protocol Library ++Origin: Eclipse ++Version: 0.16.0 ++License: EPL-v20 ++URL: http://www.eclipse.org/ ++Description: Eclipse Language Server Protocol Library ++Files: org.eclipse.lsp4j-0.16.0.jar org.eclipse.lsp4j.generator-0.16.0.jar org.eclipse.lsp4j.jsonrpc-0.16.0.jar org.eclipse.lsp4j.debug-0.16.0.jar org.eclipse.lsp4j.jsonrpc.debug-0.16.0.jar ++ ++Eclipse Public License - v 2.0 ++THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE (“AGREEMENT”). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. ++ ++1. Definitions ++“Contribution” means: ++ ++a) in the case of the initial Contributor, the initial content Distributed under this Agreement, and ++b) in the case of each subsequent Contributor: ++i) changes to the Program, and ++ii) additions to the Program; where such changes and/or additions to the Program originate from and are Distributed by that particular Contributor. A Contribution “originates” from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include changes or additions to the Program that are not Modified Works. ++“Contributor” means any person or entity that Distributes the Program. ++ ++“Licensed Patents” mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. ++ ++“Program” means the Contributions Distributed in accordance with this Agreement. ++ ++“Recipient” means anyone who receives the Program under this Agreement or any Secondary License (as applicable), including Contributors. ++ ++“Derivative Works” shall mean any work, whether in Source Code or other form, that is based on (or derived from) the Program and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. ++ ++“Modified Works” shall mean any work in Source Code or other form that results from an addition to, deletion from, or modification of the contents of the Program, including, for purposes of clarity any new file in Source Code form that contains any contents of the Program. Modified Works shall not include works that contain only declarations, interfaces, types, classes, structures, or files of the Program solely in each case in order to link to, bind by name, or subclass the Program or Modified Works thereof. ++ ++“Distribute” means the acts of a) distributing or b) making available in any manner that enables the transfer of a copy. ++ ++“Source Code” means the form of a Program preferred for making modifications, including but not limited to software source code, documentation source, and configuration files. ++ ++“Secondary License” means either the GNU General Public License, Version 2.0, or any later versions of that license, including any exceptions or additional permissions as identified by the initial Contributor. ++ ++2. Grant of Rights ++a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, Distribute and sublicense the Contribution of such Contributor, if any, and such Derivative Works. ++ ++b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in Source Code or other form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. ++ ++c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to Distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. ++ ++d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. ++ ++e) Notwithstanding the terms of any Secondary License, no Contributor makes additional grants to any Recipient (other than those set forth in this Agreement) as a result of such Recipient's receipt of the Program under the terms of a Secondary License (if permitted under the terms of Section 3). ++ ++3. Requirements ++3.1 If a Contributor Distributes the Program in any form, then: ++ ++a) the Program must also be made available as Source Code, in accordance with section 3.2, and the Contributor must accompany the Program with a statement that the Source Code for the Program is available under this Agreement, and informs Recipients how to obtain it in a reasonable manner on or through a medium customarily used for software exchange; and ++ ++b) the Contributor may Distribute the Program under a license different than this Agreement, provided that such license: ++ ++i) effectively disclaims on behalf of all other Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; ++ii) effectively excludes on behalf of all other Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; ++iii) does not attempt to limit or alter the recipients' rights in the Source Code under section 3.2; and ++iv) requires any subsequent distribution of the Program by any party to be under a license that satisfies the requirements of this section 3. ++3.2 When the Program is Distributed as Source Code: ++ ++a) it must be made available under this Agreement, or if the Program (i) is combined with other material in a separate file or files made available under a Secondary License, and (ii) the initial Contributor attached to the Source Code the notice described in Exhibit A of this Agreement, then the Program may be made available under the terms of such Secondary Licenses, and ++b) a copy of this Agreement must be included with each copy of the Program. ++3.3 Contributors may not remove or alter any copyright, patent, trademark, attribution notices, disclaimers of warranty, or limitations of liability (“notices”) contained within the Program from any copy of the Program which they Distribute, provided that Contributors may add their own appropriate notices. ++ ++4. Commercial Distribution ++Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor (“Commercial Contributor”) hereby agrees to defend and indemnify every other Contributor (“Indemnified Contributor”) against any losses, damages and costs (collectively “Losses”) arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. ++ ++For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. ++ ++5. No Warranty ++EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. ++ ++6. Disclaimer of Liability ++EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. ++ ++7. General ++If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. ++ ++If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. ++ ++All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. ++ ++Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be Distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to Distribute the Program (including its Contributions) under the new version. ++ ++Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. Nothing in this Agreement is intended to be enforceable by any entity that is not a Contributor or Recipient. No third-party beneficiary rights are created under this Agreement. ++ ++Exhibit A - Form of Secondary Licenses Notice ++“This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), version(s), and exceptions or additional permissions here}.” ++ ++Simply including a copy of this Agreement, including this Exhibit A is not sufficient to license the Source Code under Secondary Licenses. ++ ++If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. ++ ++You may add additional accurate notices of copyright ownership. + diff --git a/ide/lsp.client/external/lsp4j-0.13.0-license.txt b/ide/lsp.client/external/lsp4j-0.13.0-license.txt deleted file mode 100644 index 2729106479..0000000000 diff --git a/vscode/l10n/bundle.l10n.en.json b/vscode/l10n/bundle.l10n.en.json index db88197..aeef13a 100644 --- a/vscode/l10n/bundle.l10n.en.json +++ b/vscode/l10n/bundle.l10n.en.json @@ -88,6 +88,7 @@ "jdk.extension.error_msg.doesntSupportNewTeamplate":"Client {client} doesn't support creating a new file from template", "jdk.extension.error_msg.doesntSupportNewProject":"Client {client} doesn't support new project", "jdk.extension.error_msg.doesntSupportGoToTest":"Client {client} doesn't support go to test", + "jdk.extension.error_msg.doesntSupportNoteboookCellExecution":"Client {client} doesn't support notebook cell execution", "jdk.extension.error_msg.noSuperImpl":"No super implementation found", "jdk.extension.error_msg.cacheDeletionError":"Error deleting the cache", "jdk.extension.message.cacheDeleted":"Cache deleted successfully", diff --git a/vscode/l10n/bundle.l10n.ja.json b/vscode/l10n/bundle.l10n.ja.json index 3f46973..98c235c 100755 --- a/vscode/l10n/bundle.l10n.ja.json +++ b/vscode/l10n/bundle.l10n.ja.json @@ -88,6 +88,7 @@ "jdk.extension.error_msg.doesntSupportNewTeamplate":"クライアント{client}では、「テンプレートからファイル新規作成」はサポートされていません", "jdk.extension.error_msg.doesntSupportNewProject":"クライアント{client}では、新規プロジェクトはサポートされていません", "jdk.extension.error_msg.doesntSupportGoToTest":"クライアント{client}では、「テストへ移動」はサポートされていません", + "jdk.extension.error_msg.doesntSupportNoteboookCellExecution":"Client {client} doesn't support notebook cell execution", "jdk.extension.error_msg.noSuperImpl":"スーパークラスの実装が見つかりません", "jdk.extension.error_msg.cacheDeletionError":"キャッシュの削除中にエラーが発生しました", "jdk.extension.message.cacheDeleted":"キャッシュが正常に削除されました", diff --git a/vscode/l10n/bundle.l10n.zh-cn.json b/vscode/l10n/bundle.l10n.zh-cn.json index ac4bdcd..1ed0d94 100755 --- a/vscode/l10n/bundle.l10n.zh-cn.json +++ b/vscode/l10n/bundle.l10n.zh-cn.json @@ -88,6 +88,7 @@ "jdk.extension.error_msg.doesntSupportNewTeamplate":"客户端 {client} 不支持从模板新建文件", "jdk.extension.error_msg.doesntSupportNewProject":"客户端 {client} 不支持新项目", "jdk.extension.error_msg.doesntSupportGoToTest":"客户端 {client} 不支持转至测试", + "jdk.extension.error_msg.doesntSupportNoteboookCellExecution":"Client {client} doesn't support notebook cell execution", "jdk.extension.error_msg.noSuperImpl":"未找到超类实现", "jdk.extension.error_msg.cacheDeletionError":"删除高速缓存时出错", "jdk.extension.message.cacheDeleted":"已成功删除高速缓存", diff --git a/vscode/package-lock.json b/vscode/package-lock.json index a7edb66..aee3c2c 100644 --- a/vscode/package-lock.json +++ b/vscode/package-lock.json @@ -11,13 +11,13 @@ "dependencies": { "@vscode/debugadapter": "^1.68.0", "@vscode/l10n": "^0.0.18", + "ajv": "^6.12.6", "jsonc-parser": "3.3.1", "vscode-languageclient": "^9.0.1" }, "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.2", "@types/chai": "^4.3.17", - "@types/glob": "^8.1.0", "@types/mocha": "^10.0.10", "@types/node": "^18.19.64", "@types/sinon": "^17.0.4", @@ -52,45 +52,45 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.8.tgz", - "integrity": "sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.7.tgz", + "integrity": "sha512-xgu/ySj2mTiUFmdE9yCMfBxLp4DHd5DwmbbD05YAuICfodYT3VvRxbrh81LGQ/8UpSdtMdfKMn3KouYDX59DGQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.8.tgz", - "integrity": "sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.7.tgz", + "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.25.7", - "@babel/generator": "^7.25.7", - "@babel/helper-compilation-targets": "^7.25.7", - "@babel/helper-module-transforms": "^7.25.7", - "@babel/helpers": "^7.25.7", - "@babel/parser": "^7.25.8", - "@babel/template": "^7.25.7", - "@babel/traverse": "^7.25.7", - "@babel/types": "^7.25.8", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.27.7", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.7", + "@babel/types": "^7.27.7", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -121,12 +121,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz", - "integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", "dev": true, "dependencies": { - "@babel/types": "^7.25.7", + "@babel/parser": "^7.27.5", + "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -136,13 +137,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz", - "integrity": "sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.25.7", - "@babel/helper-validator-option": "^7.25.7", + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -161,28 +162,27 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz", - "integrity": "sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "dev": true, "dependencies": { - "@babel/traverse": "^7.25.7", - "@babel/types": "^7.25.7" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz", - "integrity": "sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.25.7", - "@babel/helper-simple-access": "^7.25.7", - "@babel/helper-validator-identifier": "^7.25.7", - "@babel/traverse": "^7.25.7" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" }, "engines": { "node": ">=6.9.0" @@ -191,23 +191,10 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz", - "integrity": "sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.25.7", - "@babel/types": "^7.25.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", "engines": { @@ -215,9 +202,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, "license": "MIT", "engines": { @@ -225,36 +212,36 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz", - "integrity": "sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", - "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0" + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", - "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz", + "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.0" + "@babel/types": "^7.27.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -264,31 +251,31 @@ } }, "node_modules/@babel/template": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", - "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.7.tgz", - "integrity": "sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.7.tgz", + "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.25.7", - "@babel/generator": "^7.25.7", - "@babel/parser": "^7.25.7", - "@babel/template": "^7.25.7", - "@babel/types": "^7.25.7", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.5", + "@babel/parser": "^7.27.7", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -297,14 +284,14 @@ } }, "node_modules/@babel/types": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", - "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz", + "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -333,9 +320,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz", - "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", "cpu": [ "ppc64" ], @@ -350,9 +337,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz", - "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", "cpu": [ "arm" ], @@ -367,9 +354,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz", - "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", "cpu": [ "arm64" ], @@ -384,9 +371,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz", - "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", "cpu": [ "x64" ], @@ -401,9 +388,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz", - "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", "cpu": [ "arm64" ], @@ -418,9 +405,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz", - "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", "cpu": [ "x64" ], @@ -435,9 +422,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz", - "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", "cpu": [ "arm64" ], @@ -452,9 +439,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz", - "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", "cpu": [ "x64" ], @@ -469,9 +456,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz", - "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", "cpu": [ "arm" ], @@ -486,9 +473,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz", - "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", "cpu": [ "arm64" ], @@ -503,9 +490,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz", - "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", "cpu": [ "ia32" ], @@ -520,9 +507,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz", - "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", "cpu": [ "loong64" ], @@ -537,9 +524,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz", - "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", "cpu": [ "mips64el" ], @@ -554,9 +541,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz", - "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", "cpu": [ "ppc64" ], @@ -571,9 +558,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz", - "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", "cpu": [ "riscv64" ], @@ -588,9 +575,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz", - "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", "cpu": [ "s390x" ], @@ -605,9 +592,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz", - "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", "cpu": [ "x64" ], @@ -622,9 +609,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz", - "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", "cpu": [ "arm64" ], @@ -639,9 +626,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz", - "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", "cpu": [ "x64" ], @@ -656,9 +643,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz", - "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", "cpu": [ "arm64" ], @@ -673,9 +660,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz", - "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", "cpu": [ "x64" ], @@ -690,9 +677,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz", - "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", "cpu": [ "x64" ], @@ -707,9 +694,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz", - "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", "cpu": [ "arm64" ], @@ -724,9 +711,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz", - "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", "cpu": [ "ia32" ], @@ -741,9 +728,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz", - "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", "cpu": [ "x64" ], @@ -757,6 +744,27 @@ "node": ">=18" } }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -775,91 +783,6 @@ "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -885,15 +808,6 @@ "sprintf-js": "~1.0.2" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -984,17 +898,13 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.10.tgz", + "integrity": "sha512-HM2F4B9N4cA0RH2KQiIZOHAZqtP4xGS4IZ+SFe1SIbO4dyjf9MTY2Bo3vHYnm0hglWfXqBrzUBSa+cJfl3Xvrg==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -1006,25 +916,16 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.2.tgz", + "integrity": "sha512-gKYheCylLIedI+CSZoDtGkFV9YEBxRRVcfCH7OfAqh4TyUyRjEE6WVE/aXDXX0p8BIe/QgLcaAoI0220KRRFgg==", "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.27", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.27.tgz", + "integrity": "sha512-VO95AxtSFMelbg3ouljAYnfvTEwSWVt/2YLf+U5Ejd8iT5mXE2Sa/1LGyvySMne2CGsepGLI7KpF3EzE3Aq9Mg==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1051,6 +952,15 @@ "type-detect": "4.0.8" } }, + "node_modules/@sinonjs/commons/node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/@sinonjs/fake-timers": { "version": "13.0.5", "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", @@ -1072,15 +982,6 @@ "type-detect": "^4.1.0" } }, - "node_modules/@sinonjs/samsam/node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -1111,23 +1012,6 @@ "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==", "dev": true }, - "node_modules/@types/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/minimatch": "^5.1.2", - "@types/node": "*" - } - }, - "node_modules/@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "dev": true - }, "node_modules/@types/mocha": { "version": "10.0.10", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", @@ -1136,9 +1020,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "18.19.64", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.64.tgz", - "integrity": "sha512-955mDqvO2vFf/oL7V3WiUtiz+BugyX8uVbaT2H8oj3+8dRyH2FLiNdowe7eNqRM7IOIZvzDH76EoAT+gwm6aIQ==", + "version": "18.19.113", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.113.tgz", + "integrity": "sha512-TmSTE9vyebJ9vSEiU+P+0Sp4F5tMgjiEOZaQUW6wA3ODvi6uBgkHQ+EsIu0pbiKvf9QHEvyRCiaz03rV0b+IaA==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -1161,9 +1045,9 @@ "dev": true }, "node_modules/@types/vscode": { - "version": "1.95.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.95.0.tgz", - "integrity": "sha512-0LBD8TEiNbet3NvWsmn59zLzOFu/txSlGxnv5yAFHCrhG9WvAnR3IvfHzMOs2aeWqgvNjq9pO99IUw8d3n+unw==", + "version": "1.101.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.101.0.tgz", + "integrity": "sha512-ZWf0IWa+NGegdW3iU42AcDTFHWW7fApLdkdnBqwYEtHVIBGbTu0ZNQKP/kX3Ds/uMJXIMQNAojHR4vexCEEz5Q==", "dev": true }, "node_modules/@vscode/codicons": { @@ -1214,9 +1098,9 @@ } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1238,13 +1122,10 @@ } }, "node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", "dev": true, - "dependencies": { - "debug": "^4.3.4" - }, "engines": { "node": ">= 14" } @@ -1262,25 +1143,38 @@ "node": ">=8" } }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, "node_modules/ansi-regex": { - "version": "5.0.1", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -1293,18 +1187,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/anymatch": { - "version": "3.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/append-transform": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", @@ -1331,6 +1213,8 @@ }, "node_modules/argparse": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, "license": "Python-2.0" }, @@ -1343,14 +1227,6 @@ "node": "*" } }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -1366,27 +1242,17 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/browser-stdout": { "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true, "license": "ISC" }, "node_modules/browserslist": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", - "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "dev": true, "funding": [ { @@ -1403,10 +1269,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001663", - "electron-to-chromium": "^1.5.28", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -1431,20 +1297,19 @@ } }, "node_modules/camelcase": { - "version": "6.2.0", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001668", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001668.tgz", - "integrity": "sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw==", + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", "dev": true, "funding": [ { @@ -1479,17 +1344,10 @@ "node": ">=4" } }, - "node_modules/chai/node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/chalk": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { @@ -1505,6 +1363,8 @@ }, "node_modules/chalk/node_modules/supports-color": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { @@ -1527,29 +1387,19 @@ } }, "node_modules/chokidar": { - "version": "3.5.3", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "funding": { + "url": "https://paulmillr.com/funding/" } }, "node_modules/clean-stack": { @@ -1605,54 +1455,116 @@ "node": ">=12" } }, - "node_modules/color-convert": { - "version": "2.0.1", + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, "engines": { - "node": ">=7.0.0" + "node": ">=8" } }, - "node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", "which": "^2.0.1" }, "engines": { @@ -1660,9 +1572,9 @@ } }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -1677,14 +1589,13 @@ } }, "node_modules/decamelize": { - "version": "4.0.0", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, "node_modules/deep-eql": { @@ -1715,9 +1626,9 @@ } }, "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", "dev": true, "engines": { "node": ">=0.3.1" @@ -1730,13 +1641,15 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.5.37", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.37.tgz", - "integrity": "sha512-u7000ZB/X0K78TaQqXZ5ktoR7J79B9US7IkE4zyvcILYwOGY2Tx9GRPYstn7HmuPcMxZ+BDGqIsyLpZQi9ufPw==", + "version": "1.5.177", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.177.tgz", + "integrity": "sha512-7EH2G59nLsEMj97fpDuvVcYi6lwTcM1xuWw3PssD8xzboAW7zj7iB3COEEEATUfjLHrs5uKBLQT03V/8URx06g==", "dev": true }, "node_modules/emoji-regex": { - "version": "8.0.0", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, @@ -1747,9 +1660,9 @@ "dev": true }, "node_modules/esbuild": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", - "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1760,31 +1673,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.3", - "@esbuild/android-arm": "0.25.3", - "@esbuild/android-arm64": "0.25.3", - "@esbuild/android-x64": "0.25.3", - "@esbuild/darwin-arm64": "0.25.3", - "@esbuild/darwin-x64": "0.25.3", - "@esbuild/freebsd-arm64": "0.25.3", - "@esbuild/freebsd-x64": "0.25.3", - "@esbuild/linux-arm": "0.25.3", - "@esbuild/linux-arm64": "0.25.3", - "@esbuild/linux-ia32": "0.25.3", - "@esbuild/linux-loong64": "0.25.3", - "@esbuild/linux-mips64el": "0.25.3", - "@esbuild/linux-ppc64": "0.25.3", - "@esbuild/linux-riscv64": "0.25.3", - "@esbuild/linux-s390x": "0.25.3", - "@esbuild/linux-x64": "0.25.3", - "@esbuild/netbsd-arm64": "0.25.3", - "@esbuild/netbsd-x64": "0.25.3", - "@esbuild/openbsd-arm64": "0.25.3", - "@esbuild/openbsd-x64": "0.25.3", - "@esbuild/sunos-x64": "0.25.3", - "@esbuild/win32-arm64": "0.25.3", - "@esbuild/win32-ia32": "0.25.3", - "@esbuild/win32-x64": "0.25.3" + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" } }, "node_modules/escalade": { @@ -1798,6 +1711,8 @@ }, "node_modules/escape-string-regexp": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", "engines": { @@ -1820,17 +1735,15 @@ "node": ">=4" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/find-cache-dir": { "version": "3.3.2", @@ -1851,6 +1764,8 @@ }, "node_modules/find-up": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { @@ -1866,6 +1781,8 @@ }, "node_modules/flat": { "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, "license": "BSD-3-Clause", "bin": { @@ -1873,12 +1790,12 @@ } }, "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -1888,18 +1805,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/fromentries": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", @@ -1927,18 +1832,6 @@ "dev": true, "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.2", - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -1950,6 +1843,8 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "license": "ISC", "engines": { @@ -1988,15 +1883,15 @@ } }, "node_modules/glob": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.2.tgz", - "integrity": "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", "dev": true, "license": "ISC", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" @@ -2011,76 +1906,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob/node_modules/jackspeak": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz", - "integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob/node_modules/lru-cache": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", - "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", - "dev": true, - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob/node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -2098,6 +1923,8 @@ }, "node_modules/has-flag": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { @@ -2122,6 +1949,8 @@ }, "node_modules/he": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, "license": "MIT", "bin": { @@ -2148,12 +1977,12 @@ } }, "node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { @@ -2198,47 +2027,21 @@ }, "node_modules/inherits": { "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true, "license": "ISC" }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-interactive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", @@ -2252,17 +2055,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/is-plain-obj": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true, "license": "MIT", "engines": { @@ -2289,6 +2085,8 @@ }, "node_modules/is-unicode-supported": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, "license": "MIT", "engines": { @@ -2315,6 +2113,8 @@ }, "node_modules/isexe": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, @@ -2441,19 +2241,19 @@ } }, "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, + "engines": { + "node": "20 || >=22" + }, "funding": { "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/js-tokens": { @@ -2465,6 +2265,8 @@ }, "node_modules/js-yaml": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "license": "MIT", "dependencies": { @@ -2475,9 +2277,9 @@ } }, "node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, "bin": { "jsesc": "bin/jsesc" @@ -2486,6 +2288,11 @@ "node": ">=6" } }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -2526,6 +2333,8 @@ }, "node_modules/locate-path": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { @@ -2554,10 +2363,13 @@ "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", "dev": true }, "node_modules/log-symbols": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "license": "MIT", "dependencies": { @@ -2633,15 +2445,18 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": "*" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minipass": { @@ -2655,29 +2470,29 @@ } }, "node_modules/mocha": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz", - "integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==", + "version": "11.7.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.1.tgz", + "integrity": "sha512-5EK+Cty6KheMS/YLPPMJC64g5V61gIR25KsRItHw6x4hEKT6Njp1n9LOlH4gpevuwMVS66SXaBBpg+RWZkza4A==", "dev": true, "license": "MIT", "dependencies": { - "ansi-colors": "^4.1.3", "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", + "chokidar": "^4.0.1", "debug": "^4.3.5", - "diff": "^5.2.0", + "diff": "^7.0.0", "escape-string-regexp": "^4.0.0", "find-up": "^5.0.0", "glob": "^10.4.5", "he": "^1.2.0", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", + "minimatch": "^9.0.5", "ms": "^2.1.3", + "picocolors": "^1.1.1", "serialize-javascript": "^6.0.2", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", - "workerpool": "^6.5.1", + "workerpool": "^9.2.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1", "yargs-unparser": "^2.0.0" @@ -2711,33 +2526,57 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mocha/node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "node_modules/mocha/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, - "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" + "@isaacs/cliui": "^8.0.2" }, "funding": { "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/mocha/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/ms": { @@ -2759,19 +2598,11 @@ } }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true }, - "node_modules/normalize-path": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/nyc": { "version": "17.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-17.1.0.tgz", @@ -2813,13 +2644,13 @@ "node": ">=18" } }, - "node_modules/nyc/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/nyc/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "engines": { - "node": ">=6" + "node": ">=8" } }, "node_modules/nyc/node_modules/cliui": { @@ -2833,14 +2664,11 @@ "wrap-ansi": "^6.2.0" } }, - "node_modules/nyc/node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "node_modules/nyc/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "node_modules/nyc/node_modules/find-up": { "version": "4.1.0", @@ -2889,6 +2717,18 @@ "node": ">=8" } }, + "node_modules/nyc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/nyc/node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -2916,6 +2756,39 @@ "node": ">=8" } }, + "node_modules/nyc/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/nyc/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/nyc/node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -3021,19 +2894,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/ora/node_modules/chalk": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", @@ -3115,24 +2975,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/p-limit": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3147,6 +2993,8 @@ }, "node_modules/p-locate": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { @@ -3210,6 +3058,8 @@ }, "node_modules/path-exists": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", "engines": { @@ -3236,28 +3086,31 @@ } }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", "dev": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": "20 || >=22" + } }, "node_modules/pathval": { "version": "1.1.1", @@ -3269,22 +3122,11 @@ } }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, - "node_modules/picomatch": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -3356,9 +3198,9 @@ "dev": true }, "node_modules/process-on-spawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", - "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz", + "integrity": "sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==", "dev": true, "dependencies": { "fromentries": "^1.2.0" @@ -3367,6 +3209,14 @@ "node": ">=8" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -3391,21 +3241,18 @@ "util-deprecate": "~1.0.1" } }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, "node_modules/readdirp": { - "version": "3.6.0", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, "engines": { - "node": ">=8.10.0" + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/release-zalgo": { @@ -3422,6 +3269,8 @@ }, "node_modules/require-directory": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", "engines": { @@ -3460,19 +3309,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/restore-cursor/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -3511,30 +3347,28 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true }, "node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "bin": { "semver": "bin/semver.js" }, @@ -3585,10 +3419,17 @@ } }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/sinon": { "version": "20.0.0", @@ -3608,15 +3449,6 @@ "url": "https://opencollective.com/sinon" } }, - "node_modules/sinon/node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/sinon/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3668,6 +3500,12 @@ "node": ">=8.0.0" } }, + "node_modules/spawn-wrap/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -3696,23 +3534,22 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, "node_modules/string-width": { - "version": "4.2.3", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/string-width-cjs": { @@ -3731,8 +3568,25 @@ "node": ">=8" } }, - "node_modules/strip-ansi": { + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -3742,6 +3596,21 @@ "node": ">=8" } }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", @@ -3756,6 +3625,15 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -3767,6 +3645,8 @@ }, "node_modules/strip-json-comments": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", "engines": { @@ -3778,6 +3658,8 @@ }, "node_modules/supports-color": { "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3826,16 +3708,16 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "dependencies": { - "is-number": "^7.0.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=8.0" + "node": "*" } }, "node_modules/ts-mockito": { @@ -3900,9 +3782,9 @@ } }, "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", "dev": true, "engines": { "node": ">=4" @@ -3947,9 +3829,9 @@ "dev": true }, "node_modules/update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "funding": [ { @@ -3967,7 +3849,7 @@ ], "dependencies": { "escalade": "^3.2.0", - "picocolors": "^1.1.0" + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -3976,6 +3858,14 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4049,6 +3939,8 @@ }, "node_modules/which": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", "dependencies": { @@ -4068,24 +3960,24 @@ "dev": true }, "node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.3.tgz", + "integrity": "sha512-slxCaKbYjEdFT/o2rH9xS1hf4uRDch1w7Uo+apxhZ+sf/1d9e0ZVkn42kPNGP2dgjIx6YFvSevj0zHvbWe2jdw==", "dev": true }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -4110,6 +4002,59 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -4129,6 +4074,12 @@ "typedarray-to-buffer": "^3.1.5" } }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -4176,6 +4127,8 @@ }, "node_modules/yargs-unparser": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, "license": "MIT", "dependencies": { @@ -4188,6 +4141,71 @@ "node": ">=10" } }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -4199,6 +4217,8 @@ }, "node_modules/yocto-queue": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", "engines": { diff --git a/vscode/package.json b/vscode/package.json index b28064b..935040f 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -41,7 +41,8 @@ "workspaceContains:build.gradle", "onDebug", "onDebugDynamicConfigurations", - "onNotebook:ijnb-notebook" + "onNotebook:ijnb-notebook", + "onNotebook:jupyter-notebook" ], "main": "./out/extension.js", "l10n": "./l10n", @@ -482,7 +483,7 @@ }, { "command": "jdk.jshell.project", - "title": "Open JShell in context of a Project", + "title": "%jdk.jshell.project%", "category": "Java" }, { @@ -590,7 +591,7 @@ }, { "command": "jdk.notebook.new", - "title": "Create New Notebook...", + "title": "%jdk.notebook.new%", "category": "Java", "icon": "$(new-file)" } @@ -635,6 +636,11 @@ "command": "jdk.project.debug", "when": "nbJdkReady && editorLangId == java && resourceExtname == .java", "group": "javadebug@2" + }, + { + "command": "jdk.jshell.project", + "when": "nbJdkReady && editorLangId == java && resourceExtname == .java", + "group": "javadebug@3" } ], "explorer/context": [ @@ -662,6 +668,11 @@ "command": "jdk.project.debug", "when": "nbJdkReady && resourceExtname == .java", "group": "javadebug@2" + }, + { + "command": "jdk.jshell.project", + "when": "nbJdkReady && resourceExtname == .java", + "group": "javadebug@3" } ], "commandPalette": [ @@ -875,7 +886,6 @@ "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.2", "@types/chai": "^4.3.17", - "@types/glob": "^8.1.0", "@types/mocha": "^10.0.10", "@types/node": "^18.19.64", "@types/sinon": "^17.0.4", @@ -895,6 +905,7 @@ "dependencies": { "@vscode/debugadapter": "^1.68.0", "@vscode/l10n": "^0.0.18", + "ajv": "^6.12.6", "jsonc-parser": "3.3.1", "vscode-languageclient": "^9.0.1" }, diff --git a/vscode/package.nls.json b/vscode/package.nls.json index 628d5b5..ac93a93 100644 --- a/vscode/package.nls.json +++ b/vscode/package.nls.json @@ -6,11 +6,13 @@ "jdk.workspace.new": "New File from Template...", "jdk.workspace.newproject": "New Project...", "jdk.java.goto.super.implementation": "Go to Super Implementation", + "jdk.jshell.project": "Open JShell in context of a Project", "jdk.open.type": "Open Type...", "jdk.foundProjects.deleteEntry": "Delete", "jdk.Edit.org.openide.actions.DeleteAction": "Delete", "workbench.action.debug.run": "Run Without Debugging", "workbench.action.debug.start": "Start Debugging", + "jdk.notebook.new": "Create New Notebook...", "jdk.project.run": "Run Project Without Debugging", "jdk.project.debug": "Debug Project", "jdk.project.test": "Test Project", diff --git a/vscode/src/commands/commands.ts b/vscode/src/commands/commands.ts index ec9699e..b7f2a6d 100644 --- a/vscode/src/commands/commands.ts +++ b/vscode/src/commands/commands.ts @@ -87,5 +87,6 @@ export const nbCommands = { javaProjectPackages: appendPrefixToCommand('java.get.project.packages'), openStackTrace: appendPrefixToCommand('open.stacktrace'), executeNotebookCell: appendPrefixToCommand("jshell.execute.cell"), + interruptNotebookCellExecution: appendPrefixToCommand("jshell.interrupt.cell"), openJshellInProject: appendPrefixToCommand("jshell.project.open"), } \ No newline at end of file diff --git a/vscode/src/commands/notebook.ts b/vscode/src/commands/notebook.ts index bbe44ea..cba5132 100644 --- a/vscode/src/commands/notebook.ts +++ b/vscode/src/commands/notebook.ts @@ -11,6 +11,9 @@ import { globalState } from '../globalState'; import { getContextUri, isNbCommandRegistered } from './utils'; import { l10n } from '../localiser'; import { extConstants } from '../constants'; +import { Notebook } from '../notebooks/notebook'; +import { ICodeCell } from '../notebooks/types'; +import { randomUUID } from 'crypto'; const createNewNotebook = async (ctx?: any) => { try { @@ -86,30 +89,17 @@ const createNewNotebook = async (ctx?: any) => { return; } - const emptyNotebook = { - cells: [{ - cell_type: "code", - source: [], - metadata: { - language: "java" - }, - execution_count: null, - outputs: [] - }], - metadata: { - kernelspec: { - name: "java", - language: "java", - display_name: "Java" - }, - language_info: { - name: "java" - } - }, - nbformat: 4, - nbformat_minor: 5 + const newCell: ICodeCell = { + cell_type: 'code', + execution_count: null, + outputs: [], + id: randomUUID(), + metadata: {}, + source: '' }; + const emptyNotebook = new Notebook([newCell]).toJSON(); + await fs.promises.writeFile(finalNotebookPath, JSON.stringify(emptyNotebook, null, 2), { encoding: 'utf8' }); LOGGER.log(`Created notebook at: ${finalNotebookPath}`); @@ -129,11 +119,15 @@ const openJshellInContextOfProject = async (ctx: any) => { let client: LanguageClient = await globalState.getClientPromise().client; if (await isNbCommandRegistered(nbCommands.openJshellInProject)) { const res: string[] = await commands.executeCommand(nbCommands.openJshellInProject, getContextUri(ctx)?.toString()); - - const args = res.join(" "); - const terminal = window.createTerminal("Jshell instance"); + const { envMap, finalArgs } = passArgsToTerminal(res); + // Direct sendText is not working since it truncates the command exceeding a certain length. + // Open issues on vscode: 130688, 134324 and many more + // So, workaround by setting env variables. + const terminal = window.createTerminal({ + name: "Jshell instance", env: envMap + }); + terminal.sendText(`jshell ${finalArgs.join(' ')}`, true); terminal.show(); - terminal.sendText(`jshell ${args}`, true); } else { throw l10n.value("jdk.extension.error_msg.doesntSupportGoToTest", { client }); } @@ -143,6 +137,18 @@ const openJshellInContextOfProject = async (ctx: any) => { } } +const passArgsToTerminal = (args: string[]): { envMap: { [key: string]: string }, finalArgs: string[] } => { + const envMap: { [key: string]: string } = {}; + const finalArgs = args.map((arg, index) => { + if (arg.startsWith('-') || arg.startsWith('--')) { + return arg; + } + const envName = `jshellArgsEnv${index}`; + envMap[envName] = arg; + return `$${envName}`; + }); + return { envMap, finalArgs }; +} export const registerNotebookCommands: ICommand[] = [ { diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts index 058d57f..4508043 100644 --- a/vscode/src/extension.ts +++ b/vscode/src/extension.ts @@ -31,8 +31,8 @@ import { registerFileProviders } from './lsp/listeners/textDocumentContentProvid import { ExtensionContextInfo } from './extensionContextInfo'; import { ClientPromise } from './lsp/clientPromise'; import { globalState } from './globalState'; +import { registerNotebookProviders } from './notebooks/register'; import { Telemetry } from './telemetry/telemetry'; -import { registerNotebooks } from './notebooks/register'; export function activate(context: ExtensionContext): VSNetBeansAPI { const contextInfo = new ExtensionContextInfo(context); @@ -46,7 +46,7 @@ export function activate(context: ExtensionContext): VSNetBeansAPI { registerDebugger(context); subscribeCommands(context); registerFileProviders(context); - registerNotebooks(context); + registerNotebookProviders(context); launchConfigurations.updateLaunchConfig(); diff --git a/vscode/src/lsp/listeners/notifications/handlers.ts b/vscode/src/lsp/listeners/notifications/handlers.ts index 2ef5cf6..6161c2d 100644 --- a/vscode/src/lsp/listeners/notifications/handlers.ts +++ b/vscode/src/lsp/listeners/notifications/handlers.ts @@ -15,8 +15,8 @@ */ import { LogMessageNotification, MessageType, TelemetryEventNotification } from "vscode-languageclient"; import { notificationOrRequestListenerType } from "../../types"; -import { asRanges, ShowStatusMessageParams, StatusMessageRequest, TestProgressNotification, TextEditorDecorationDisposeNotification, TextEditorDecorationSetNotification } from "../../protocol"; -import { commands, window, workspace } from "vscode"; +import { asRanges, NotebookCellExecutionResult, ShowStatusMessageParams, StatusMessageRequest, TestProgressNotification, TextEditorDecorationDisposeNotification, TextEditorDecorationSetNotification } from "../../protocol"; +import { commands, window } from "vscode"; import { isNbJavacDisabledHandler, updateConfigurationValue } from "../../../configurations/handlers"; import { l10n } from "../../../localiser"; import { configKeys } from "../../../configurations/configuration"; @@ -26,6 +26,7 @@ import { globalState } from "../../../globalState"; import { WorkspaceChangeData, WorkspaceChangeEvent } from "../../../telemetry/events/workspaceChange"; import { Telemetry } from "../../../telemetry/telemetry"; import { JdkFeatureEvent, JdkFeatureEventData } from "../../../telemetry/events/jdkFeature"; +import { notebookKernel } from "../../../notebooks/kernel"; const checkInstallNbJavac = (msg: string) => { const NO_JAVA_SUPPORT = "Cannot initialize Java support"; @@ -144,6 +145,10 @@ const telemetryEventHandler = (param: any) => { } } +const notebookCellExecutionResultHandler = async (params: NotebookCellExecutionResult.params): Promise => { + await notebookKernel.handleCellExecutionNotification(params); +} + export const notificationListeners: notificationOrRequestListenerType[] = [{ type: StatusMessageRequest.type, handler: showStatusBarMessageHandler @@ -162,4 +167,7 @@ export const notificationListeners: notificationOrRequestListenerType[] = [{ }, { type: TelemetryEventNotification.type, handler: telemetryEventHandler +}, { + type: NotebookCellExecutionResult.type, + handler: notebookCellExecutionResultHandler }]; \ No newline at end of file diff --git a/vscode/src/lsp/listeners/requests/handlers.ts b/vscode/src/lsp/listeners/requests/handlers.ts index da24cff..d29d3f5 100644 --- a/vscode/src/lsp/listeners/requests/handlers.ts +++ b/vscode/src/lsp/listeners/requests/handlers.ts @@ -15,7 +15,7 @@ */ import { QuickPickItem, Uri, window, workspace, WorkspaceConfiguration } from "vscode"; import { notificationOrRequestListenerType } from "../../types"; -import { ExecInHtmlPageRequest, HtmlPageRequest, InputBoxRequest, InputBoxStep, MutliStepInputRequest, QuickPickRequest, QuickPickStep, SaveDocumentRequestParams, SaveDocumentsRequest, TextEditorDecorationCreateRequest, UpdateConfigurationRequest } from "../../protocol"; +import { ExecInHtmlPageRequest, HtmlPageRequest, InputBoxRequest, InputBoxStep, MutliStepInputRequest, NotebookCellStateRequest, NotebookCellStateRequestParams, NotebookCellStateResponse, QuickPickRequest, QuickPickStep, SaveDocumentRequestParams, SaveDocumentsRequest, ShowInputBoxParams, TextEditorDecorationCreateRequest, UpdateConfigurationRequest } from "../../protocol"; import { InputStep, MultiStepInput } from "../../../utils"; import { runConfigurationUpdateAll } from "../../../views/runConfiguration"; import { isError } from "../../../utils"; @@ -69,8 +69,8 @@ const multiStepInputRequestHandler = async (param: any) => { return data; } -const inputBoxRequestHandler = async (param: any) => { - return await window.showInputBox({ title: param.title, prompt: param.prompt, value: param.value, password: param.password }); +const inputBoxRequestHandler = async (param: ShowInputBoxParams) => { + return await window.showInputBox({ title: param?.title, prompt: param.prompt, value: param.value, password: param?.password, ignoreFocusOut: param?.ignoreFocusOut }); } const saveDocumentRequestHandler = async (request: SaveDocumentRequestParams) => { @@ -114,6 +114,17 @@ const quickPickRequestHandler = async (param: any) => { return selected ? Array.isArray(selected) ? selected : [selected] : undefined; } +const getNotebookCellStateHandler = (param: NotebookCellStateRequestParams): NotebookCellStateResponse => { + const notebookInstance = workspace.notebookDocuments.find(notebook => notebook.uri.toString() === param.notebookUri); + const cellInstance = notebookInstance?.getCells().find(cell => cell.document.uri.toString() === param.cellUri); + if (!notebookInstance || !cellInstance) { + return { + version: -1, + text: "" + }; + } + return { version: cellInstance.document.version, text: cellInstance.document.getText() } +} export const requestListeners: notificationOrRequestListenerType[] = [{ type: TextEditorDecorationCreateRequest.type, @@ -139,4 +150,7 @@ export const requestListeners: notificationOrRequestListenerType[] = [{ }, { type: ExecInHtmlPageRequest.type, handler: execInHtmlPage +}, { + type: NotebookCellStateRequest.type, + handler: getNotebookCellStateHandler }]; \ No newline at end of file diff --git a/vscode/src/lsp/listeners/requests/register.ts b/vscode/src/lsp/listeners/requests/register.ts index 9db1617..116b434 100644 --- a/vscode/src/lsp/listeners/requests/register.ts +++ b/vscode/src/lsp/listeners/requests/register.ts @@ -17,14 +17,12 @@ import { NbLanguageClient } from "../../nbLanguageClient" import { notificationOrRequestListenerType } from "../../types" import { requestListeners } from "./handlers" -import { notebookListeners } from "./notebooks"; import { terminalListeners } from "./terminal" const listeners: notificationOrRequestListenerType[] = [ ...requestListeners, - ...terminalListeners, - ...notebookListeners + ...terminalListeners ]; export const registerRequestListeners = (client: NbLanguageClient) => { diff --git a/vscode/src/lsp/protocol.ts b/vscode/src/lsp/protocol.ts index 3b1ddf2..ee8a3b9 100644 --- a/vscode/src/lsp/protocol.ts +++ b/vscode/src/lsp/protocol.ts @@ -112,6 +112,10 @@ export interface ShowInputBoxParams { * Controls if a password input is shown. Password input hides the typed text. */ password?: boolean; + /** + * Controls if focus is changed from the input box whether it should close or not. + */ + ignoreFocusOut?: boolean; } export namespace InputBoxRequest { @@ -344,10 +348,43 @@ export namespace ResetOutputRequest { export const type = new ProtocolRequestType('output/reset'); } -export interface NotebookCellContentRequestParams { +export namespace NotebookCellExecutionResult { + export enum STATUS { + QUEUED = "QUEUED", + EXECUTING = "EXECUTING", + SUCCESS = "SUCCESS", + FAILURE = "FAILURE", + INTERRUPTED = "INTERRUPTED" + } + + export interface Result { + data: string; + mimeType: string; + } + + export interface params { + notebookUri: string; + cellUri: string; + status: STATUS; + errorStream: Result; + diagnostics: string[]; + errorDiagnostics: string[]; + outputStream: Result; + metadata?: any; + } + export const type = new ProtocolNotificationType('notebook/execution/progress'); +} + +export interface NotebookCellStateRequestParams { notebookUri: string; cellUri: string; } -export namespace NotebookCellContentRequest { - export const type = new ProtocolRequestType('notebookDocument/cellContentRequest'); + +export interface NotebookCellStateResponse { + version: number; + text: string; +} + +export namespace NotebookCellStateRequest { + export const type = new ProtocolRequestType('notebook/cell/state'); } diff --git a/vscode/src/notebooks/codeCellExecution.ts b/vscode/src/notebooks/codeCellExecution.ts new file mode 100644 index 0000000..09b4781 --- /dev/null +++ b/vscode/src/notebooks/codeCellExecution.ts @@ -0,0 +1,122 @@ +import { commands, NotebookCell, NotebookCellExecution, NotebookCellOutput, NotebookController } from "vscode"; +import { LOGGER } from "../logger"; +import { NotebookCellExecutionResult } from "../lsp/protocol"; +import { createErrorOutputItem } from "./utils"; +import { nbCommands } from "../commands/commands"; +import { mimeTypes } from "./constants"; +import { MimeTypeHandler } from "./mimeTypeHandler"; + +export class CodeCellExecution { + private controller?: NotebookController; + private execution?: NotebookCellExecution; + private isExecutionStarted: boolean = false; + private mimeMap = new Map(); + private output: NotebookCellOutput = new NotebookCellOutput([]); + + constructor( + private controllerId: string, + private notebookId: string, + private cell: NotebookCell + ) { } + + public queued = async (controller: NotebookController | undefined) => { + if (!controller) { + LOGGER.warn(`Received undefined controller ${this.getCellId()}`); + return; + } + LOGGER.log(`${this.getCellId()} queued for execution`); + this.controller = controller; + } + + public executing = async (out: NotebookCellExecutionResult.Result | undefined, + err: NotebookCellExecutionResult.Result | undefined, + diagnostics: string[] | undefined, + errorDiagnostics: string[] | undefined, + metadata: any, + executionOrder: number | undefined) => { + + if (!this.isExecutionStarted) { + this.handleExecutionStart(executionOrder); + } + if (!this.execution) { + return + } + + if (out) { + const { data, mimeType } = out; + const newData = new TextDecoder().decode(Uint8Array.from(data)); + this.handleOutput(newData, mimeType); + } + + if (err) { + const { data } = err; + const newData = new TextDecoder().decode(Uint8Array.from(data)); + this.handleOutput(newData, mimeTypes.ERROR, true); + } + if (diagnostics) { + diagnostics.forEach(diag => { + this.handleOutput(diag + "\n", mimeTypes.TEXT); + }); + } + + if (errorDiagnostics) { + errorDiagnostics.forEach(diag => { + this.handleOutput(diag + "\n", mimeTypes.ERROR, true); + }); + } + } + + private handleOutput = async (data: string, mimeType: string, isError: boolean = false) => { + const oldData = this.mimeMap.get(mimeType) ?? ""; + const updatedData = oldData + data; + this.mimeMap.set(mimeType, updatedData); + + if (isError) { + await this.execution!.replaceOutputItems(createErrorOutputItem(updatedData), this.output); + } else { + await this.execution!.replaceOutputItems( + new MimeTypeHandler(mimeType).makeOutputItem(updatedData), + this.output + ); + } + } + + public executionCompleted = (status: boolean) => { + if (this.isExecutionStarted) { + status ? + LOGGER.log(`${this.getCellId()} successfully executed`) + : + LOGGER.error(`${this.getCellId()} failed while executing`); + this.execution!.end(status, Date.now()); + } + } + + public executionInterrupted = () => { + if (this.isExecutionStarted) { + LOGGER.log(`${this.getCellId()} interrupted while executing`); + this.execution!.end(false, Date.now()); + } + } + + private handleExecutionStart = async (executionOrder: number | undefined) => { + if (this.controller) { + this.execution = this.controller.createNotebookCellExecution(this.cell); + this.isExecutionStarted = true; + this.execution.start(Date.now()); + this.execution.executionOrder = executionOrder; + this.execution.clearOutput(); + await this.execution.replaceOutput(this.output); + this.execution.token.onCancellationRequested(async () => { + await commands.executeCommand(nbCommands.interruptNotebookCellExecution, this.notebookId); + }); + return; + } + LOGGER.warn(`Controller for cell is not created yet ${this.getCellId()}`); + } + + public getCellId = () => this.cell.document.uri.toString(); + + public getControllerId = () => this.controllerId; + + public getNotebookId = () => this.notebookId; +} \ No newline at end of file diff --git a/vscode/src/notebooks/constants.ts b/vscode/src/notebooks/constants.ts new file mode 100644 index 0000000..98083a7 --- /dev/null +++ b/vscode/src/notebooks/constants.ts @@ -0,0 +1,23 @@ + +export namespace ijnbConstants { + export const NOTEBOOK_TYPE = 'ijnb-notebook'; + export const KERNEL_ID = 'ijnb-kernel-ijnb'; + export const KERNEL_LABEL = 'IJNB Kernel'; +} + +export namespace ipynbConstants { + export const NOTEBOOK_TYPE = 'jupyter-notebook'; + export const KERNEL_ID = 'ijnb-kernel-jupyter'; + export const KERNEL_LABEL = 'IJNB Kernel'; +} + +export namespace supportLanguages { + export const JAVA = 'java'; + export const MARKDOWN = 'markdown'; +} + +export namespace mimeTypes { + export const TEXT = "text/plain"; + export const ERROR = "text/plain"; + export const MARKDOWN = "text/markdown"; +} \ No newline at end of file diff --git a/vscode/src/notebooks/executionSummary.ts b/vscode/src/notebooks/executionSummary.ts new file mode 100644 index 0000000..034aa41 --- /dev/null +++ b/vscode/src/notebooks/executionSummary.ts @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * 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. + */ + +export interface ExecutionSummaryData { + executionOrder?: number | null; + success?: boolean; +} + +export class ExecutionSummary { + constructor( + public executionOrder: number | null = null, + public success: boolean = false + ) {} + + static fromMetadata( + meta?: ExecutionSummaryData, + fallbackExecCount?: number | null + ): ExecutionSummary { + const order = + meta?.executionOrder != null + ? meta.executionOrder + : fallbackExecCount ?? null; + const success = meta?.success ?? false; + return new ExecutionSummary(order, success); + } + + toJSON(): ExecutionSummaryData { + return { + executionOrder: this.executionOrder, + success: this.success, + }; + } +} diff --git a/vscode/src/notebooks/impl.ts b/vscode/src/notebooks/impl.ts deleted file mode 100644 index f336dff..0000000 --- a/vscode/src/notebooks/impl.ts +++ /dev/null @@ -1,332 +0,0 @@ -import * as vscode from 'vscode'; -import { LanguageClient } from 'vscode-languageclient/node'; -import { isNbCommandRegistered } from '../commands/utils'; -import { nbCommands } from '../commands/commands'; -import { globalState } from '../globalState'; -import { getConfigurationValue } from '../configurations/handlers'; -import { configKeys } from '../configurations/configuration'; - -export class IJNBNotebookSerializer implements vscode.NotebookSerializer { - async deserializeNotebook(content: Uint8Array, _token: vscode.CancellationToken): Promise { - let notebook: any; - const contents = this.decoder.decode(content); - - console.log(`Content Size: ${content.length}`); - console.log("Decoded Content: ", contents); - - if (!contents.trim()) { - return new vscode.NotebookData([ - new vscode.NotebookCellData( - vscode.NotebookCellKind.Markup, - '# Error: Empty Notebook\n\nThe notebook file appears to be empty.', - 'markdown' - ) - ]); - } - - try { - notebook = JSON.parse(contents); - } catch (error) { - console.error('Failed to parse notebook content:', error); - vscode.window.showErrorMessage(`Failed to open notebook: ${(error as Error).message}`); - - return new vscode.NotebookData([ - new vscode.NotebookCellData( - vscode.NotebookCellKind.Markup, - `# Error Opening Notebook\n\nThere was an error parsing the notebook file. The file may be corrupted or in an invalid format.\n\nError details: ${(error as Error).message}`, - 'markdown' - ) - ]); - } - - if (!notebook || !Array.isArray(notebook.cells)) { - console.error('Invalid notebook structure'); - vscode.window.showErrorMessage('The notebook file has an invalid structure.'); - return new vscode.NotebookData([ - new vscode.NotebookCellData( - vscode.NotebookCellKind.Markup, - '# Error: Invalid Notebook Structure\n\nThe notebook file does not have the expected structure.', - 'markdown' - ) - ]); - } - - const cells = notebook.cells.map((cell: any) => { - if (typeof cell.cell_type !== 'string' || !Array.isArray(cell.source)) { - console.warn('Invalid cell structure:', cell); - return new vscode.NotebookCellData( - vscode.NotebookCellKind.Markup, - '# Error: Invalid Cell\n\nThis cell could not be parsed correctly.', - 'markdown' - ); - } - - const cellContent = cell.source.join(''); - const cellData = new vscode.NotebookCellData( - cell.cell_type === 'code' ? vscode.NotebookCellKind.Code : vscode.NotebookCellKind.Markup, - cellContent, - cell.cell_type === 'code' ? 'java' : 'markdown' - ); - - if (cell.outputs && Array.isArray(cell.outputs)) { - const outputs: vscode.NotebookCellOutput[] = []; - - for (const output of cell.outputs) { - if (output.output_type === 'display_data' || output.output_type === 'execute_result') { - const items: vscode.NotebookCellOutputItem[] = []; - - if (output.data) { - // Handle text/plain - if (output.data['text/plain']) { - const text = Array.isArray(output.data['text/plain']) - ? output.data['text/plain'].join('\n') - : output.data['text/plain']; - items.push(vscode.NotebookCellOutputItem.text(text, 'text/plain')); - } - - // Handle image formats - for (const mimeType in output.data) { - if (mimeType.startsWith('image/')) { - const base64Data = Array.isArray(output.data[mimeType]) - ? output.data[mimeType].join('') - : output.data[mimeType]; - - // Convert base64 string to Uint8Array - const imageData = this.base64ToUint8Array(base64Data); - items.push(new vscode.NotebookCellOutputItem(imageData, mimeType)); - } - } - } - - if (items.length > 0) { - outputs.push(new vscode.NotebookCellOutput(items, output.metadata)); - } - } else if (output.output_type === 'stream') { - const text = Array.isArray(output.text) ? output.text.join('') : output.text; - outputs.push(new vscode.NotebookCellOutput([ - vscode.NotebookCellOutputItem.text(text) - ])); - } else if (output.output_type === 'error') { - outputs.push(new vscode.NotebookCellOutput([ - vscode.NotebookCellOutputItem.error({ - name: output.ename || 'Error', - message: output.evalue || 'Unknown error', - stack: output.traceback ? output.traceback.join('\n') : undefined - }) - ])); - } - } - - if (outputs.length > 0) { - cellData.outputs = outputs; - } - } - - if (cell.execution_count !== null && cell.execution_count !== undefined) { - cellData.executionSummary = { - executionOrder: cell.execution_count, - success: true - }; - } - - return cellData; - }); - - return new vscode.NotebookData(cells); - } - - async serializeNotebook(data: vscode.NotebookData, _token: vscode.CancellationToken): Promise { - const notebook = { - cells: data.cells.map(cell => { - const cellData: any = { - cell_type: cell.kind === vscode.NotebookCellKind.Code ? 'code' : 'markdown', - source: [cell.value], - metadata: { - ...cell.metadata, - language: cell.languageId - }, - execution_count: cell.executionSummary?.executionOrder ?? null, - outputs: [] - }; - - if (cell.outputs && cell.outputs.length > 0) { - cellData.outputs = cell.outputs.map(output => { - const outputData: any = { - output_type: 'execute_result', - data: {}, - metadata: output.metadata, - execution_count: cell.executionSummary?.executionOrder ?? null - }; - - for (const item of output.items) { - const errorItem = item.mime === 'application/vnd.code.notebook.error'; - if (errorItem) { - const errorData = JSON.parse(Buffer.from(item.data).toString()); - return { - output_type: 'error', - ename: errorData.name, - evalue: errorData.message, - traceback: errorData.stack ? [errorData.stack] : [] - }; - } - - // Handle text items - if (item.mime === 'text/plain') { - outputData.data['text/plain'] = Buffer.from(item.data).toString(); - } - // Handle image items - else if (item.mime.startsWith('image/')) { - // Convert binary data to base64 string for storage - outputData.data[item.mime] = this.uint8ArrayToBase64(item.data); - } - } - - return outputData; - }); - } - - return cellData; - }), - metadata: { - language_info: { - name: 'java' - }, - orig_nbformat: 4 - }, - nbformat: 4, - nbformat_minor: 2 - }; - - return this.encoder.encode(JSON.stringify(notebook, null, 2)); - } - - private readonly decoder = new TextDecoder(); - private readonly encoder = new TextEncoder(); - - private base64ToUint8Array(base64: string): Uint8Array { - if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') { - return Buffer.from(base64, 'base64'); - } else { - return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); - } - } - - private uint8ArrayToBase64(data: Uint8Array): string { - if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') { - return Buffer.from(data).toString('base64'); - } else { - return btoa(String.fromCharCode.apply(null, Array.from(data))); - } - } -} - -export class IJNBKernel implements vscode.Disposable { - private readonly controller: vscode.NotebookController; - - constructor() { - this.controller = vscode.notebooks.createNotebookController( - 'ijnb-kernel', - 'ijnb-notebook', - 'IJNB Kernel' - ); - - this.controller.supportedLanguages = ['markdown', 'java']; - this.controller.supportsExecutionOrder = true; - this.controller.executeHandler = this.executeCell.bind(this); - } - - dispose() { - this.controller.dispose(); - } - - private async executeCell(cells: vscode.NotebookCell[], notebook: vscode.NotebookDocument, _controller: vscode.NotebookController): Promise { - console.log("Starting execution for cells:", cells); - - const notebookId = notebook.uri.toString(); - const classpath = getConfigurationValue(configKeys.notebookClasspath); - - for (let cell of cells) { - console.log("Executing cell:", cell.document.getText()); - - const execution = this.controller.createNotebookCellExecution(cell); - execution.executionOrder = 1; - execution.start(Date.now()); - - try { - const cellContent = cell.document.getText(); - console.log("Cell content:", cellContent); - - if (cell.document.languageId === 'markdown') { - await execution.replaceOutput([ - new vscode.NotebookCellOutput([ - vscode.NotebookCellOutputItem.text(cellContent) - ]) - ]); - } else { - await globalState.getClientPromise().client; - if (await isNbCommandRegistered(nbCommands.executeNotebookCell)) { - const response: { data: string, mimeType: string }[] = await vscode.commands.executeCommand( - nbCommands.executeNotebookCell, - cellContent, - notebookId, - classpath || null - ); - - console.log("Response:", response); - const outputMap: Map = new Map(); - response.forEach((el) => { - if (!outputMap.has(el.mimeType)) { - outputMap.set(el.mimeType, [el.data]); - } else { - outputMap.set(el.mimeType, [...outputMap.get(el.mimeType)!, el.data]); - } - }); - const output: vscode.NotebookCellOutputItem[] = []; - for (const [mimeType, value] of outputMap) { - if (mimeType.startsWith('image')) { - output.push(createNotebookCellOutput(value[0], mimeType)); - } else { - output.push(createNotebookCellOutput(value.join("\n"), mimeType)); - } - } - await execution.replaceOutput([ - new vscode.NotebookCellOutput(output.length ? output : [vscode.NotebookCellOutputItem.text('')]) - ]); - } else { - throw new Error("Notebook not supported"); - } - } - - execution.end(true, Date.now()); - } catch (err) { - console.error(`Error executing cell: ${err}`); - - await execution.replaceOutput([ - new vscode.NotebookCellOutput([ - vscode.NotebookCellOutputItem.error({ - name: 'Execution Error', - message: `${err}` - }) - ]) - ]); - - execution.end(false, Date.now()); - } - } - } -} - -const createNotebookCellOutput = (data: string, mimeType: string): vscode.NotebookCellOutputItem => { - if (mimeType.startsWith('image')) { - return new vscode.NotebookCellOutputItem(base64ToUint8Array(data), mimeType); - } - return vscode.NotebookCellOutputItem.text(data, mimeType); -} - -export function base64ToUint8Array(base64: string): Uint8Array { - if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') { - return Buffer.from(base64, 'base64'); - } else { - return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); - } -} \ No newline at end of file diff --git a/vscode/src/notebooks/kernel.ts b/vscode/src/notebooks/kernel.ts new file mode 100644 index 0000000..409f86d --- /dev/null +++ b/vscode/src/notebooks/kernel.ts @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * 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. + */ + +import { globalState } from '../globalState'; +import { isNbCommandRegistered } from '../commands/utils'; +import { nbCommands } from '../commands/commands'; +import { NotebookCellExecutionResult } from '../lsp/protocol'; +import { NotebookCell, NotebookController, NotebookDocument, Disposable, notebooks, commands, NotebookCellOutput, workspace } from 'vscode'; +import { LanguageClient } from 'vscode-languageclient/node'; +import { l10n } from '../localiser'; +import { ijnbConstants, ipynbConstants, supportLanguages } from './constants'; +import { LOGGER } from '../logger'; +import { CodeCellExecution } from './codeCellExecution'; +import { isError, isString } from '../utils'; +import { MimeTypeHandler } from './mimeTypeHandler'; +import { createErrorOutput } from './utils'; + +export class IJNBKernel implements Disposable { + private readonly controllers: NotebookController[] = []; + private cellControllerIdMap = new Map(); + static executionCounter = new Map(); + + constructor() { + const custom = notebooks.createNotebookController( + ijnbConstants.KERNEL_ID, + ijnbConstants.NOTEBOOK_TYPE, + ijnbConstants.KERNEL_LABEL + ); + + const jupyter = notebooks.createNotebookController( + ipynbConstants.KERNEL_ID, + ipynbConstants.NOTEBOOK_TYPE, + ipynbConstants.KERNEL_LABEL + ); + + for (const ctr of [custom, jupyter]) { + ctr.supportedLanguages = [supportLanguages.JAVA, supportLanguages.MARKDOWN]; + ctr.supportsExecutionOrder = true; + ctr.executeHandler = this.executeCells.bind(this); + this.controllers.push(ctr); + } + } + + dispose(): void { + for (const ctr of this.controllers) { + ctr.dispose(); + } + } + + private async executeCells( + cells: NotebookCell[], + notebook: NotebookDocument, + controller: NotebookController + ): Promise { + const notebookId = notebook.uri.toString(); + + for (const cell of cells) { + if (cell.document.languageId === supportLanguages.MARKDOWN) { + await this.handleMarkdownCellExecution(notebookId, cell, controller); + } else if (cell.document.languageId === supportLanguages.JAVA) { + await this.handleCodeCellExecution(notebookId, cell, controller); + } else { + await this.handleUnkownLanguageTypeExecution(notebookId, cell, controller); + } + } + } + + private handleCodeCellExecution = async (notebookId: string, cell: NotebookCell, controller: NotebookController) => { + const cellId = cell.document.uri.toString(); + const sourceCode = cell.document.getText(); + const codeCellExecution = new CodeCellExecution(controller.id, notebookId, cell); + this.getIncrementedExecutionCounter(notebookId); + try { + this.cellControllerIdMap.set(cellId, codeCellExecution); + const client: LanguageClient = await globalState.getClientPromise().client; + + if (!(await isNbCommandRegistered(nbCommands.executeNotebookCell))) { + throw l10n.value("jdk.extension.error_msg.doesntSupportNoteboookCellExecution", { client }); + } + + const response = await commands.executeCommand(nbCommands.executeNotebookCell, + notebookId, + cellId, + sourceCode); + + if (!response) { + LOGGER.error(`Some error occurred while cell execution: ${cellId}`); + } + } catch (error) { + LOGGER.error(isError(error) ? error.message : String(error)); + } finally { + this.cellControllerIdMap.delete(cellId); + } + } + + public handleCellExecutionNotification = async (params: NotebookCellExecutionResult.params) => { + const codeCellExecution = this.cellControllerIdMap.get(params.cellUri); + if (!codeCellExecution) { + LOGGER.warn(`There is no code cell execution object created for ${params.cellUri}`); + return; + } + switch (params.status) { + case NotebookCellExecutionResult.STATUS.QUEUED: + const controller = this.controllers.find(el => el.id === codeCellExecution.getControllerId()); + codeCellExecution.queued(controller); + break; + case NotebookCellExecutionResult.STATUS.EXECUTING: + const { outputStream, errorStream, diagnostics, errorDiagnostics, metadata } = params; + await codeCellExecution.executing(outputStream, errorStream, diagnostics, errorDiagnostics, metadata, this.getExecutionCounter(codeCellExecution.getNotebookId())); + break; + case NotebookCellExecutionResult.STATUS.SUCCESS: + codeCellExecution.executionCompleted(true); + this.cellControllerIdMap.delete(params.cellUri); + break; + case NotebookCellExecutionResult.STATUS.FAILURE: + codeCellExecution.executionCompleted(false); + this.cellControllerIdMap.delete(params.cellUri); + break; + case NotebookCellExecutionResult.STATUS.INTERRUPTED: + codeCellExecution.executionInterrupted(); + this.cellControllerIdMap.delete(params.cellUri); + break; + } + } + + private handleUnkownLanguageTypeExecution = async (notebookId: string, cell: NotebookCell, controller: NotebookController) => { + const exec = controller.createNotebookCellExecution(cell); + exec.executionOrder = this.getIncrementedExecutionCounter(notebookId); + exec.start(Date.now()); + await exec.replaceOutput(createErrorOutput(new Error(`Doesn't support ${cell.document.languageId} execution`))); + exec.end(false, Date.now()); + } + + private getIncrementedExecutionCounter = (notebookId: string) => { + const next = (IJNBKernel.executionCounter.get(notebookId) ?? 0) + 1; + IJNBKernel.executionCounter.set(notebookId, next); + return next; + } + + private getExecutionCounter = (notebookId: string) => { + return IJNBKernel.executionCounter.get(notebookId); + } + + private handleMarkdownCellExecution = async (notebookId: string, cell: NotebookCell, controller: NotebookController) => { + const exec = controller.createNotebookCellExecution(cell); + const mimeType = 'text/markdown'; + exec.executionOrder = this.getIncrementedExecutionCounter(notebookId); + try { + exec.start(Date.now()); + await exec.replaceOutput([ + new NotebookCellOutput([new MimeTypeHandler(mimeType).makeOutputItem(cell.document.getText())]), + ]); + exec.end(true, Date.now()); + } catch (error) { + await exec.replaceOutput(createErrorOutput(error as Error)); + exec.end(false, Date.now()); + } + } + + cleanUpKernel = workspace.onDidCloseNotebookDocument(doc => { + if (doc.notebookType === ijnbConstants.NOTEBOOK_TYPE) { + IJNBKernel.executionCounter.delete(doc.uri.toString()); + } + }); +} + +export const notebookKernel = new IJNBKernel(); \ No newline at end of file diff --git a/vscode/src/notebooks/mimeTypeHandler.ts b/vscode/src/notebooks/mimeTypeHandler.ts new file mode 100644 index 0000000..4df267e --- /dev/null +++ b/vscode/src/notebooks/mimeTypeHandler.ts @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * 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. + */ + +import { Buffer } from 'buffer'; +import * as vscode from 'vscode'; +import { IMimeBundle } from './types'; +import { mimeTypes } from './constants'; + +export type DataOrBytes = string | Uint8Array; + +export class MimeTypeHandler { + constructor(public readonly value: string) {} + get isText(): boolean { + return this.value === mimeTypes.TEXT; + } + get isImage(): boolean { + return this.value.startsWith('image/'); + } + + static toBytes(data: DataOrBytes): Uint8Array { + if (typeof data === 'string') { + return Buffer.from(data, 'base64'); + } + return data; + } + + static toString(data: DataOrBytes): string { + if (typeof data === 'string') { + return data; + } + return new TextDecoder().decode(data); + } + + makeOutputItem(data: DataOrBytes): vscode.NotebookCellOutputItem { + if (this.isImage) { + const bytes = MimeTypeHandler.toBytes(data); + return new vscode.NotebookCellOutputItem(bytes, this.value); + } + const text = MimeTypeHandler.toString(data); + return vscode.NotebookCellOutputItem.text(text, this.value); + } + + static itemsFromBundle(bundle: IMimeBundle): vscode.NotebookCellOutputItem[] { + return Object.entries(bundle).flatMap(([mime, data]) => { + const mt = new MimeTypeHandler(mime); + if (mt.isText || mt.isImage) { + const payload = Array.isArray(data) ? data.join('') : data; + return [mt.makeOutputItem(payload)]; + } + return []; + }); + } +} diff --git a/vscode/src/notebooks/nbformat.v4.d7.schema.json b/vscode/src/notebooks/nbformat.v4.d7.schema.json new file mode 100644 index 0000000..22585f4 --- /dev/null +++ b/vscode/src/notebooks/nbformat.v4.d7.schema.json @@ -0,0 +1,594 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "description": "Jupyter Notebook v4.5 JSON schema.", + "type": "object", + "additionalProperties": false, + "required": [ + "metadata", + "nbformat_minor", + "nbformat", + "cells" + ], + "properties": { + "metadata": { + "description": "Notebook root-level metadata.", + "type": "object", + "additionalProperties": true, + "properties": { + "kernelspec": { + "description": "Kernel information.", + "type": "object", + "required": [ + "name", + "display_name" + ], + "properties": { + "name": { + "description": "Name of the kernel specification.", + "type": "string" + }, + "display_name": { + "description": "Name to display in UI.", + "type": "string" + } + } + }, + "language_info": { + "description": "Kernel information.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "description": "The programming language which this kernel runs.", + "type": "string" + }, + "codemirror_mode": { + "description": "The codemirror mode to use for code in this language.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "file_extension": { + "description": "The file extension for files in this language.", + "type": "string" + }, + "mimetype": { + "description": "The mimetype corresponding to files in this language.", + "type": "string" + }, + "pygments_lexer": { + "description": "The pygments lexer to use for code in this language.", + "type": "string" + } + } + }, + "orig_nbformat": { + "description": "Original notebook format (major number) before converting the notebook between versions. This should never be written to a file.", + "type": "integer", + "minimum": 1 + }, + "title": { + "description": "The title of the notebook document", + "type": "string" + }, + "authors": { + "description": "The author(s) of the notebook document", + "type": "array", + "item": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "additionalProperties": true + } + } + } + }, + "nbformat_minor": { + "description": "Notebook format (minor number). Incremented for backward compatible changes to the notebook format.", + "type": "integer", + "minimum": 5 + }, + "nbformat": { + "description": "Notebook format (major number). Incremented between backwards incompatible changes to the notebook format.", + "type": "integer", + "minimum": 4, + "maximum": 4 + }, + "cells": { + "description": "Array of cells of the current notebook.", + "type": "array", + "items": { + "$ref": "#/definitions/cell" + } + } + }, + "definitions": { + "cell_id": { + "description": "A string field representing the identifier of this particular cell.", + "type": "string", + "pattern": "^[a-zA-Z0-9-_]+$", + "minLength": 1, + "maxLength": 64 + }, + "cell": { + "type": "object", + "oneOf": [ + { + "$ref": "#/definitions/raw_cell" + }, + { + "$ref": "#/definitions/markdown_cell" + }, + { + "$ref": "#/definitions/code_cell" + } + ] + }, + "raw_cell": { + "description": "Notebook raw nbconvert cell.", + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "cell_type", + "metadata", + "source" + ], + "properties": { + "id": { + "$ref": "#/definitions/cell_id" + }, + "cell_type": { + "description": "String identifying the type of cell.", + "const": "raw" + }, + "metadata": { + "description": "Cell-level metadata.", + "type": "object", + "additionalProperties": true, + "properties": { + "format": { + "description": "Raw cell metadata format for nbconvert.", + "type": "string" + }, + "jupyter": { + "description": "Official Jupyter Metadata for Raw Cells", + "type": "object", + "additionalProperties": true, + "source_hidden": { + "description": "Whether the source is hidden.", + "type": "boolean" + } + }, + "name": { + "$ref": "#/definitions/misc/metadata_name" + }, + "tags": { + "$ref": "#/definitions/misc/metadata_tags" + } + } + }, + "attachments": { + "$ref": "#/definitions/misc/attachments" + }, + "source": { + "$ref": "#/definitions/misc/source" + } + } + }, + "markdown_cell": { + "description": "Notebook markdown cell.", + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "cell_type", + "metadata", + "source" + ], + "properties": { + "id": { + "$ref": "#/definitions/cell_id" + }, + "cell_type": { + "description": "String identifying the type of cell.", + "const": "markdown" + }, + "metadata": { + "description": "Cell-level metadata.", + "type": "object", + "properties": { + "name": { + "$ref": "#/definitions/misc/metadata_name" + }, + "tags": { + "$ref": "#/definitions/misc/metadata_tags" + }, + "jupyter": { + "description": "Official Jupyter Metadata for Markdown Cells", + "type": "object", + "additionalProperties": true, + "source_hidden": { + "description": "Whether the source is hidden.", + "type": "boolean" + } + } + }, + "additionalProperties": true + }, + "attachments": { + "$ref": "#/definitions/misc/attachments" + }, + "source": { + "$ref": "#/definitions/misc/source" + } + } + }, + "code_cell": { + "description": "Notebook code cell.", + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "cell_type", + "metadata", + "source", + "outputs", + "execution_count" + ], + "properties": { + "id": { + "$ref": "#/definitions/cell_id" + }, + "cell_type": { + "description": "String identifying the type of cell.", + "const": "code" + }, + "metadata": { + "description": "Cell-level metadata.", + "type": "object", + "additionalProperties": true, + "properties": { + "jupyter": { + "description": "Official Jupyter Metadata for Code Cells", + "type": "object", + "additionalProperties": true, + "source_hidden": { + "description": "Whether the source is hidden.", + "type": "boolean" + }, + "outputs_hidden": { + "description": "Whether the outputs are hidden.", + "type": "boolean" + } + }, + "execution": { + "description": "Execution time for the code in the cell. This tracks time at which messages are received from iopub or shell channels", + "type": "object", + "properties": { + "iopub.execute_input": { + "description": "header.date (in ISO 8601 format) of iopub channel's execute_input message. It indicates the time at which the kernel broadcasts an execute_input message to connected frontends", + "type": "string" + }, + "iopub.status.busy": { + "description": "header.date (in ISO 8601 format) of iopub channel's kernel status message when the status is 'busy'", + "type": "string" + }, + "shell.execute_reply": { + "description": "header.date (in ISO 8601 format) of the shell channel's execute_reply message. It indicates the time at which the execute_reply message was created", + "type": "string" + }, + "iopub.status.idle": { + "description": "header.date (in ISO 8601 format) of iopub channel's kernel status message when the status is 'idle'. It indicates the time at which kernel finished processing the associated request", + "type": "string" + } + }, + "additionalProperties": true, + "patternProperties": { + "^.*$": { + "type": "string" + } + } + }, + "collapsed": { + "description": "Whether the cell's output is collapsed/expanded.", + "type": "boolean" + }, + "scrolled": { + "description": "Whether the cell's output is scrolled, unscrolled, or autoscrolled.", + "enum": [ + true, + false, + "auto" + ] + }, + "name": { + "$ref": "#/definitions/misc/metadata_name" + }, + "tags": { + "$ref": "#/definitions/misc/metadata_tags" + } + } + }, + "source": { + "$ref": "#/definitions/misc/source" + }, + "outputs": { + "description": "Execution, display, or stream outputs.", + "type": "array", + "items": { + "$ref": "#/definitions/output" + } + }, + "execution_count": { + "description": "The code cell's prompt number. Will be null if the cell has not been run.", + "type": [ + "integer", + "null" + ], + "minimum": 0 + } + } + }, + "unrecognized_cell": { + "description": "Unrecognized cell from a future minor-revision to the notebook format.", + "type": "object", + "additionalProperties": true, + "required": [ + "cell_type", + "metadata" + ], + "properties": { + "cell_type": { + "description": "String identifying the type of cell.", + "not": { + "enum": [ + "markdown", + "code", + "raw" + ] + } + }, + "metadata": { + "description": "Cell-level metadata.", + "type": "object", + "properties": { + "name": { + "$ref": "#/definitions/misc/metadata_name" + }, + "tags": { + "$ref": "#/definitions/misc/metadata_tags" + } + }, + "additionalProperties": true + } + } + }, + "output": { + "type": "object", + "oneOf": [ + { + "$ref": "#/definitions/execute_result" + }, + { + "$ref": "#/definitions/display_data" + }, + { + "$ref": "#/definitions/stream" + }, + { + "$ref": "#/definitions/error" + } + ] + }, + "execute_result": { + "description": "Result of executing a code cell.", + "type": "object", + "additionalProperties": false, + "required": [ + "output_type", + "data", + "metadata", + "execution_count" + ], + "properties": { + "output_type": { + "description": "Type of cell output.", + "const": "execute_result" + }, + "execution_count": { + "description": "A result's prompt number.", + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "data": { + "$ref": "#/definitions/misc/mimebundle" + }, + "metadata": { + "$ref": "#/definitions/misc/output_metadata" + } + } + }, + "display_data": { + "description": "Data displayed as a result of code cell execution.", + "type": "object", + "additionalProperties": false, + "required": [ + "output_type", + "data", + "metadata" + ], + "properties": { + "output_type": { + "description": "Type of cell output.", + "const": "display_data" + }, + "data": { + "$ref": "#/definitions/misc/mimebundle" + }, + "metadata": { + "$ref": "#/definitions/misc/output_metadata" + } + } + }, + "stream": { + "description": "Stream output from a code cell.", + "type": "object", + "additionalProperties": false, + "required": [ + "output_type", + "name", + "text" + ], + "properties": { + "output_type": { + "description": "Type of cell output.", + "const": "stream" + }, + "name": { + "description": "The name of the stream (stdout, stderr).", + "type": "string" + }, + "text": { + "description": "The stream's text output, represented as an array of strings.", + "$ref": "#/definitions/misc/multiline_string" + } + } + }, + "error": { + "description": "Output of an error that occurred during code cell execution.", + "type": "object", + "additionalProperties": false, + "required": [ + "output_type", + "ename", + "evalue", + "traceback" + ], + "properties": { + "output_type": { + "description": "Type of cell output.", + "const": "error" + }, + "ename": { + "description": "The name of the error.", + "type": "string" + }, + "evalue": { + "description": "The value, or message, of the error.", + "type": "string" + }, + "traceback": { + "description": "The error's traceback, represented as an array of strings.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "unrecognized_output": { + "description": "Unrecognized output from a future minor-revision to the notebook format.", + "type": "object", + "additionalProperties": true, + "required": [ + "output_type" + ], + "properties": { + "output_type": { + "description": "Type of cell output.", + "not": { + "enum": [ + "execute_result", + "display_data", + "stream", + "error" + ] + } + } + } + }, + "misc": { + "metadata_name": { + "description": "The cell's name. If present, must be a non-empty string. Cell names are expected to be unique across all the cells in a given notebook. This criterion cannot be checked by the json schema and must be established by an additional check.", + "type": "string", + "pattern": "^.+$" + }, + "metadata_tags": { + "description": "The cell's tags. Tags must be unique, and must not contain commas.", + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[^,]+$" + } + }, + "attachments": { + "description": "Media attachments (e.g. inline images), stored as mimebundle keyed by filename.", + "type": "object", + "patternProperties": { + ".*": { + "description": "The attachment's data stored as a mimebundle.", + "$ref": "#/definitions/misc/mimebundle" + } + } + }, + "source": { + "description": "Contents of the cell, represented as an array of lines.", + "$ref": "#/definitions/misc/multiline_string" + }, + "execution_count": { + "description": "The code cell's prompt number. Will be null if the cell has not been run.", + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "mimebundle": { + "description": "A mime-type keyed dictionary of data", + "type": "object", + "additionalProperties": { + "description": "mimetype output (e.g. text/plain), represented as either an array of strings or a string.", + "$ref": "#/definitions/misc/multiline_string" + }, + "patternProperties": { + "^application/(.*\\+)?json$": { + "description": "Mimetypes with JSON output, can be any type" + } + } + }, + "output_metadata": { + "description": "Cell output metadata.", + "type": "object", + "additionalProperties": true + }, + "multiline_string": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/vscode/src/notebooks/notebook.ts b/vscode/src/notebooks/notebook.ts new file mode 100644 index 0000000..d114e59 --- /dev/null +++ b/vscode/src/notebooks/notebook.ts @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * 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. + */ + +import * as vscode from 'vscode'; +import { ICell, INotebook } from './types'; +import { serializeCell } from './utils'; +import Ajv = require('ajv'); +import schema = require('./nbformat.v4.d7.schema.json'); + +export class NotebookVersionInfo { + static readonly NBFORMAT = 4; + static readonly NBFORMAT_MINOR = 5; +} + +export class Notebook { + readonly nbformat: number; + readonly nbformat_minor: number; + readonly metadata: { language_info: { name: string } }; + readonly cells: ICell[]; + static ajv = new Ajv(); + static validateFn = this.ajv.compile(schema); + + constructor(cells: ICell[], language: string = 'java') { + this.nbformat = NotebookVersionInfo.NBFORMAT; + this.nbformat_minor = NotebookVersionInfo.NBFORMAT_MINOR; + this.metadata = { language_info: { name: language } }; + this.cells = cells; + } + + static fromNotebookData( + data: vscode.NotebookData, + language: string = 'java' + ): Notebook { + const cells = data.cells.map((cell) => { + try { + return serializeCell(cell) + } catch (cellError) { + console.error('Error serializing cell: ', cell, cellError); + throw new Error('Failed to serialize one or more cells') + } + }) + return new Notebook(cells, language); + } + + toJSON(): INotebook { + return { + nbformat: this.nbformat, + nbformat_minor: this.nbformat_minor, + metadata: this.metadata, + cells: this.cells, + }; + } + + toUint8Array(): Uint8Array { + const json = JSON.stringify(this.toJSON(), null, 2); + return new TextEncoder().encode(json); + } + + assertValidNotebook(){ + Notebook.assertValidNotebookJson(this.toJSON()); + } + + static assertValidNotebookJson(notebook: INotebook) { + if (!Notebook.validateFn(notebook)) { + const errors = (Notebook.validateFn.errors || []) + .map(e => `${e.dataPath || '/'} ${e.message}`) + .join('\n'); + throw new Error(`Notebook JSON validation failed:\n${errors}`); + } + console.log("Notebook successfully validated."); + } +} diff --git a/vscode/src/notebooks/register.ts b/vscode/src/notebooks/register.ts index d9adb03..37a4f14 100644 --- a/vscode/src/notebooks/register.ts +++ b/vscode/src/notebooks/register.ts @@ -1,11 +1,35 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * 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. + */ + import { ExtensionContext, workspace } from "vscode"; -import { IJNBKernel, IJNBNotebookSerializer } from "./impl"; +import { notebookKernel } from "./kernel"; +import { notebookSerializer } from "./serializer"; -export const registerNotebooks = (context: ExtensionContext) => { +export const registerNotebookProviders = (context: ExtensionContext) => { context.subscriptions.push( workspace.registerNotebookSerializer( 'ijnb-notebook', - new IJNBNotebookSerializer()), - new IJNBKernel() - ); + notebookSerializer + )); + + context.subscriptions.push(notebookKernel); + context.subscriptions.push(notebookKernel.cleanUpKernel); } \ No newline at end of file diff --git a/vscode/src/notebooks/serializer.ts b/vscode/src/notebooks/serializer.ts new file mode 100644 index 0000000..10f1a9d --- /dev/null +++ b/vscode/src/notebooks/serializer.ts @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * 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. + */ + +import * as vscode from 'vscode'; +import { errorNotebook, parseCell } from './utils'; +import { ICell, INotebook } from './types'; +import { Notebook } from './notebook'; + +class IJNBNotebookSerializer implements vscode.NotebookSerializer { + private readonly decoder = new TextDecoder(); + + async deserializeNotebook( + content: Uint8Array, + _token: vscode.CancellationToken + ): Promise { + const raw = this.decoder.decode(content).trim(); + if (!raw) { + return errorNotebook('Empty Notebook', 'The notebook file appears to be empty.'); + } + + let parsed: INotebook; + try { + parsed = JSON.parse(raw) as INotebook; + Notebook.assertValidNotebookJson(parsed); + } catch (err) { + console.error('Failed to parse notebook content:', err); + vscode.window.showErrorMessage(`Failed to open notebook: ${(err as Error).message}`); + return errorNotebook( + 'Error Opening Notebook', + `Failed to parse notebook: ${(err as Error).message}` + ); + } + + if (!parsed || !Array.isArray(parsed.cells)) { + return errorNotebook('Invalid Notebook Structure', 'Missing or invalid `cells` array.'); + } + + let cells: vscode.NotebookCellData[]; + try { + cells = parsed.cells.map((cell: ICell) => parseCell(cell)); + } catch (cellError) { + return errorNotebook( + 'Cell parsing error', + (cellError as Error).message + ); + } + return new vscode.NotebookData(cells); + } + + async serializeNotebook( + data: vscode.NotebookData, + _token: vscode.CancellationToken + ): Promise { + try { + const notebook = Notebook.fromNotebookData(data, 'java'); + notebook.assertValidNotebook(); + return notebook.toUint8Array(); + } catch (err) { + console.error('Unhandled error in serializeNotebook:', err); + vscode.window.showErrorMessage(`Failed to serialize notebook: ${(err as Error).message}`); + throw err; + } + } +} + +export const notebookSerializer = new IJNBNotebookSerializer(); \ No newline at end of file diff --git a/vscode/src/notebooks/types.ts b/vscode/src/notebooks/types.ts new file mode 100644 index 0000000..ec6d061 --- /dev/null +++ b/vscode/src/notebooks/types.ts @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * 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. + */ + +export interface INotebook { + nbformat: number; + nbformat_minor: number; + metadata: INotebookMetadata; + cells: ICell[]; +} + +export interface INotebookMetadata { + language_info?: { + name: string; + version?: string; + }; + kernelspec?: { + name: string; + display_name?: string; + language?: string; + }; + [key: string]: unknown; +} + +export type ICell = IMarkdownCell | ICodeCell | IRawCell; + +interface IBaseCell { + id: string; + cell_type: string; + metadata: IMetadata | undefined; + source: string | string[]; +} + +export interface IMarkdownCell extends IBaseCell { + cell_type: 'markdown'; +} + +export interface IRawCell extends IBaseCell { + cell_type: 'raw'; +} + +export interface ICodeCell extends IBaseCell { + cell_type: 'code'; + execution_count: number | null; + outputs: IOutput[]; +} + +export type IOutput = + | IStreamOutput + | IErrorOutput + | IDisplayDataOutput + | IExecuteResultOutput; + +export interface IStreamOutput { + output_type: 'stream'; + name: 'stdout' | 'stderr'; + text: string | string[]; + metadata: IMetadata | undefined; +} + +export interface IErrorOutput { + output_type: 'error'; + ename: string; + evalue: string; + traceback: string[]; + metadata: IMetadata | undefined; +} + +export interface IDisplayDataOutput { + output_type: 'display_data'; + data: IMimeBundle; + metadata: IMetadata | undefined; +} + +export interface IExecuteResultOutput { + output_type: 'execute_result'; + data: IMimeBundle; + metadata: IMetadata | undefined; + execution_count: number | null; +} + +export interface IMimeBundle { + [mime: string]: string | string[]; +} + +export interface IMetadata { + [key: string]: any; +} \ No newline at end of file diff --git a/vscode/src/notebooks/utils.ts b/vscode/src/notebooks/utils.ts new file mode 100644 index 0000000..69b34bf --- /dev/null +++ b/vscode/src/notebooks/utils.ts @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * 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. + */ + +import { Buffer } from 'buffer'; +import * as vscode from 'vscode'; +import { + ICell, + ICodeCell, + IMarkdownCell, + IOutput, + IExecuteResultOutput, + IMimeBundle, + IMetadata +} from './types'; +import { randomUUID } from 'crypto'; +import { isString } from '../utils'; +import { mimeTypes } from './constants'; +import { MimeTypeHandler } from './mimeTypeHandler'; +import { ExecutionSummary } from './executionSummary'; + + +export function base64ToUint8Array(base64: string): Uint8Array { + if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') { + return Buffer.from(base64, 'base64'); + } + const binary = atob(base64); + const len = binary.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = binary.charCodeAt(i); + } + return bytes; +} + +export function uint8ArrayToBase64(data: Uint8Array): string { + if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') { + return Buffer.from(data).toString('base64'); + } + let binary = ''; + data.forEach((byte) => (binary += String.fromCharCode(byte))); + return btoa(binary); +} + +export const createErrorOutput = (err: string | Error) => { + return new vscode.NotebookCellOutput([createErrorOutputItem(err)]); +} + +export const createErrorOutputItem = (err: string | Error) => { + return vscode.NotebookCellOutputItem.text(isString(err) ? err: err.message); +} + +export function parseCell(cell: ICell): vscode.NotebookCellData { + if (cell.cell_type !== 'code' && cell.cell_type !== 'markdown') + throw new Error(`Invalid cell_type: ${cell.cell_type}`); + if (cell.source === undefined || cell.source === null) + throw new Error('Missing cell.source'); + const kind = + cell.cell_type === 'code' ? vscode.NotebookCellKind.Code : vscode.NotebookCellKind.Markup; + const language = kind === vscode.NotebookCellKind.Code ? 'java' : 'markdown'; + const value = Array.isArray(cell.source) ? cell.source.join('') : String(cell.source); + + const cellData = new vscode.NotebookCellData(kind, value, language); + cellData.metadata = { id: cell.id, ...cell.metadata }; + if (cell.cell_type === 'code') { + const execSummary = ExecutionSummary.fromMetadata( + (cell.metadata as IMetadata).executionSummary, + cell.execution_count ?? null + ); + if (execSummary.executionOrder) { + cellData.executionSummary = { + executionOrder: execSummary.executionOrder ?? undefined, + success: execSummary.success, + }; + } + + if (Array.isArray(cell.outputs)) { + const outputs = cell.outputs.flatMap((out: IOutput) => { + const parsed = parseOutput(out); + if (!parsed) { + throw new Error(`Unrecognized output format: ${JSON.stringify(out)}`); + } + return parsed; + }); + if (outputs.length) { + cellData.outputs = outputs; + } + } + } + if (cell.id) console.log(`${cell.id.slice(0, 5)} Successfully parsed`); + return cellData; +} + +export function parseOutput(raw: IOutput): vscode.NotebookCellOutput[] { + const outputs: vscode.NotebookCellOutput[] = []; + switch (raw.output_type) { + case 'stream': + outputs.push( + new vscode.NotebookCellOutput([ + new MimeTypeHandler(mimeTypes.TEXT).makeOutputItem(Array.isArray(raw.text) ? raw.text.join('') : raw.text), + ]) + ); + break; + + case 'error': + outputs.push( + new vscode.NotebookCellOutput([ + vscode.NotebookCellOutputItem.error({ + name: raw.ename ?? 'Error', + message: raw.evalue ?? '', + stack: Array.isArray(raw.traceback) ? raw.traceback.join('\n') : undefined, + }), + ]) + ); + break; + + case 'display_data': + case 'execute_result': + const bundle = raw.data ?? {}; + const items = MimeTypeHandler.itemsFromBundle(bundle); + if (items.length) { + outputs.push(new vscode.NotebookCellOutput(items, raw.metadata)); + } + break; + } + return outputs; +} + +export function serializeCell(cell: vscode.NotebookCellData): ICell { + const baseMeta = (cell.metadata as Record) || {}; + const id = baseMeta.id || randomUUID(); + + if (cell.kind === vscode.NotebookCellKind.Code) { + const exec = cell.executionSummary ?? {}; + const executionCount = exec.executionOrder ?? null; + const success = exec.success; + + const execSummary = new ExecutionSummary(executionCount, success); + const metadata = executionCount ? { ...baseMeta, executionSummary: execSummary.toJSON() } : {}; + + const outputs: IOutput[] = (cell.outputs || []).map((output): IOutput => { + const data: IMimeBundle = {}; + const outMetadata = output.metadata ?? {}; + + for (const item of output.items) { + if (item.mime === mimeTypes.TEXT) { + data[mimeTypes.TEXT] = Buffer.from(item.data).toString(); + } else { + data[item.mime] = uint8ArrayToBase64(item.data); + } + } + + const execOut: IExecuteResultOutput = { + output_type: 'execute_result', + data, + metadata: outMetadata, + execution_count: executionCount, + }; + return execOut; + }); + + const codeCell: ICodeCell = { + id, + cell_type: 'code', + source: cell.value, + metadata: { + language: cell.languageId, + ...metadata + }, + execution_count: executionCount, + outputs, + }; + if (codeCell.id) console.log(`${codeCell.id.slice(0, 5)} Successfully serialized code cell`); + return codeCell; + } + const mdCell: IMarkdownCell = { + id, + cell_type: 'markdown', + source: cell.value, + metadata: { + language: cell.languageId, + id, + ...cell.metadata + }, + }; + return mdCell; +} + +export function errorNotebook(title: string, message: string, consoleMessage: string = ''): vscode.NotebookData { + console.error(title, ': ', message, ': ', consoleMessage); + return new vscode.NotebookData([ + new vscode.NotebookCellData( + vscode.NotebookCellKind.Markup, + `# ${title}\n\n${message}`, + 'markdown' + ), + ]); +} diff --git a/vscode/src/test/unit/mocks/vscode/mockVscode.ts b/vscode/src/test/unit/mocks/vscode/mockVscode.ts index 0fe128a..2478173 100644 --- a/vscode/src/test/unit/mocks/vscode/mockVscode.ts +++ b/vscode/src/test/unit/mocks/vscode/mockVscode.ts @@ -19,6 +19,7 @@ import { URI } from './uri'; import { mockWindowNamespace } from './namespaces/window'; import { mockEnvNamespace } from './namespaces/env'; import { mockedEnums } from './vscodeHostedTypes'; +import { NotebookCellOutputItem } from './notebookCellOutputItem'; type VSCode = typeof vscode; const mockedVSCode: Partial = {}; @@ -26,6 +27,7 @@ const mockedVSCode: Partial = {}; const mockedVscodeClassesAndTypes = () => { mockedVSCode.Uri = URI as any; mockedVSCode.ViewColumn = mockedEnums.viewColumn; + mockedVSCode.NotebookCellOutputItem = NotebookCellOutputItem as any; } const mockNamespaces = () => { diff --git a/vscode/src/lsp/listeners/requests/notebooks.ts b/vscode/src/test/unit/mocks/vscode/notebookCellOutputItem.ts similarity index 51% rename from vscode/src/lsp/listeners/requests/notebooks.ts rename to vscode/src/test/unit/mocks/vscode/notebookCellOutputItem.ts index b5a0190..8cc5fc8 100644 --- a/vscode/src/lsp/listeners/requests/notebooks.ts +++ b/vscode/src/test/unit/mocks/vscode/notebookCellOutputItem.ts @@ -1,5 +1,5 @@ /* - Copyright (c) 2023-2025, Oracle and/or its affiliates. + Copyright (c) 2025, Oracle and/or its affiliates. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,14 +14,18 @@ limitations under the License. */ -import { NotebookCellContentRequest, NotebookCellContentRequestParams } from "../../protocol"; -import { notificationOrRequestListenerType } from "../../types"; - -const notebookCellContentRequestHandler = (params: NotebookCellContentRequestParams) => { - return ""; -} - -export const notebookListeners: notificationOrRequestListenerType[] = [{ - type: NotebookCellContentRequest.type, - handler: notebookCellContentRequestHandler -}]; \ No newline at end of file +export class NotebookCellOutputItem { + public data: Uint8Array; + public mime: string; + private constructor(data: Uint8Array, mime: string) { + this.data = data; + this.mime = mime; + } + public static create(data: Uint8Array, mime: string) { + return new NotebookCellOutputItem(data, mime); + } + public static text(text: string, mime: string) { + const enc = new TextEncoder().encode(text); + return new NotebookCellOutputItem(enc, mime); + } +} \ No newline at end of file diff --git a/vscode/src/test/unit/notebooks/executionSummary.unit.test.ts b/vscode/src/test/unit/notebooks/executionSummary.unit.test.ts new file mode 100644 index 0000000..d08836d --- /dev/null +++ b/vscode/src/test/unit/notebooks/executionSummary.unit.test.ts @@ -0,0 +1,108 @@ +/* + Copyright (c) 2025, Oracle and/or its affiliates. + + Licensed 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 + + https://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. +*/ + +import { expect } from 'chai'; +import { ExecutionSummary, ExecutionSummaryData } from '../../../notebooks/executionSummary'; +import { describe, it } from 'mocha'; + +describe('ExecutionSummary', () => { + describe('constructor defaults', () => { + it('defaults to null executionOrder and false success', () => { + const es = new ExecutionSummary(); + expect(es.executionOrder).to.be.null; + expect(es.success).to.equal(false); + }); + + it('accepts explicit values', () => { + const es = new ExecutionSummary(3, true); + expect(es.executionOrder).to.equal(3); + expect(es.success).to.equal(true); + + const es2 = new ExecutionSummary(0, false); + expect(es2.executionOrder).to.equal(0); + expect(es2.success).to.equal(false); + }); + }); + + describe('static fromMetadata()', () => { + it('returns defaults when meta and fallback are undefined', () => { + const es = ExecutionSummary.fromMetadata(); + expect(es.executionOrder).to.be.null; + expect(es.success).to.equal(false); + }); + + it('uses fallbackExecCount when meta.executionOrder is undefined', () => { + const es = ExecutionSummary.fromMetadata({}, 7); + expect(es.executionOrder).to.equal(7); + expect(es.success).to.equal(false); + }); + + it('uses fallbackExecCount when meta.executionOrder is null', () => { + const meta: ExecutionSummaryData = { executionOrder: null }; + const es = ExecutionSummary.fromMetadata(meta, 12); + expect(es.executionOrder).to.equal(12); + expect(es.success).to.equal(false); + }); + + it('ignores fallback when meta.executionOrder is provided', () => { + const meta: ExecutionSummaryData = { executionOrder: 5, success: true }; + const es = ExecutionSummary.fromMetadata(meta, 10); + expect(es.executionOrder).to.equal(5); + expect(es.success).to.equal(true); + }); + + it('defaults success to false if meta.success is undefined', () => { + const meta: ExecutionSummaryData = { executionOrder: 2 }; + const es = ExecutionSummary.fromMetadata(meta, null); + expect(es.executionOrder).to.equal(2); + expect(es.success).to.equal(false); + }); + + it('respects meta.success when false explicitly', () => { + const meta: ExecutionSummaryData = { executionOrder: 8, success: false }; + const es = ExecutionSummary.fromMetadata(meta, 1); + expect(es.executionOrder).to.equal(8); + expect(es.success).to.equal(false); + }); + }); + + describe('toJSON()', () => { + it('serializes current state to ExecutionSummaryData', () => { + const es = new ExecutionSummary(9, true); + const json = es.toJSON(); + expect(json).to.deep.equal({ executionOrder: 9, success: true }); + }); + + it('handles null executionOrder serialization', () => { + const es = new ExecutionSummary(null, false); + const json = es.toJSON(); + expect(json).to.deep.equal({ executionOrder: null, success: false }); + }); + + it('round-trips fromMetadata → toJSON', () => { + const meta: ExecutionSummaryData = { executionOrder: 15, success: true }; + const es = ExecutionSummary.fromMetadata(meta, 20); + const json = es.toJSON(); + expect(json).to.deep.equal(meta); + }); + + it('round-trips fallbackExecCount when meta missing', () => { + const es = ExecutionSummary.fromMetadata(undefined, 33); + const json = es.toJSON(); + expect(json).to.deep.equal({ executionOrder: 33, success: false }); + }); + }); +}); diff --git a/vscode/src/test/unit/notebooks/mimeTypeHandler.unit.test.ts b/vscode/src/test/unit/notebooks/mimeTypeHandler.unit.test.ts new file mode 100644 index 0000000..e4aea7f --- /dev/null +++ b/vscode/src/test/unit/notebooks/mimeTypeHandler.unit.test.ts @@ -0,0 +1,186 @@ +/* + Copyright (c) 2025, Oracle and/or its affiliates. + + Licensed 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 + + https://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. +*/ + +import { expect } from 'chai'; +import { Buffer } from 'buffer'; +import { NotebookCellOutputItem } from 'vscode'; +import { MimeTypeHandler } from '../../../notebooks/mimeTypeHandler'; +import { mimeTypes } from '../../../notebooks/constants'; +import { IMimeBundle } from '../../../notebooks/types'; +import { describe, it } from 'mocha'; + +describe('MimeTypeHandler', () => { + + function decodeData(item: NotebookCellOutputItem): string { + const bytes: Uint8Array = (item as any).data; + return new TextDecoder().decode(bytes); + } + + describe('getters isText / isImage', () => { + it('isText ⇢ true only for mimeTypes.TEXT', () => { + expect(new MimeTypeHandler(mimeTypes.TEXT).isText).to.be.true; + expect(new MimeTypeHandler('image/jpeg').isText).to.be.false; + }); + + it('isImage ⇢ true only for image/*', () => { + expect(new MimeTypeHandler('image/png').isImage).to.be.true; + expect(new MimeTypeHandler(mimeTypes.TEXT).isImage).to.be.false; + }); + + }); + + describe('static toBytes()', () => { + it('decodes a base64 string into the original Uint8Array', () => { + const text = 'Hello, 世界!'; + const b64 = Buffer.from(text).toString('base64'); + const out = MimeTypeHandler.toBytes(b64); + expect(out).to.be.instanceOf(Uint8Array); + expect(Buffer.from(out).toString()).to.equal(text); + }); + + it('returns the same Uint8Array when passed through', () => { + const arr = new Uint8Array([1, 2, 3]); + expect(MimeTypeHandler.toBytes(arr)).to.equal(arr); + }); + + it('decodes empty string to empty Uint8Array', () => { + const out = MimeTypeHandler.toBytes(''); + expect(out).to.be.instanceOf(Uint8Array); + expect(out.length).to.equal(0); + }); + }); + + describe('static toString()', () => { + it('returns the same string if input is already string', () => { + const s = 'just a test'; + expect(MimeTypeHandler.toString(s)).to.equal(s); + }); + + it('decodes a Uint8Array into a string via TextDecoder', () => { + const text = '¡Hola!'; + const encoder = new TextEncoder(); + const arr = encoder.encode(text); + expect(MimeTypeHandler.toString(arr)).to.equal(text); + }); + + it('decodes empty Uint8Array to empty string', () => { + const arr = new Uint8Array([]); + expect(MimeTypeHandler.toString(arr)).to.equal(''); + }); + }); + + describe('makeOutputItem()', () => { + it('for TEXT builds a NotebookCellOutputItem containing the UTF-8 bytes of the string', () => { + const handler = new MimeTypeHandler(mimeTypes.TEXT); + const item = handler.makeOutputItem('plain text'); + expect(item).to.be.instanceOf(NotebookCellOutputItem); + expect((item as any).mime).to.equal(mimeTypes.TEXT); + expect(decodeData(item)).to.equal('plain text'); + }); + + it('handles empty text payload correctly', () => { + const handler = new MimeTypeHandler(mimeTypes.TEXT); + const item = handler.makeOutputItem(''); + expect(decodeData(item)).to.equal(''); + expect((item as any).data.length).to.equal(0); + }); + + it('for image/* with a base64 string decodes back to the original bytes', () => { + const raw = new Uint8Array([10, 20, 30]); + const b64 = Buffer.from(raw).toString('base64'); + const handler = new MimeTypeHandler('image/png'); + const item = handler.makeOutputItem(b64); + expect((item as any).mime).to.equal('image/png'); + const got = (item as any).data as Uint8Array; + expect(Array.from(got)).to.deep.equal(Array.from(raw)); + }); + + it('for image/* with a Uint8Array leaves the bytes untouched', () => { + const raw = new Uint8Array([5, 6, 7]); + const handler = new MimeTypeHandler('image/gif'); + const item = handler.makeOutputItem(raw); + expect((item as any).mime).to.equal('image/gif'); + expect((item as any).data).to.equal(raw); + }); + + it('unknown mime routes through text branch', () => { + const handler = new MimeTypeHandler('application/xml'); + const payload = ''; + const item = handler.makeOutputItem(payload); + expect((item as any).mime).to.equal('application/xml'); + expect(decodeData(item)).to.equal(payload); + }); + }); + + describe('static itemsFromBundle()', () => { + it('filters to only text & image entries and decodes each correctly', () => { + const rawImg = new Uint8Array([1, 2, 3]); + const b64img = Buffer.from(rawImg).toString('base64'); + const bundle: IMimeBundle = { + [mimeTypes.TEXT]: 'foo', + 'image/png': b64img, + 'application/json': '{"x":1}', + }; + + const items = MimeTypeHandler.itemsFromBundle(bundle); + expect(items).to.have.length(2); + + const textItem = items.find(i => (i as any).mime === mimeTypes.TEXT)!; + expect(decodeData(textItem)).to.equal('foo'); + + const imgItem = items.find(i => (i as any).mime === 'image/png')!; + const gotBytes = (imgItem as any).data as Uint8Array; + expect(Array.from(gotBytes)).to.deep.equal(Array.from(rawImg)); + }); + + it('joins string-array values before creating the output item', () => { + const bundle: IMimeBundle = { + [mimeTypes.TEXT]: ['a', 'b', 'c'], + }; + const items = MimeTypeHandler.itemsFromBundle(bundle); + expect(items).to.have.length(1); + const only = items[0]; + expect(decodeData(only)).to.equal('abc'); + expect((only as any).mime).to.equal(mimeTypes.TEXT); + }); + + it('joins base64-string-array for image payload', () => { + // "SGVs" + "bG8=" => "SGVsbG8=" which decodes to "Hello" + const segs = ['SGVs', 'bG8=']; + const bundle: IMimeBundle = { + 'image/png': segs, + }; + const items = MimeTypeHandler.itemsFromBundle(bundle); + expect(items).to.have.length(1); + const only = items[0]; + expect((only as any).mime).to.equal('image/png'); + expect(decodeData(only)).to.equal('Hello'); + }); + + it('empty bundle yields no items', () => { + const items = MimeTypeHandler.itemsFromBundle({}); + expect(items).to.be.empty; + }); + + it('unsupported mime types are dropped', () => { + const bundle: IMimeBundle = { + 'application/json': '[1,2,3]', + 'video/mp4': 'abcd', + }; + expect(MimeTypeHandler.itemsFromBundle(bundle)).to.be.empty; + }); + }); +}); diff --git a/vscode/tsconfig.json b/vscode/tsconfig.json index 64351bb..0da96ea 100644 --- a/vscode/tsconfig.json +++ b/vscode/tsconfig.json @@ -9,7 +9,8 @@ ], "sourceMap": true, "rootDir": "src", - "strict": true /* enable all strict type-checking options */ + "strict": true, /* enable all strict type-checking options */ + "resolveJsonModule": true /* Additional Checks */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ From 4acbfcac7d90090e2e503b0feac18e64143e6099 Mon Sep 17 00:00:00 2001 From: Shivam Madan Date: Thu, 21 Aug 2025 17:33:44 +0530 Subject: [PATCH 6/9] Unit tests for notebook module 1. Added mock lsp client helper class 2. Added test for NotebookConfigs class 3. Updated test dependency in project.xml --- nbcode/notebooks/nbproject/project.xml | 4 + .../nbcode/java/notebook/MockNbClient.java | 186 ++++++++++++++++ .../java/notebook/NotebookConfigsTest.java | 199 ++++++++++++++++++ 3 files changed, 389 insertions(+) create mode 100644 nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/MockNbClient.java create mode 100644 nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigsTest.java diff --git a/nbcode/notebooks/nbproject/project.xml b/nbcode/notebooks/nbproject/project.xml index c3bdeaa..2082093 100644 --- a/nbcode/notebooks/nbproject/project.xml +++ b/nbcode/notebooks/nbproject/project.xml @@ -153,6 +153,10 @@ + + org.netbeans.modules.java.lsp.server + + diff --git a/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/MockNbClient.java b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/MockNbClient.java new file mode 100644 index 0000000..004e5de --- /dev/null +++ b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/MockNbClient.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may 'ou may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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.netbeans.modules.nbcode.java.notebook; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import org.eclipse.lsp4j.ConfigurationParams; +import org.eclipse.lsp4j.MessageActionItem; +import org.eclipse.lsp4j.MessageParams; +import org.eclipse.lsp4j.PublishDiagnosticsParams; +import org.eclipse.lsp4j.ShowMessageRequestParams; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.netbeans.modules.java.lsp.server.explorer.api.NodeChangedParams; +import org.netbeans.modules.java.lsp.server.input.QuickPickItem; +import org.netbeans.modules.java.lsp.server.input.ShowInputBoxParams; +import org.netbeans.modules.java.lsp.server.input.ShowMutliStepInputParams; +import org.netbeans.modules.java.lsp.server.input.ShowQuickPickParams; +import org.netbeans.modules.java.lsp.server.notebook.CellStateResponse; +import org.netbeans.modules.java.lsp.server.notebook.NotebookCellExecutionProgressResultParams; +import org.netbeans.modules.java.lsp.server.notebook.NotebookCellStateParams; +import org.netbeans.modules.java.lsp.server.protocol.DecorationRenderOptions; +import org.netbeans.modules.java.lsp.server.protocol.HtmlPageParams; +import org.netbeans.modules.java.lsp.server.protocol.NbCodeClientCapabilities; +import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient; +import org.netbeans.modules.java.lsp.server.protocol.OutputMessage; +import org.netbeans.modules.java.lsp.server.protocol.SaveDocumentRequestParams; +import org.netbeans.modules.java.lsp.server.protocol.SetTextEditorDecorationParams; +import org.netbeans.modules.java.lsp.server.protocol.ShowStatusMessageParams; +import org.netbeans.modules.java.lsp.server.protocol.TestProgressParams; +import org.netbeans.modules.java.lsp.server.protocol.UpdateConfigParams; + +/** + * Overrides all the methods with UnsupportedOperation extend this class and + * override the method you want to mock + * + * @author shimadan + */ +public class MockNbClient implements NbCodeLanguageClient { + + @Override + public NbCodeClientCapabilities getNbCodeCapabilities() { + NbCodeClientCapabilities caps = new NbCodeClientCapabilities(); + caps.setConfigurationPrefix("jdk."); + return caps; + } + + @Override + public CompletableFuture configurationUpdate(UpdateConfigParams ucp) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public CompletableFuture> configuration(ConfigurationParams configurationParams) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public void showStatusBarMessage(ShowStatusMessageParams ssmp) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public CompletableFuture showHtmlPage(HtmlPageParams hpp) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public CompletableFuture execInHtmlPage(HtmlPageParams hpp) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public CompletableFuture> showQuickPick(ShowQuickPickParams sqpp) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public CompletableFuture showInputBox(ShowInputBoxParams sibp) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public CompletableFuture, String>>> showMultiStepInput(ShowMutliStepInputParams smsip) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public void notifyTestProgress(TestProgressParams tpp) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public CompletableFuture createTextEditorDecoration(DecorationRenderOptions dro) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public void setTextEditorDecoration(SetTextEditorDecorationParams stedp) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public void disposeTextEditorDecoration(String params) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public void notifyNodeChange(NodeChangedParams ncp) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public CompletableFuture requestDocumentSave(SaveDocumentRequestParams sdrp) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public CompletableFuture writeOutput(OutputMessage om) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public CompletableFuture showOutput(String outputName) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public CompletableFuture closeOutput(String outputName) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public CompletableFuture resetOutput(String outputName) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public void telemetryEvent(Object object) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public void publishDiagnostics(PublishDiagnosticsParams diagnostics) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public void showMessage(MessageParams messageParams) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public CompletableFuture showMessageRequest(ShowMessageRequestParams requestParams) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public void logMessage(MessageParams message) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public void notifyNotebookCellExecutionProgress(NotebookCellExecutionProgressResultParams params) { + throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + } + + @Override + public CompletableFuture getNotebookCellState(NotebookCellStateParams params) { + throw new UnsupportedOperationException("Not supported yet."); + } +} diff --git a/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigsTest.java b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigsTest.java new file mode 100644 index 0000000..5350c74 --- /dev/null +++ b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigsTest.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may 'ou may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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.netbeans.modules.nbcode.java.notebook; + +import com.google.gson.JsonObject; +import com.google.gson.JsonArray; +import com.google.gson.JsonPrimitive; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import org.eclipse.lsp4j.ConfigurationItem; +import org.eclipse.lsp4j.ConfigurationParams; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import static org.junit.Assert.*; + +/* TODO + test multiple configuration scenarios + 1.Client sends nulls/empty + 2.Missing keys +*/ + +/* +Version 1 21/08/25 +*/ + +/** + * Mock LSP Client sending sample configurations + * Verifies that the NotebookConfigs class + * parses and handles configurations appropriately + * + * @author shimadan + */ +public class NotebookConfigsTest { + + private NotebookConfigs instance; + private CompletableFuture initialized; + private JsonObject configsObj = new JsonObject(); + private static final String CLASSPATH_KEY = "jdk.notebook.classpath"; + private static final String IMPLICIT_IMPORTS_KEY = "jdk.notebook.implicitImports"; + private static final String ADD_MODULES_KEY = "jdk.notebook.addmodules"; + private static final String ENABLE_PREVIEW_KEY = "jdk.notebook.enablePreview"; + private static final String MODULEPATH_KEY = "jdk.notebook.modulepath"; + + public NotebookConfigsTest() { + } + + @Before + public void setUp() { + setConfigObject(); + LanguageClientInstance.getInstance(). + setClient(new MockNbClientConfigs()); + instance = NotebookConfigs.getInstance(); + instance.initConfigs(); + initialized = instance.getInitialized(); + } + + @After + public void tearDown() { + } + + /** + * Test of getInitialized method, of class NotebookConfigs. + */ + @Test + public void testGetInitialized() { + System.out.println("getInitialized"); + CompletableFuture result = instance.getInitialized(); + try { + result.get(5, TimeUnit.SECONDS); + } catch (Exception ex) { + fail("Configuration initialization failed"); + } + } + + /** + * Test of getClassPath method, of class NotebookConfigs. + */ + @Test + public void testGetClassPath() { + System.out.println("getClassPath"); + try { + initialized.get(5, TimeUnit.SECONDS); + String expResult = configsObj.get(CLASSPATH_KEY).getAsString(); + String result = instance.getClassPath(); + assertEquals(expResult, result); + } catch (Exception ex) { + fail("Configuration initialization failed"); + } + } + + /** + * Test of getModulePath method, of class NotebookConfigs. + */ + @Test + public void testGetModulePath() { + System.out.println("getModulePath"); + + try { + initialized.get(5, TimeUnit.SECONDS); + String expResult = configsObj.get(MODULEPATH_KEY).getAsString(); + String result = instance.getModulePath(); + assertEquals(expResult, result); + } catch (Exception ex) { + fail("Configuration initialization failed"); + } + } + + /** + * Test of getAddModules method, of class NotebookConfigs. + */ + @Test + public void testGetAddModules() { + System.out.println("getAddModules"); + try { + initialized.get(5, TimeUnit.SECONDS); + String expResult = configsObj.get(ADD_MODULES_KEY).getAsString(); + String result = instance.getAddModules(); + assertEquals(expResult, result); + } catch (Exception ex) { + fail("Configuration initialization failed"); + } + } + + /** + * Test of isEnablePreview method, of class NotebookConfigs. + */ + @Test + public void testIsEnablePreview() { + System.out.println("getIsEnablePreview"); + try { + initialized.get(5, TimeUnit.SECONDS); + boolean expResult = configsObj.get(ENABLE_PREVIEW_KEY).getAsBoolean(); + boolean result = instance.isEnablePreview(); + assertEquals(expResult, result); + } catch (Exception ex) { + fail("Configuration initialization failed"); + } + } + + /** + * Test of getImplicitImports method, of class NotebookConfigs. + */ + @Test + public void testGetImplicitImports() { + System.out.println("getImplicitImports"); + try { + initialized.get(5, TimeUnit.SECONDS); + List expResult = configsObj.get(IMPLICIT_IMPORTS_KEY). + getAsJsonArray().asList().stream(). + map((elem) -> elem.getAsString()).toList(); + List result = instance.getImplicitImports(); + assertEquals(expResult, result); + } catch (Exception ex) { + fail("Configuration initialization failed"); + } + } + + private void setConfigObject() { + JsonArray imports = new JsonArray(); + imports.add(new JsonPrimitive("java.math.*")); + imports.add(new JsonPrimitive("javafx.scene.control.*")); + configsObj.add(IMPLICIT_IMPORTS_KEY, imports); + configsObj.add(CLASSPATH_KEY, new JsonPrimitive("path/to/javafx-sdk-24.0.1/lib/javafx.base.jar")); + configsObj.add(MODULEPATH_KEY, new JsonPrimitive("/path/to/javafx-sdk/lib")); + configsObj.add(ENABLE_PREVIEW_KEY, new JsonPrimitive(false)); + configsObj.add(ADD_MODULES_KEY, new JsonPrimitive("javafx.controls,javafx.graphics")); + + } + + private class MockNbClientConfigs extends MockNbClient { + + @Override + public CompletableFuture> configuration(ConfigurationParams configurationParams) { + List items = configurationParams.getItems(); + List configs = new ArrayList<>(); + for (ConfigurationItem item : items) { + configs.add(configsObj.get(item.getSection())); + } + return CompletableFuture.completedFuture(configs); + } + } +} From faacdef18915324866a5b897f9adb04f26c49f86 Mon Sep 17 00:00:00 2001 From: Achal Talati Date: Fri, 15 Aug 2025 15:48:23 +0530 Subject: [PATCH 7/9] Improvements in notebook usability - Added opening notebooks in context of a project - Also added change project context button in the notebook toolbar - Added few more tests - Added localization in notebooks - Fixed jshell flow and updated open jshell label - Other cleanup and fixes - updated netbeans patch - cleanup of some unused code and added license header to missing files - formatted files and removed unused imports - updated ajv to latest version: 8.17.1 - updated artifactory urls - fixed the execution status issue - removed console.log and replaced with standard extension logger --- build.xml | 1 - .../nbcode/java/notebook/CellState.java | 4 +- .../java/notebook/JshellStreamsHandler.java | 36 ++-- .../java/notebook/LanguageClientInstance.java | 13 +- .../notebook/NotebookCommandsHandler.java | 7 +- .../nbcode/java/notebook/NotebookConfigs.java | 16 +- .../NotebookDocumentServiceHandlerImpl.java | 8 +- .../NotebookDocumentStateManager.java | 7 +- .../java/notebook/NotebookSessionManager.java | 202 ++++++++++++------ .../nbcode/java/project/CommandHandler.java | 80 +++---- .../project/ProjectConfigurationUtils.java | 2 +- .../nbcode/java/project/ProjectContext.java | 116 ++++++++++ .../java/project/ProjectContextInfo.java | 42 ++++ .../ProjectModulePathConfigurationUtils.java | 2 - .../java/notebook/CustomInputStreamTest.java | 159 ++++++++++++++ patches/java-notebooks.diff | 109 ++++++---- vscode/l10n/bundle.l10n.en.json | 12 +- vscode/l10n/bundle.l10n.ja.json | 12 +- vscode/l10n/bundle.l10n.zh-cn.json | 12 +- vscode/package-lock.json | 68 +++--- vscode/package.json | 37 +++- vscode/package.nls.ja.json | 9 + vscode/package.nls.json | 9 +- vscode/package.nls.zh-cn.json | 9 + vscode/src/commands/commands.ts | 4 +- vscode/src/commands/create.ts | 1 + vscode/src/commands/notebook.ts | 57 +++-- vscode/src/configurations/configuration.ts | 3 +- vscode/src/extension.ts | 2 + vscode/src/lsp/listeners/requests/handlers.ts | 4 +- vscode/src/notebooks/codeCellExecution.ts | 33 ++- vscode/src/notebooks/constants.ts | 20 ++ vscode/src/notebooks/notebook.ts | 12 +- vscode/src/notebooks/types.ts | 10 + vscode/src/notebooks/utils.ts | 5 +- 35 files changed, 849 insertions(+), 274 deletions(-) create mode 100644 nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectContext.java create mode 100644 nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectContextInfo.java create mode 100644 nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/CustomInputStreamTest.java diff --git a/build.xml b/build.xml index 791c91a..4c5bd3e 100644 --- a/build.xml +++ b/build.xml @@ -79,7 +79,6 @@ patches/dev-dependency-licenses.diff patches/nb-telemetry.diff patches/change-method-parameters-refactoring-qualified-names.diff - patches/javavscode-375.diff patches/upgrade-lsp4j.diff patches/java-notebooks.diff diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CellState.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CellState.java index 3e57226..4c04975 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CellState.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CellState.java @@ -144,8 +144,8 @@ protected CompletableFuture requestLatestCellState() { } return client.getNotebookCellState(new NotebookCellStateParams(notebookUri, cellUri)); } - - protected VersionAwareContent getVersionAwareContent(){ + + protected VersionAwareContent getVersionAwareContent() { return this.content.get(); } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/JshellStreamsHandler.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/JshellStreamsHandler.java index 7529d55..cc8d1c1 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/JshellStreamsHandler.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/JshellStreamsHandler.java @@ -15,7 +15,6 @@ */ package org.netbeans.modules.nbcode.java.notebook; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; @@ -26,10 +25,11 @@ /** * Handles JShell output and error streams for notebook execution. - * + * * @author atalati */ public class JshellStreamsHandler implements AutoCloseable { + private static final Logger LOG = Logger.getLogger(JshellStreamsHandler.class.getName()); private final String notebookId; private final StreamingOutputStream outStream; @@ -37,18 +37,18 @@ public class JshellStreamsHandler implements AutoCloseable { private final PrintStream printOutStream; private final PrintStream printErrStream; private final InputStream inputStream; - + public JshellStreamsHandler(String notebookId, BiConsumer streamCallback) { this(notebookId, streamCallback, streamCallback); } - - public JshellStreamsHandler(String notebookId, - BiConsumer outStreamCallback, - BiConsumer errStreamCallback) { + + public JshellStreamsHandler(String notebookId, + BiConsumer outStreamCallback, + BiConsumer errStreamCallback) { if (notebookId == null || notebookId.trim().isEmpty()) { throw new IllegalArgumentException("Notebook Id cannot be null or empty"); } - + this.notebookId = notebookId; this.outStream = new StreamingOutputStream(createCallback(outStreamCallback)); this.errStream = new StreamingOutputStream(createCallback(errStreamCallback)); @@ -56,35 +56,35 @@ public JshellStreamsHandler(String notebookId, this.printErrStream = new PrintStream(errStream); this.inputStream = new CustomInputStream(LanguageClientInstance.getInstance().getClient()); } - + private Consumer createCallback(BiConsumer callback) { return callback != null ? output -> callback.accept(notebookId, output) : null; } - + public void setOutStreamCallback(BiConsumer callback) { outStream.setCallback(createCallback(callback)); } - + public void setErrStreamCallback(BiConsumer callback) { errStream.setCallback(createCallback(callback)); } - + public PrintStream getPrintOutStream() { return printOutStream; } - + public PrintStream getPrintErrStream() { return printErrStream; } - + public InputStream getInputStream() { return inputStream; } - + public String getNotebookId() { return notebookId; } - + public void flushOutputStreams() { try { outStream.flush(); @@ -93,7 +93,7 @@ public void flushOutputStreams() { // nothing can be done } } - + @Override public void close() { try { @@ -106,4 +106,4 @@ public void close() { LOG.log(Level.WARNING, "IOException occurred while closing the streams {0}", ex.getMessage()); } } -} \ No newline at end of file +} diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/LanguageClientInstance.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/LanguageClientInstance.java index e60945f..e42a296 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/LanguageClientInstance.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/LanguageClientInstance.java @@ -23,6 +23,7 @@ * @author atalati */ public class LanguageClientInstance { + private WeakReference client = null; private LanguageClientInstance() { @@ -31,17 +32,17 @@ private LanguageClientInstance() { public static LanguageClientInstance getInstance() { return LanguageClientInstance.Singleton.instance; } - + private static class Singleton { private static final LanguageClientInstance instance = new LanguageClientInstance(); } - - public NbCodeLanguageClient getClient(){ - return this.client == null ? null: this.client.get(); + + public NbCodeLanguageClient getClient() { + return this.client == null ? null : this.client.get(); } - - public void setClient(NbCodeLanguageClient client){ + + public void setClient(NbCodeLanguageClient client) { this.client = new WeakReference<>(client); } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookCommandsHandler.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookCommandsHandler.java index 757bec8..6d85c40 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookCommandsHandler.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookCommandsHandler.java @@ -34,7 +34,8 @@ public class NotebookCommandsHandler implements CommandProvider { private static final String NBLS_JSHELL_EXEC = "nbls.jshell.execute.cell"; private static final String NBLS_JSHELL_INTERRUPT = "nbls.jshell.interrupt.cell"; private static final String NBLS_OPEN_PROJECT_JSHELL = "nbls.jshell.project.open"; - private static final Set COMMANDS = new HashSet<>(Arrays.asList(NBLS_JSHELL_EXEC, NBLS_OPEN_PROJECT_JSHELL, NBLS_JSHELL_INTERRUPT)); + private static final String NBLS_NOTEBOOK_PROJECT_MAPPING = "nbls.notebook.project.context"; + private static final Set COMMANDS = new HashSet<>(Arrays.asList(NBLS_JSHELL_EXEC, NBLS_OPEN_PROJECT_JSHELL, NBLS_JSHELL_INTERRUPT, NBLS_NOTEBOOK_PROJECT_MAPPING)); @Override public Set getCommands() { @@ -47,11 +48,13 @@ public CompletableFuture runCommand(String command, List argumen switch (command) { case NBLS_JSHELL_EXEC: - return CodeEval.getInstance().evaluate(arguments).thenApply(list -> (Object)list); + return CodeEval.getInstance().evaluate(arguments).thenApply(list -> (Object) list); case NBLS_JSHELL_INTERRUPT: return CompletableFuture.completedFuture(CodeEval.getInstance().interrupt(arguments)); case NBLS_OPEN_PROJECT_JSHELL: return CommandHandler.openJshellInProjectContext(arguments).thenApply(list -> (Object) list); + case NBLS_NOTEBOOK_PROJECT_MAPPING: + return CommandHandler.getNotebookProjectMappingPath(arguments).thenApply(prj -> (Object) prj); default: return CompletableFuture.failedFuture(new UnsupportedOperationException("Command not supported: " + command)); } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java index 61a0327..0b027c5 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java @@ -33,11 +33,17 @@ */ public class NotebookConfigs { - private static final String[] NOTEBOOK_CONFIG_LABELS = {"notebook.classpath", "notebook.modulepath", "notebook.addmodules", "notebook.enablePreview", "notebook.implicitImports"}; + private static final String[] NOTEBOOK_CONFIG_LABELS = {"notebook.classpath", + "notebook.modulepath", + "notebook.addmodules", + "notebook.enablePreview", + "notebook.implicitImports", + "notebook.projects.mapping"}; private String classPath = null; private String modulePath = null; private String addModules = null; private boolean enablePreview = false; + private JsonObject notebookProjectMapping = new JsonObject(); private List implicitImports = null; private CompletableFuture initialized; @@ -65,6 +71,10 @@ public List getImplicitImports() { return implicitImports; } + public JsonObject getNotebookProjectMapping() { + return notebookProjectMapping; + } + private NotebookConfigs() { } @@ -121,6 +131,10 @@ private CompletableFuture initializeConfigs() throws InterruptedException, implicitImports = ((JsonArray) c.get(4)).asList().stream().map((elem) -> elem.getAsString()).toList(); } + if (c.get(5) != null) { + notebookProjectMapping = (JsonObject) c.get(5); + + } } }); diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java index 79062a1..5d2476d 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java @@ -85,16 +85,16 @@ public class NotebookDocumentServiceHandlerImpl implements NotebookDocumentServi public void didOpen(DidOpenNotebookDocumentParams params) { try { NbCodeLanguageClient client = LanguageClientInstance.getInstance().getClient(); - if(client == null){ + if (client == null) { return; } - client.showStatusBarMessage(new ShowStatusMessageParams(MessageType.Info,"Intializing Java kernel for notebook.")); - NotebookSessionManager.getInstance().createSession(params.getNotebookDocument()).whenComplete((JShell jshell,Throwable t) -> { + client.showStatusBarMessage(new ShowStatusMessageParams(MessageType.Info, "Intializing Java kernel for notebook.")); + NotebookSessionManager.getInstance().createSession(params.getNotebookDocument()).whenComplete((JShell jshell, Throwable t) -> { if (t == null) { client.showStatusBarMessage(new ShowStatusMessageParams(MessageType.Info, "Java kernel initialized successfully")); } else { // if package import fails user is not informed ? - client.showMessage(new MessageParams(MessageType.Error,"Error could not initialize Java kernel for the notebook.")); + client.showMessage(new MessageParams(MessageType.Error, "Error could not initialize Java kernel for the notebook.")); LOG.log(Level.SEVERE, "Error could not initialize Java kernel for the notebook. : {0}", t.getMessage()); } }); diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java index a492915..af42261 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java @@ -150,7 +150,7 @@ private void updateNotebookCellContent(NotebookDocumentChangeEventCellTextConten } int newVersion = contentChange.getDocument().getVersion(); String currentContent = cellState.getContent(); - + try { String updatedContent = applyContentChanges(currentContent, contentChange.getChanges()); cellState.setContent(updatedContent, newVersion); @@ -220,6 +220,7 @@ private String applyRangeChange(String content, TextDocumentContentChangeEvent c return result.toString(); } + // protected methods for ease of unit testing protected void addNewCellState(NotebookCell cell, TextDocumentItem item) { if (cell == null || item == null) { @@ -236,8 +237,8 @@ protected void addNewCellState(NotebookCell cell, TextDocumentItem item) { throw new RuntimeException("Failed to create cell state", e); } } - - protected Map getCellsMap(){ + + protected Map getCellsMap() { return cellsMap; } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java index 15a47ed..d1b423c 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java @@ -15,18 +15,27 @@ */ package org.netbeans.modules.nbcode.java.notebook; +import com.google.gson.JsonObject; +import java.net.URI; +import java.nio.file.Paths; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; +import java.util.function.BiConsumer; import java.util.logging.Level; import java.util.logging.Logger; import jdk.jshell.JShell; import org.eclipse.lsp4j.NotebookDocument; +import org.netbeans.api.project.Project; import static org.netbeans.modules.nbcode.java.notebook.NotebookUtils.checkEmptyString; +import org.netbeans.modules.nbcode.java.project.ProjectConfigurationUtils; +import org.netbeans.modules.nbcode.java.project.ProjectContext; +import org.netbeans.modules.nbcode.java.project.ProjectContextInfo; /** * @@ -43,6 +52,7 @@ public class NotebookSessionManager { private final Map> sessions = new ConcurrentHashMap<>(); private final Map jshellStreamsMap = new ConcurrentHashMap<>(); + private final Map notebookPrjMap = new ConcurrentHashMap<>(); private NotebookSessionManager() { } @@ -56,37 +66,37 @@ private static class Singleton { private static final NotebookSessionManager instance = new NotebookSessionManager(); } - private CompletableFuture jshellBuilder(JshellStreamsHandler streamsHandler) { - return CompletableFuture.supplyAsync(() -> { - try { - NotebookConfigs.getInstance().getInitialized().get(); - } catch (InterruptedException ex) { - LOG.log(Level.WARNING, "InterruptedException occurred while getting notebook configs: {0}", ex.getMessage()); - } catch (ExecutionException ex) { - LOG.log(Level.WARNING, "ExecutionException occurred while getting notebook configs: {0}", ex.getMessage()); - } - List compilerOptions = getCompilerOptions(); - List remoteOptions = getRemoteVmOptions(); - if (compilerOptions.isEmpty()) { - return JShell.builder() - .out(streamsHandler.getPrintOutStream()) - .err(streamsHandler.getPrintErrStream()) - .in(streamsHandler.getInputStream()) - .compilerOptions() - .remoteVMOptions() - .build(); - } else { - return JShell.builder() - .out(streamsHandler.getPrintOutStream()) - .err(streamsHandler.getPrintErrStream()) - .in(streamsHandler.getInputStream()) - .compilerOptions(compilerOptions.toArray(new String[0])) - .remoteVMOptions(remoteOptions.toArray(new String[0])) - .build(); - } + private CompletableFuture jshellBuilder(String notebookUri, JshellStreamsHandler streamsHandler) { + return NotebookConfigs.getInstance().getInitialized() + .thenCompose(v -> getProjectContextForNotebook(notebookUri) + .thenApply(prj -> { + if (prj != null) { + notebookPrjMap.put(notebookUri, new ProjectContextInfo(prj)); + } + return jshellBuildWithProject(prj, streamsHandler); + })).exceptionally(throwable -> { + LOG.log(Level.WARNING, "Failed to get project context, using default JShell configuration", throwable); + return jshellBuildWithProject(null, streamsHandler); }); } + private JShell jshellBuildWithProject(Project prj, JshellStreamsHandler streamsHandler) { + List compilerOptions = getCompilerOptions(prj); + List remoteOptions = getRemoteVmOptions(prj); + + JShell.Builder builder = JShell.builder() + .out(streamsHandler.getPrintOutStream()) + .err(streamsHandler.getPrintErrStream()) + .in(streamsHandler.getInputStream()); + + if (!compilerOptions.isEmpty()) { + builder.compilerOptions(compilerOptions.toArray(new String[0])) + .remoteVMOptions(remoteOptions.toArray(new String[0])); + } + + return builder.build(); + } + public CompletableFuture createSession(NotebookDocument notebookDoc) { String notebookId = notebookDoc.getUri(); @@ -94,7 +104,7 @@ public CompletableFuture createSession(NotebookDocument notebookDoc) { JshellStreamsHandler handler = new JshellStreamsHandler(id, CodeEval.getInstance().outStreamFlushCb, CodeEval.getInstance().errStreamFlushCb); jshellStreamsMap.put(id, handler); - CompletableFuture future = jshellBuilder(handler); + CompletableFuture future = jshellBuilder(notebookDoc.getUri(), handler); future.thenAccept(jshell -> onJshellInit(notebookId, jshell)) .exceptionally(ex -> { @@ -106,58 +116,85 @@ public CompletableFuture createSession(NotebookDocument notebookDoc) { }); } - private List getCompilerOptions() { + private List getCompilerOptions(Project prj) { List compilerOptions = new ArrayList<>(); - String classpath = NotebookConfigs.getInstance().getClassPath(); - String modulePath = NotebookConfigs.getInstance().getModulePath(); - String addModules = NotebookConfigs.getInstance().getAddModules(); - boolean isEnablePreview = NotebookConfigs.getInstance().isEnablePreview(); - String notebookJdkVersion = NotebookConfigs.getInstance().getJdkVersion(); - - if (!checkEmptyString(classpath)) { - compilerOptions.add(CLASS_PATH); - compilerOptions.add(classpath); - } - if (!checkEmptyString(modulePath)) { - compilerOptions.add(MODULE_PATH); - compilerOptions.add(modulePath); - } - if (!checkEmptyString(addModules)) { - compilerOptions.add(ADD_MODULES); - compilerOptions.add(addModules); - } - if (isEnablePreview) { + NotebookConfigs configs = NotebookConfigs.getInstance(); + + BiConsumer addOption = (flag, value) -> { + if (!checkEmptyString(value)) { + compilerOptions.add(flag); + compilerOptions.add(value); + } + }; + + addOption.accept(CLASS_PATH, configs.getClassPath()); + addOption.accept(MODULE_PATH, configs.getModulePath()); + addOption.accept(ADD_MODULES, configs.getAddModules()); + + if (configs.isEnablePreview()) { compilerOptions.add(ENABLE_PREVIEW); compilerOptions.add(SOURCE_FLAG); - compilerOptions.add(notebookJdkVersion); + compilerOptions.add(configs.getJdkVersion()); + } + + if (prj != null) { + List projOptions = ProjectConfigurationUtils.compilerOptions(prj); + Map prjConfigMap = new HashMap<>(); + for (int i = 0; i < projOptions.size() - 1; i += 2) { + prjConfigMap.put(projOptions.get(i), projOptions.get(i + 1)); + } + + if (checkEmptyString(configs.getClassPath()) && prjConfigMap.containsKey(CLASS_PATH)) { + addOption.accept(CLASS_PATH, prjConfigMap.get(CLASS_PATH)); + } + if (checkEmptyString(configs.getModulePath()) && prjConfigMap.containsKey(MODULE_PATH)) { + addOption.accept(MODULE_PATH, prjConfigMap.get(MODULE_PATH)); + } + if (checkEmptyString(configs.getAddModules()) && prjConfigMap.containsKey(ADD_MODULES)) { + addOption.accept(ADD_MODULES, prjConfigMap.get(ADD_MODULES)); + } } return compilerOptions; } - private List getRemoteVmOptions() { + private List getRemoteVmOptions(Project prj) { List remoteOptions = new ArrayList<>(); - String classpath = NotebookConfigs.getInstance().getClassPath(); - String modulePath = NotebookConfigs.getInstance().getModulePath(); - String addModules = NotebookConfigs.getInstance().getAddModules(); - boolean isEnablePreview = NotebookConfigs.getInstance().isEnablePreview(); - - if (!checkEmptyString(classpath)) { - remoteOptions.add(CLASS_PATH); - remoteOptions.add(classpath); - } - if (!checkEmptyString(modulePath)) { - remoteOptions.add(MODULE_PATH); - remoteOptions.add(modulePath); - } - if (!checkEmptyString(addModules)) { - remoteOptions.add(ADD_MODULES); - remoteOptions.add(addModules); - } + NotebookConfigs configs = NotebookConfigs.getInstance(); + boolean isEnablePreview = configs.isEnablePreview(); + + BiConsumer addOption = (flag, value) -> { + if (!checkEmptyString(value)) { + remoteOptions.add(flag); + remoteOptions.add(value); + } + }; + + addOption.accept(CLASS_PATH, configs.getClassPath()); + addOption.accept(MODULE_PATH, configs.getModulePath()); + addOption.accept(ADD_MODULES, configs.getAddModules()); + if (isEnablePreview) { remoteOptions.add(ENABLE_PREVIEW); } + if (prj != null) { + List projOptions = ProjectConfigurationUtils.launchVMOptions(prj); + Map prjConfigMap = new HashMap<>(); + for (int i = 0; i < projOptions.size() - 1; i += 2) { + prjConfigMap.put(projOptions.get(i), projOptions.get(i + 1)); + } + + if (checkEmptyString(configs.getClassPath()) && prjConfigMap.containsKey(CLASS_PATH)) { + addOption.accept(CLASS_PATH, prjConfigMap.get(CLASS_PATH)); + } + if (checkEmptyString(configs.getModulePath()) && prjConfigMap.containsKey(MODULE_PATH)) { + addOption.accept(MODULE_PATH, prjConfigMap.get(MODULE_PATH)); + } + if (checkEmptyString(configs.getAddModules()) && prjConfigMap.containsKey(ADD_MODULES)) { + addOption.accept(ADD_MODULES, prjConfigMap.get(ADD_MODULES)); + } + } return remoteOptions; } @@ -194,6 +231,10 @@ public JshellStreamsHandler getJshellStreamsHandler(String notebookId) { return jshellStreamsMap.get(notebookId); } + public ProjectContextInfo getNotebookPrjNameContext(String notebookId) { + return notebookPrjMap.get(notebookId); + } + public void closeSession(String notebookUri) { CompletableFuture future = sessions.remove(notebookUri); JShell jshell = future.getNow(null); @@ -204,5 +245,30 @@ public void closeSession(String notebookUri) { if (handler != null) { handler.close(); } + notebookPrjMap.remove(notebookUri); + } + + private CompletableFuture getProjectContextForNotebook(String notebookUri) { + JsonObject mapping = NotebookConfigs.getInstance().getNotebookProjectMapping(); + String notebookPath = URI.create(notebookUri).getPath(); + String projectKey = mapping.has(notebookPath) + ? Paths.get(mapping.get(notebookPath).getAsString()).toUri().toString() + : notebookUri; + + Project prj = ProjectContext.getProject(projectKey); + + if (prj == null) { + LOG.log(Level.WARNING, "Project not found or not open in workspace: {0}", projectKey); + return CompletableFuture.completedFuture(null); + } + + return ProjectConfigurationUtils.buildProject(prj).thenApply(buildStatus -> { + if (!buildStatus) { + LOG.log(Level.WARNING, "Error while building project: {0}", projectKey); + return null; + } + return prj; + }); + } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/CommandHandler.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/CommandHandler.java index f88cf95..92c85b7 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/CommandHandler.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/CommandHandler.java @@ -15,18 +15,14 @@ */ package org.netbeans.modules.nbcode.java.project; -import com.google.gson.JsonPrimitive; -import java.net.MalformedURLException; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.logging.Level; import java.util.logging.Logger; -import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.Project; -import org.netbeans.modules.java.lsp.server.Utils; +import org.netbeans.modules.nbcode.java.notebook.NotebookSessionManager; import org.netbeans.modules.nbcode.java.notebook.NotebookUtils; -import org.openide.filesystems.FileObject; -import org.openide.util.Exceptions; /** * @@ -37,24 +33,30 @@ public class CommandHandler { private static final Logger LOG = Logger.getLogger(CommandHandler.class.getName()); public static CompletableFuture> openJshellInProjectContext(List args) { - CompletableFuture> future = new CompletableFuture<>(); - LOG.log(Level.FINER, "Request received for opening Jshell instance with project context {0}", args); - - String uri = NotebookUtils.getArgument(args, 0, String.class); - if (uri == null) { - future.completeExceptionally(new IllegalArgumentException("uri is required. It cannot be null")); - return future; + String context = NotebookUtils.getArgument(args, 0, String.class); + String additionalContext = NotebookUtils.getArgument(args, 1, String.class); + CompletableFuture prjFuture; + + if (context != null) { + prjFuture = CompletableFuture.completedFuture(ProjectContext.getProject(context)); + } else { + Project editorPrj = additionalContext != null ? ProjectContext.getProject(additionalContext) : null; + prjFuture = editorPrj != null + ? ProjectContext.getProject(false, new ProjectContextInfo(editorPrj)) + : ProjectContext.getProject(); } - - Project prj = getProject(uri); - if (prj != null) { + + return prjFuture.thenCompose(prj -> { + if (prj == null) { + return CompletableFuture.completedFuture(new ArrayList<>()); + } return ProjectConfigurationUtils.buildProject(prj) .thenCompose(isBuildSuccess -> { - if (Boolean.TRUE.equals(isBuildSuccess)) { + if (isBuildSuccess) { List vmOptions = ProjectConfigurationUtils.launchVMOptions(prj); - LOG.log(Level.INFO, "Opened Jshell instance with project context {0}", uri); + LOG.log(Level.INFO, "Opened Jshell instance with project context {0}", context); return CompletableFuture.completedFuture(vmOptions); } else { CompletableFuture> failed = new CompletableFuture<>(); @@ -62,43 +64,15 @@ public static CompletableFuture> openJshellInProjectContext(List args) { - LOG.log(Level.FINER, "Request received for opening notebook with project context {0}", args); - - String uri = NotebookUtils.getArgument(args, 0, String.class); - String notebookUri = NotebookUtils.getArgument(args, 1, String.class); - - Project prj = getProject(uri); - if (prj != null) { - List remoteVmOptions = ProjectConfigurationUtils.launchVMOptions(prj); - List compileOptions = ProjectConfigurationUtils.compilerOptions(prj); - - LOG.log(Level.INFO, "Opened Notebook instance with project context {0}", uri); - return true; - } - LOG.log(Level.WARNING, "Cannot open Jshell instance as project is null"); - return false; + public static CompletableFuture getNotebookProjectMappingPath(List args) { + LOG.log(Level.FINER, "Request received for notebook project mapping with args: {0}", args); + String notebookUri = NotebookUtils.getArgument(args, 0, String.class); + ProjectContextInfo prjCxtInfo = NotebookSessionManager.getInstance().getNotebookPrjNameContext(notebookUri); + return ProjectContext.getProject(true, prjCxtInfo) + .thenApply(prj -> prj == null ? null : prj.getProjectDirectory().getPath()); } - public static Project getProject(String uri) { - try { - if (uri == null) { - return null; - } - FileObject file = Utils.fromUri(uri); - Project prj = FileOwnerQuery.getOwner(file); - - return prj; - } catch (MalformedURLException ex) { - Exceptions.printStackTrace(ex); - } - return null; - } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectConfigurationUtils.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectConfigurationUtils.java index db223ee..cc7dc11 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectConfigurationUtils.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectConfigurationUtils.java @@ -169,7 +169,7 @@ public static boolean isModularJDK(JavaPlatform pl) { } return false; } - + public static CompletableFuture buildProject(Project project) { CompletableFuture future = new CompletableFuture<>(); ActionProvider p = project.getLookup().lookup(ActionProvider.class); diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectContext.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectContext.java new file mode 100644 index 0000000..3a9bd57 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectContext.java @@ -0,0 +1,116 @@ +/* +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed 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 + * + * https://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.netbeans.modules.nbcode.java.project; + +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import org.netbeans.api.project.FileOwnerQuery; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectUtils; +import org.netbeans.modules.java.lsp.server.LspServerState; +import org.netbeans.modules.java.lsp.server.Utils; +import org.netbeans.modules.java.lsp.server.input.QuickPickItem; +import org.netbeans.modules.java.lsp.server.input.ShowQuickPickParams; +import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient; +import org.openide.filesystems.FileObject; +import org.openide.util.Exceptions; +import org.openide.util.Lookup; + +/** + * + * @author atalati + */ +public class ProjectContext { + + public static Project getProject(String uri) { + try { + if (uri == null) { + return null; + } + FileObject file = Utils.fromUri(uri); + Project prj = FileOwnerQuery.getOwner(file); + + return prj; + } catch (MalformedURLException ex) { + Exceptions.printStackTrace(ex); + } + return null; + } + + public static CompletableFuture getProject() { + return getProject(false, null); + } + + public static CompletableFuture getProject(boolean forceShowQuickPick, ProjectContextInfo prjCxtInfo) { + LspServerState serverState = Lookup.getDefault().lookup(LspServerState.class); + if (serverState == null) { + return CompletableFuture.completedFuture(null); + } + if (forceShowQuickPick) { + return serverState.openedProjects() + .thenCompose(prjs -> selectFromMultipleProjects(prjs, prjCxtInfo).thenApply(res + -> res.isEmpty() ? null : res.getFirst())); + } + return serverState.openedProjects().thenCompose(prjs -> { + switch (prjs.length) { + case 0: + return CompletableFuture.completedFuture(null); + case 1: + return CompletableFuture.completedFuture(prjs[0]); + default: + return selectFromMultipleProjects(prjs, prjCxtInfo).thenApply(res + -> res.isEmpty() ? null : res.getFirst()); + } + }); + } + + private static CompletableFuture> selectFromMultipleProjects(Project[] prjs, ProjectContextInfo defaultPrjSelected) { + NbCodeLanguageClient client = Lookup.getDefault().lookup(NbCodeLanguageClient.class); + if (client == null) { + return CompletableFuture.completedFuture(new ArrayList<>()); + } + String title = "Select Project"; + List items = new ArrayList<>(); + Map prjMap = new HashMap<>(); + for (Project prj : prjs) { + String displayName = ProjectUtils.getInformation(prj).getDisplayName(); + QuickPickItem item = new QuickPickItem(displayName); + prjMap.put(displayName, prj); + items.add(item); + } + String placeholder = defaultPrjSelected != null ? "Current project context: " + defaultPrjSelected.getName() + : items.isEmpty() ? "No projects found" : "No project context"; + + ShowQuickPickParams params = new ShowQuickPickParams(title, placeholder, false, items); + return client.showQuickPick(params).thenApply(selected -> { + List res = new ArrayList<>(); + if (selected == null) { + return res; + } + for (QuickPickItem item : selected) { + if (prjMap.containsKey(item.getLabel())) { + res.add(prjMap.get(item.getLabel())); + } + } + return res; + }); + } +} diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectContextInfo.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectContextInfo.java new file mode 100644 index 0000000..991659c --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectContextInfo.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * Licensed 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 + * + * https://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.netbeans.modules.nbcode.java.project; + +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectUtils; + +/** + * + * @author atalati + */ +public class ProjectContextInfo { + + private final String name; + private final String path; + + public ProjectContextInfo(Project prj) { + this.name = ProjectUtils.getInformation(prj).getDisplayName(); + this.path = prj.getProjectDirectory().getPath(); + } + + public String getName() { + return name; + } + + public String getPath() { + return path; + } +} diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectModulePathConfigurationUtils.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectModulePathConfigurationUtils.java index 14bde96..6aeb4a4 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectModulePathConfigurationUtils.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectModulePathConfigurationUtils.java @@ -30,7 +30,6 @@ import org.netbeans.api.java.classpath.JavaClassPathConstants; import org.netbeans.api.java.platform.JavaPlatform; import org.netbeans.api.java.queries.BinaryForSourceQuery; -import org.netbeans.api.java.queries.SourceLevelQuery; import org.netbeans.api.java.source.ClassIndex; import org.netbeans.api.java.source.ClasspathInfo; import org.netbeans.api.java.source.SourceUtils; @@ -38,7 +37,6 @@ import org.netbeans.spi.java.classpath.support.ClassPathSupport; import org.openide.filesystems.FileObject; import org.openide.filesystems.URLMapper; -import org.openide.modules.SpecificationVersion; /** * Methods in this class are taken from the org.netbeans.modules.jshell.support diff --git a/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/CustomInputStreamTest.java b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/CustomInputStreamTest.java new file mode 100644 index 0000000..957ad6e --- /dev/null +++ b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/CustomInputStreamTest.java @@ -0,0 +1,159 @@ +package org.netbeans.modules.nbcode.java.notebook; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CompletableFuture; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import org.netbeans.modules.java.lsp.server.input.ShowInputBoxParams; + +public class CustomInputStreamTest { + + private CustomInputStream inputStream; + private TestClient mockClient; + + @Before + public void setUp() { + mockClient = new TestClient(); + inputStream = new CustomInputStream(mockClient); + } + + @After + public void tearDown() { + inputStream = null; + mockClient = null; + } + + @Test + public void testReadNoClient() throws IOException { + inputStream.client = null; + assertEquals(-1, inputStream.read()); + + byte[] buffer = new byte[10]; + assertEquals(-1, inputStream.read(buffer, 0, 10)); + } + + @Test + public void testReadWithInput() throws IOException { + mockClient.setNextInput("hello"); + + byte[] buffer = new byte[1024]; + int bytesRead = inputStream.read(buffer, 0, 1024); + String readString = new String(buffer, 0, bytesRead, StandardCharsets.UTF_8); + assertEquals("hello" + System.lineSeparator(), readString); + } + + @Test + public void testReadSingleByte() throws IOException { + mockClient.setNextInput("a"); + + int firstByte = inputStream.read(); + assertEquals('a', firstByte); + + int secondByte = inputStream.read(); + assertEquals(System.lineSeparator().charAt(0), (char) secondByte); + } + + @Test + public void testMultipleInputs() throws IOException { + mockClient.setNextInput("first"); + + byte[] buffer = new byte[1024]; + + int bytesRead1 = inputStream.read(buffer, 0, ("first" + System.lineSeparator()).length()); + String readString1 = new String(buffer, 0, bytesRead1, StandardCharsets.UTF_8); + assertEquals("first" + System.lineSeparator(), readString1); + mockClient.setNextInput("second"); + + int bytesRead2 = inputStream.read(buffer, 0, ("second" + System.lineSeparator()).length()); + String readString2 = new String(buffer, 0, bytesRead2, StandardCharsets.UTF_8); + assertEquals("second" + System.lineSeparator(), readString2); + } + + @Test + public void testNullFutureInput() throws IOException { + mockClient.setNextInput(null); + + assertEquals(-1, inputStream.read()); + + byte[] buffer = new byte[10]; + assertEquals(-1, inputStream.read(buffer, 0, 10)); + } + + @Test + public void testEmptyInput() throws IOException { + mockClient.setNextInput(""); + + byte[] buffer = new byte[1024]; + int bytesRead = inputStream.read(buffer, 0, 1024); + String readString = new String(buffer, 0, bytesRead, StandardCharsets.UTF_8); + assertEquals(System.lineSeparator(), readString); + } + + @Test(expected = IOException.class) + public void testExecutionException() throws IOException { + mockClient.setNextException(new RuntimeException("Test failure")); + + inputStream.read(); + } + + @Test + public void testInterruptedException() { + final CompletableFuture pendingFuture = new CompletableFuture<>(); + mockClient.setNextFuture(pendingFuture); + + Thread testThread = new Thread(() -> { + try { + inputStream.read(); + fail("Should have thrown IOException"); + } catch (IOException e) { + assertTrue(Thread.currentThread().isInterrupted()); + assertTrue(e.getCause() instanceof InterruptedException); + } + }); + + testThread.start(); + testThread.interrupt(); + + try { + testThread.join(5000); + } catch (InterruptedException e) { + fail("Test interrupted"); + } + + assertTrue("Test thread should have completed", !testThread.isAlive()); + } + + private static class TestClient extends MockNbClient { + private CompletableFuture nextFuture; + + public void setNextInput(String input) { + this.nextFuture = CompletableFuture.completedFuture(input); + } + + + public void setNextException(Throwable ex) { + this.nextFuture = new CompletableFuture<>(); + this.nextFuture.completeExceptionally(ex); + } + + public void setNextFuture(CompletableFuture future) { + this.nextFuture = future; + } + + @Override + public CompletableFuture showInputBox(ShowInputBoxParams params) { + if (nextFuture != null) { + CompletableFuture toReturn = nextFuture; + nextFuture = null; + return toReturn; + } + return null; + } + } +} \ No newline at end of file diff --git a/patches/java-notebooks.diff b/patches/java-notebooks.diff index 97a9cde..4175dba 100644 --- a/patches/java-notebooks.diff +++ b/patches/java-notebooks.diff @@ -857,23 +857,23 @@ index 747d151600..bbf1f263d1 100644 private static final Logger LOG = Logger.getLogger(TextDocumentServiceImpl.class.getName()); private static final String COMMAND_RUN_SINGLE = "nbls.run.single"; // NOI18N -@@ -305,13 +305,14 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli +@@ -309,12 +309,15 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli private static final String NETBEANS_COMPLETION_WARNING_TIME = "completion.warning.time";// NOI18N private static final String NETBEANS_JAVA_ON_SAVE_ORGANIZE_IMPORTS = "java.onSave.organizeImports";// NOI18N private static final String NETBEANS_CODE_COMPLETION_COMMIT_CHARS = "java.completion.commit.chars";// NOI18N -+ private static final String NOTEBOOK_TEXT_DOC_URI_IDENTIFIER = "vscode-notebook-cell"; - ++ private static final String NOTEBOOK_TEXT_DOC_URI_IDENTIFIER = "vscode-notebook-cell";// NOI18N private static final String URL = "url";// NOI18N private static final String INDEX = "index";// NOI18N private static final RequestProcessor BACKGROUND_TASKS = new RequestProcessor(TextDocumentServiceImpl.class.getName(), 1, false, false); private static final RequestProcessor WORKER = new RequestProcessor(TextDocumentServiceImpl.class.getName(), 1, false, false); -- + + private NotebookDocumentServiceHandler notebookDocumentServiceDelegator = null; ++ /** * File URIs touched / queried by the client. */ -@@ -334,6 +335,48 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli +@@ -337,7 +340,49 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli runDiagnosticTasks(doc, true); } } @@ -898,7 +898,7 @@ index 747d151600..bbf1f263d1 100644 + } + notebookDocumentServiceDelegator.didOpen(params); + } -+ + + @Override + public void didChange(DidChangeNotebookDocumentParams params) { + if(isNotebookSupportEnabled()){ @@ -919,10 +919,11 @@ index 747d151600..bbf1f263d1 100644 + notebookDocumentServiceDelegator.didClose(params); + } + } - ++ @ServiceProvider(service=IndexingAware.class, position=0) public static final class RefreshDocument implements IndexingAware { -@@ -378,6 +421,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + +@@ -381,6 +426,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli "INFO_LongCodeCompletion=Analyze completions taking longer than {0}. A sampler snapshot has been saved to: {1}" }) public CompletableFuture, CompletionList>> completion(CompletionParams params) { @@ -935,13 +936,14 @@ index 747d151600..bbf1f263d1 100644 AtomicBoolean done = new AtomicBoolean(); AtomicReference samplerRef = new AtomicReference<>(); AtomicLong samplingStart = new AtomicLong(); -@@ -626,8 +675,19 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli +@@ -628,6 +679,18 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + SemanticTokensLegend legend = new SemanticTokensLegend(new ArrayList<>(tokenLegend.keySet()), new ArrayList<>(modifiersLegend.keySet())); cap.setLegend(legend); severCapabilities.setSemanticTokensProvider(cap); - } -+ checkNotebookSupport(severCapabilities); - } -- ++ checkNotebookSupport(severCapabilities); ++ } ++ } ++ + private void checkNotebookSupport(ServerCapabilities serverCapabilities) { + if (client != null && client.getNbCodeCapabilities().wantsNotebookSupport()) { + NotebookDocumentSyncRegistrationOptions opts = new NotebookDocumentSyncRegistrationOptions(); @@ -950,13 +952,10 @@ index 747d151600..bbf1f263d1 100644 + ns.setCells(List.of(new NotebookSelectorCell("java"), new NotebookSelectorCell("markdown"))); + opts.setNotebookSelector(List.of(ns)); + serverCapabilities.setNotebookDocumentSync(opts); -+ } -+ } -+ - @Override - public CompletableFuture resolveCompletionItem(CompletionItem ci) { - JsonObject rawData = (JsonObject) ci.getData(); -@@ -702,6 +762,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + } + } + +@@ -705,6 +768,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli @Override public CompletableFuture hover(HoverParams params) { @@ -969,7 +968,7 @@ index 747d151600..bbf1f263d1 100644 // shortcut: if the projects are not yet initialized, return empty: if (server.openedProjects().getNow(null) == null) { return CompletableFuture.completedFuture(null); -@@ -726,6 +792,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli +@@ -729,6 +798,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli @Override public CompletableFuture signatureHelp(SignatureHelpParams params) { @@ -982,7 +981,7 @@ index 747d151600..bbf1f263d1 100644 // shortcut: if the projects are not yet initialized, return empty: if (server.openedProjects().getNow(null) == null) { return CompletableFuture.completedFuture(null); -@@ -777,6 +849,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli +@@ -780,6 +855,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli @Override public CompletableFuture, List>> definition(DefinitionParams params) { @@ -995,7 +994,7 @@ index 747d151600..bbf1f263d1 100644 try { String uri = params.getTextDocument().getUri(); Document rawDoc = server.getOpenedDocuments().getDocument(uri); -@@ -801,6 +879,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli +@@ -804,6 +885,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli @Override public CompletableFuture, List>> typeDefinition(TypeDefinitionParams params) { @@ -1008,7 +1007,7 @@ index 747d151600..bbf1f263d1 100644 try { String uri = params.getTextDocument().getUri(); Document rawDoc = server.getOpenedDocuments().getDocument(uri); -@@ -825,11 +909,23 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli +@@ -828,11 +915,23 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli @Override public CompletableFuture, List>> implementation(ImplementationParams params) { @@ -1032,7 +1031,7 @@ index 747d151600..bbf1f263d1 100644 return usages(params.getTextDocument().getUri(), params.getPosition(), false, params.getContext().isIncludeDeclaration()); } -@@ -985,6 +1081,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli +@@ -988,6 +1087,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli @Override public CompletableFuture> documentHighlight(DocumentHighlightParams params) { @@ -1045,7 +1044,7 @@ index 747d151600..bbf1f263d1 100644 class MOHighligther extends MarkOccurrencesHighlighterBase { @Override protected void process(CompilationInfo arg0, Document arg1, SchedulerEvent arg2) { -@@ -1028,6 +1130,13 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli +@@ -1031,6 +1136,13 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli @Override public CompletableFuture>> documentSymbol(DocumentSymbolParams params) { @@ -1059,7 +1058,7 @@ index 747d151600..bbf1f263d1 100644 final CompletableFuture>> resultFuture = new CompletableFuture<>(); BACKGROUND_TASKS.post(() -> { -@@ -1084,6 +1193,13 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli +@@ -1087,6 +1199,13 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli @Override public CompletableFuture>> codeAction(CodeActionParams params) { @@ -1073,7 +1072,7 @@ index 747d151600..bbf1f263d1 100644 lastCodeActions = new ArrayList<>(); AtomicInteger index = new AtomicInteger(0); -@@ -1268,6 +1384,12 @@ +@@ -1271,6 +1390,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli if (!client.getNbCodeCapabilities().wantsJavaSupport()) { return CompletableFuture.completedFuture(Collections.emptyList()); } @@ -1086,7 +1085,7 @@ index 747d151600..bbf1f263d1 100644 // shortcut: if the projects are not yet initialized, return empty: if (server.openedProjects().getNow(null) == null) { return CompletableFuture.completedFuture(Collections.emptyList()); -@@ -1406,6 +1542,12 @@ +@@ -1431,6 +1556,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli @Override public CompletableFuture> formatting(DocumentFormattingParams params) { @@ -1099,7 +1098,7 @@ index 747d151600..bbf1f263d1 100644 String uri = params.getTextDocument().getUri(); Document doc = server.getOpenedDocuments().getDocument(uri); return format(doc, 0, doc.getLength()); -@@ -1435,6 +1563,13 @@ +@@ -1438,6 +1569,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli @Override public CompletableFuture> rangeFormatting(DocumentRangeFormattingParams params) { @@ -1108,12 +1107,11 @@ index 747d151600..bbf1f263d1 100644 + return notebookDocumentServiceDelegator.rangeFormatting(params); + } + return CompletableFuture.completedFuture(Collections.emptyList()); -+ } -+ ++ } String uri = params.getTextDocument().getUri(); Document rawDoc = server.getOpenedDocuments().getDocument(uri); if (rawDoc instanceof StyledDocument) { -@@ -1611,6 +1611,13 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli +@@ -1479,6 +1616,13 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli @Override public CompletableFuture> prepareRename(PrepareRenameParams params) { @@ -1127,7 +1125,7 @@ index 747d151600..bbf1f263d1 100644 // shortcut: if the projects are not yet initialized, return empty: if (server.openedProjects().getNow(null) == null) { return CompletableFuture.completedFuture(null); -@@ -1670,6 +1677,13 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli +@@ -1538,6 +1682,13 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli @Override public CompletableFuture rename(RenameParams params) { @@ -1141,7 +1139,7 @@ index 747d151600..bbf1f263d1 100644 // shortcut: if the projects are not yet initialized, return empty: if (server.openedProjects().getNow(null) == null) { return CompletableFuture.completedFuture(new WorkspaceEdit()); -@@ -1799,6 +1813,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli +@@ -1667,6 +1818,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli @Override public CompletableFuture> foldingRange(FoldingRangeRequestParams params) { @@ -1154,7 +1152,7 @@ index 747d151600..bbf1f263d1 100644 JavaSource source = getJavaSource(params.getTextDocument().getUri()); if (source == null) { return CompletableFuture.completedFuture(Collections.emptyList()); -@@ -1964,6 +1971,12 @@ +@@ -1810,6 +1967,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli @Override public void didOpen(DidOpenTextDocumentParams params) { @@ -1167,7 +1165,7 @@ index 747d151600..bbf1f263d1 100644 LOG.log(Level.FINER, "didOpen: {0}", params); try { FileObject file = fromURI(params.getTextDocument().getUri(), true); -@@ -2014,6 +2049,12 @@ +@@ -1882,6 +2045,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli @Override public void didChange(DidChangeTextDocumentParams params) { @@ -1180,7 +1178,7 @@ index 747d151600..bbf1f263d1 100644 LOG.log(Level.FINER, "didChange: {0}", params); String uri = params.getTextDocument().getUri(); Document rawDoc = server.getOpenedDocuments().getDocument(uri); -@@ -2063,6 +2082,12 @@ +@@ -1909,6 +2078,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli @Override public void didClose(DidCloseTextDocumentParams params) { @@ -1193,7 +1191,7 @@ index 747d151600..bbf1f263d1 100644 LOG.log(Level.FINER, "didClose: {0}", params); try { String uri = params.getTextDocument().getUri(); -@@ -2087,6 +2112,12 @@ +@@ -1933,6 +2108,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli @Override public CompletableFuture> willSaveWaitUntil(WillSaveTextDocumentParams params) { @@ -1206,7 +1204,7 @@ index 747d151600..bbf1f263d1 100644 LOG.log(Level.FINER, "willSaveWaitUntil: {0}", params); String uri = params.getTextDocument().getUri(); JavaSource js = getJavaSource(uri); -@@ -2114,6 +2145,13 @@ +@@ -1960,6 +2141,13 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli @Override public void didSave(DidSaveTextDocumentParams savedParams) { @@ -1220,7 +1218,7 @@ index 747d151600..bbf1f263d1 100644 LOG.log(Level.FINE, "didSave: {0}", savedParams.getTextDocument().getUri()); FileObject file = fromURI(savedParams.getTextDocument().getUri()); if (file == null) { -@@ -2707,6 +2745,12 @@ +@@ -2553,6 +2741,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli @Override public CompletableFuture semanticTokensFull(SemanticTokensParams params) { @@ -1233,7 +1231,7 @@ index 747d151600..bbf1f263d1 100644 JavaSource js = getJavaSource(params.getTextDocument().getUri()); List result = new ArrayList<>(); if (js != null) { -@@ -2783,6 +2827,12 @@ +@@ -2629,6 +2823,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli @Override public CompletableFuture> prepareCallHierarchy(CallHierarchyPrepareParams params) { @@ -1246,4 +1244,29 @@ index 747d151600..bbf1f263d1 100644 FileObject file = fromURI(params.getTextDocument().getUri()); if (file == null) { return CompletableFuture.completedFuture(null); - +@@ -2829,6 +3029,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + + @Override + public CompletableFuture> inlayHint(InlayHintParams params) { ++ if (isRequiredToDelegateToNotebooks(params.getTextDocument().getUri())) { ++ if (isNotebookSupportEnabled()) { ++ return notebookDocumentServiceDelegator.inlayHint(params); ++ } ++ return CompletableFuture.completedFuture(new ArrayList<>()); ++ } + String uri = params.getTextDocument().getUri(); + ConfigurationItem conf = new ConfigurationItem(); + conf.setScopeUri(uri); +@@ -2887,6 +3093,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + @Override + public CompletableFuture> inlineValue(InlineValueParams params) { + String uri = params.getTextDocument().getUri(); ++ if (isRequiredToDelegateToNotebooks(uri)) { ++ if (isNotebookSupportEnabled()) { ++ return notebookDocumentServiceDelegator.inlineValue(params); ++ } ++ return CompletableFuture.completedFuture(new ArrayList<>()); ++ } + FileObject file = fromURI(uri); + if (file == null) { + return CompletableFuture.completedFuture(null); diff --git a/vscode/l10n/bundle.l10n.en.json b/vscode/l10n/bundle.l10n.en.json index aeef13a..b67a2c0 100644 --- a/vscode/l10n/bundle.l10n.en.json +++ b/vscode/l10n/bundle.l10n.en.json @@ -99,5 +99,15 @@ "jdk.extension.error_msg.notEnabled": "{SERVER_NAME} not enabled", "jdk.telemetry.consent": "Allow anonymous telemetry data to be reported to Oracle? You may opt-out or in at any time from the Settings for jdk.telemetry.enabled.", "jdk.configChanged": "Configuration updated for the Oracle Java extension. Please reload the window to enable it.", - "jdk.configChangedFailed": "Error while updating the configuration for the Oracle Java extension. Please reload the window to restart the extension." + "jdk.configChangedFailed": "Error while updating the configuration for the Oracle Java extension. Please reload the window to restart the extension.", + "jdk.notebook.create.select.workspace.folder": "Select workspace folder in which notebook needs to be created", + "jdk.notebook.create.select.workspace.folder.label": "Select Notebook creation folder", + "jdk.notebook.create.select.workspace.folder.title": "Select folder in which notebook needs to be created", + "jdk.notebook.create.error_msg.path.not.selected": "Path not selected for creating new notebook", + "jdk.notebook.create.error_msg.invalid.notebook.name": "Invalid notebook file name", + "jdk.notebook.create.error_msg.invalid.notebook.path": "Notebook already exists, please try creating with some different name", + "jdk.notebook.create.error_msg.failed": "Failed to create new notebook", + "jdk.jshell.open.error_msg.failed": "Some error occurred while launching jshell", + "jdk.notebook.project.mapping.error_msg.failed": "Error occurred while changing notebook project context", + "jdk.notebook.create.new.notebook.input.name": "Enter new Java notebook ({fileExtension}) file name" } diff --git a/vscode/l10n/bundle.l10n.ja.json b/vscode/l10n/bundle.l10n.ja.json index 98c235c..1fccb2d 100755 --- a/vscode/l10n/bundle.l10n.ja.json +++ b/vscode/l10n/bundle.l10n.ja.json @@ -99,5 +99,15 @@ "jdk.extension.error_msg.notEnabled": "{SERVER_NAME}が有効化されていません", "jdk.telemetry.consent": "匿名テレメトリ・データをOracleにレポートすることを許可しますか。jdk.telemetry.enabledの設定からいつでもオプトアウトまたはオプトインできます。", "jdk.configChanged": "Configuration updated for the Oracle Java extension. Please reload the window to enable it.", - "jdk.configChangedFailed": "Error while updating the configuration for the Oracle Java extension. Please reload the window to restart the extension." + "jdk.configChangedFailed": "Error while updating the configuration for the Oracle Java extension. Please reload the window to restart the extension.", + "jdk.notebook.create.select.workspace.folder": "Select workspace folder in which notebook needs to be created", + "jdk.notebook.create.select.workspace.folder.label": "Select Notebook creation folder", + "jdk.notebook.create.select.workspace.folder.title": "Select folder in which notebook needs to be created", + "jdk.notebook.create.error_msg.path.not.selected": "Path not selected for creating new notebook", + "jdk.notebook.create.error_msg.invalid.notebook.name": "Invalid notebook file name", + "jdk.notebook.create.error_msg.invalid.notebook.path": "Notebook already exists, please try creating with some different name", + "jdk.notebook.create.error_msg.failed": "Failed to create new notebook", + "jdk.jshell.open.error_msg.failed": "Some error occurred while launching jshell", + "jdk.notebook.project.mapping.error_msg.failed": "Error occurred while changing notebook project context", + "jdk.notebook.create.new.notebook.input.name": "Enter new Java notebook ({fileExtension}) file name" } diff --git a/vscode/l10n/bundle.l10n.zh-cn.json b/vscode/l10n/bundle.l10n.zh-cn.json index 1ed0d94..4378d70 100755 --- a/vscode/l10n/bundle.l10n.zh-cn.json +++ b/vscode/l10n/bundle.l10n.zh-cn.json @@ -99,5 +99,15 @@ "jdk.extension.error_msg.notEnabled": "{SERVER_NAME} 未启用", "jdk.telemetry.consent": "是否允许向 Oracle 报告匿名遥测数据?您随时可以通过 jdk.telemetry.enabled 对应的设置选择退出或加入。", "jdk.configChanged": "Configuration updated for the Oracle Java extension. Please reload the window to enable it.", - "jdk.configChangedFailed": "Error while updating the configuration for the Oracle Java extension. Please reload the window to restart the extension." + "jdk.configChangedFailed": "Error while updating the configuration for the Oracle Java extension. Please reload the window to restart the extension.", + "jdk.notebook.create.select.workspace.folder": "Select workspace folder in which notebook needs to be created", + "jdk.notebook.create.select.workspace.folder.label": "Select Notebook creation folder", + "jdk.notebook.create.select.workspace.folder.title": "Select folder in which notebook needs to be created", + "jdk.notebook.create.error_msg.path.not.selected": "Path not selected for creating new notebook", + "jdk.notebook.create.error_msg.invalid.notebook.name": "Invalid notebook file name", + "jdk.notebook.create.error_msg.invalid.notebook.path": "Notebook already exists, please try creating with some different name", + "jdk.notebook.create.error_msg.failed": "Failed to create new notebook", + "jdk.jshell.open.error_msg.failed": "Some error occurred while launching jshell", + "jdk.notebook.project.mapping.error_msg.failed": "Error occurred while changing notebook project context", + "jdk.notebook.create.new.notebook.input.name": "Enter new Java notebook ({fileExtension}) file name" } diff --git a/vscode/package-lock.json b/vscode/package-lock.json index aee3c2c..b5d010e 100644 --- a/vscode/package-lock.json +++ b/vscode/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@vscode/debugadapter": "^1.68.0", "@vscode/l10n": "^0.0.18", - "ajv": "^6.12.6", + "ajv": "^8.17.1", "jsonc-parser": "3.3.1", "vscode-languageclient": "^9.0.1" }, @@ -1144,14 +1144,15 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -1740,10 +1741,21 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" }, "node_modules/find-cache-dir": { "version": "3.3.2", @@ -2289,9 +2301,10 @@ } }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" }, "node_modules/json5": { "version": "2.2.3", @@ -3209,14 +3222,6 @@ "node": ">=8" } }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "engines": { - "node": ">=6" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -3277,6 +3282,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -3858,14 +3872,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/vscode/package.json b/vscode/package.json index 935040f..f0b289e 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -41,8 +41,7 @@ "workspaceContains:build.gradle", "onDebug", "onDebugDynamicConfigurations", - "onNotebook:ijnb-notebook", - "onNotebook:jupyter-notebook" + "onNotebook:ijnb-notebook" ], "main": "./out/extension.js", "l10n": "./l10n", @@ -258,22 +257,22 @@ "jdk.notebook.classpath": { "type": "string", "default": "", - "description": "Classpath that needs to be set for notebooks" + "description": "%jdk.notebook.classpath.description%" }, "jdk.notebook.modulepath": { "type": "string", "default": "", - "description": "Modulepath that needs to be set for notebooks" + "description": "%jdk.notebook.modulepath.description%" }, "jdk.notebook.addmodules": { "type": "string", "default": "", - "description": "Modules needs for notebooks" + "description": "%jdk.notebook.addmodules.description%" }, "jdk.notebook.enablePreview": { "type": "boolean", "default": false, - "description": "Flag to enable jdk preview features for notebooks" + "description": "%jdk.notebook.enablePreview.description%" }, "jdk.notebook.implicitImports": { "type": "array", @@ -282,7 +281,12 @@ "java.io.*", "java.math.*" ], - "description": "list of packages to import implicitly." + "description": "%jdk.notebook.implicitImports.description%" + }, + "jdk.notebook.projects.mapping": { + "type": "object", + "default": {}, + "description": "%jdk.notebook.projects.mapping.description%" }, "jdk.telemetry.enabled": { "type": "boolean", @@ -594,6 +598,12 @@ "title": "%jdk.notebook.new%", "category": "Java", "icon": "$(new-file)" + }, + { + "command": "jdk.notebook.change.project", + "title": "%jdk.notebook.change.project%", + "category": "Java", + "icon": "$(pencil)" } ], "keybindings": [ @@ -712,6 +722,10 @@ { "command": "jdk.addEventListener", "when": "false" + }, + { + "command": "jdk.notebook.change.project", + "when": "false" } ], "view/title": [ @@ -786,6 +800,13 @@ "when": "view == run-config && viewItem == configureRunSettings", "group": "inline@1" } + ], + "notebook/toolbar": [ + { + "command": "jdk.notebook.change.project", + "group": "navigation@1", + "when": "nbJdkReady && resourceExtname == .ijnb" + } ] }, "netbeans.iconMapping": [ @@ -905,7 +926,7 @@ "dependencies": { "@vscode/debugadapter": "^1.68.0", "@vscode/l10n": "^0.0.18", - "ajv": "^6.12.6", + "ajv": "^8.17.1", "jsonc-parser": "3.3.1", "vscode-languageclient": "^9.0.1" }, diff --git a/vscode/package.nls.ja.json b/vscode/package.nls.ja.json index 7bc09bb..901bf1d 100755 --- a/vscode/package.nls.ja.json +++ b/vscode/package.nls.ja.json @@ -6,11 +6,14 @@ "jdk.workspace.new": "テンプレートからの新規ファイル...", "jdk.workspace.newproject": "新規プロジェクト...", "jdk.java.goto.super.implementation": "スーパークラスの実装へ移動", + "jdk.jshell.project": "Open JShell", "jdk.open.type": "タイプを開く...", "jdk.foundProjects.deleteEntry": "削除", "jdk.Edit.org.openide.actions.DeleteAction": "削除", "workbench.action.debug.run": "デバッグなしで実行", "workbench.action.debug.start": "デバッグの開始", + "jdk.notebook.new": "Create New Notebook...", + "jdk.notebook.change.project": "Project Context", "jdk.project.run": "デバッグなしでプロジェクトの実行", "jdk.project.debug": "プロジェクトのデバッグ", "jdk.project.test": "プロジェクトのテスト", @@ -65,6 +68,12 @@ "jdk.debugger.configuration.attach.listen.description": "接続するデバッグ対象をリスニング", "jdk.debugger.configuration.attach.timeout.description": "接続を待つ間のタイムアウト", "jdk.debugger.configuration.completion.warning.time.description": "コード補完にここで指定した時間(ミリ秒単位)よりも長くかかる場合、警告が生成されます(-1で無効化)", + "jdk.notebook.classpath.description": "Classpath that needs to be set for notebooks", + "jdk.notebook.modulepath.description": "Modulepath that needs to be set for notebooks", + "jdk.notebook.addmodules.description": "Modules that needs to be added for notebooks", + "jdk.notebook.enablePreview.description": "Flag to enable jdk preview features for notebooks", + "jdk.notebook.implicitImports.description": "List of packages to import implicitly", + "jdk.notebook.projects.mapping.description": "Mapping of notebook to project", "jdk.configuration.java.completion.commit.chars": "コード補完の提案の受入れをトリガーする文字を指定します。たとえば、ピリオド(.)を入力したときに提案を受け入れるには、これを[\".\"]に設定します", "jdk.initialConfigurations.launchJavaApp.name": "Javaアプリケーションの起動", "jdk.configurationSnippets.name": "Javaアプリケーションの起動", diff --git a/vscode/package.nls.json b/vscode/package.nls.json index ac93a93..ae25d18 100644 --- a/vscode/package.nls.json +++ b/vscode/package.nls.json @@ -6,13 +6,14 @@ "jdk.workspace.new": "New File from Template...", "jdk.workspace.newproject": "New Project...", "jdk.java.goto.super.implementation": "Go to Super Implementation", - "jdk.jshell.project": "Open JShell in context of a Project", + "jdk.jshell.project": "Open JShell", "jdk.open.type": "Open Type...", "jdk.foundProjects.deleteEntry": "Delete", "jdk.Edit.org.openide.actions.DeleteAction": "Delete", "workbench.action.debug.run": "Run Without Debugging", "workbench.action.debug.start": "Start Debugging", "jdk.notebook.new": "Create New Notebook...", + "jdk.notebook.change.project": "Project Context", "jdk.project.run": "Run Project Without Debugging", "jdk.project.debug": "Debug Project", "jdk.project.test": "Test Project", @@ -67,6 +68,12 @@ "jdk.debugger.configuration.attach.listen.description": "Listen for the debuggee to attach", "jdk.debugger.configuration.attach.timeout.description": "Timeout while waiting to attach", "jdk.debugger.configuration.completion.warning.time.description": "When code completion takes longer than this specified time (in milliseconds), there will be a warning produced (-1 to disable)", + "jdk.notebook.classpath.description": "Classpath that needs to be set for notebooks", + "jdk.notebook.modulepath.description": "Modulepath that needs to be set for notebooks", + "jdk.notebook.addmodules.description": "Modules that needs to be added for notebooks", + "jdk.notebook.enablePreview.description": "Flag to enable jdk preview features for notebooks", + "jdk.notebook.implicitImports.description": "List of packages to import implicitly", + "jdk.notebook.projects.mapping.description": "Mapping of notebook to project", "jdk.configuration.java.completion.commit.chars": "Specifies the characters that trigger accepting a code completion suggestion. For example, to accept suggestions when typing a dot (.), set this to [\".\"]", "jdk.initialConfigurations.launchJavaApp.name": "Launch Java App", "jdk.configurationSnippets.name": "Launch Java App", diff --git a/vscode/package.nls.zh-cn.json b/vscode/package.nls.zh-cn.json index 64177b6..4938282 100755 --- a/vscode/package.nls.zh-cn.json +++ b/vscode/package.nls.zh-cn.json @@ -6,11 +6,14 @@ "jdk.workspace.new": "从模板新建文件...", "jdk.workspace.newproject": "新建项目...", "jdk.java.goto.super.implementation": "转至超类实现", + "jdk.jshell.project": "Open JShell", "jdk.open.type": "打开类型...", "jdk.foundProjects.deleteEntry": "删除", "jdk.Edit.org.openide.actions.DeleteAction": "删除", "workbench.action.debug.run": "运行但不调试", "workbench.action.debug.start": "启动调试", + "jdk.notebook.new": "Create New Notebook...", + "jdk.notebook.change.project": "Change Project Context", "jdk.project.run": "运行项目但不调试", "jdk.project.debug": "调试项目", "jdk.project.test": "测试项目", @@ -65,6 +68,12 @@ "jdk.debugger.configuration.attach.listen.description": "监听要附加的被调试程序", "jdk.debugger.configuration.attach.timeout.description": "等待附加操作时的超时", "jdk.debugger.configuration.completion.warning.time.description": "当代码完成所用时间超过此指定时间(毫秒)时,将生成警告(-1 表示禁用)", + "jdk.notebook.classpath.description": "Classpath that needs to be set for notebooks", + "jdk.notebook.modulepath.description": "Modulepath that needs to be set for notebooks", + "jdk.notebook.addmodules.description": "Modules that needs to be added for notebooks", + "jdk.notebook.enablePreview.description": "Flag to enable jdk preview features for notebooks", + "jdk.notebook.implicitImports.description": "List of packages to import implicitly", + "jdk.notebook.projects.mapping.description": "Mapping of notebook to project", "jdk.configuration.java.completion.commit.chars": "指定用于触发接受代码补全建议的字符。例如,要在键入点 (.) 时接受建议,请将该字符设为 [\".\"]", "jdk.initialConfigurations.launchJavaApp.name": "启动 Java 应用程序", "jdk.configurationSnippets.name": "启动 Java 应用程序", diff --git a/vscode/src/commands/commands.ts b/vscode/src/commands/commands.ts index b7f2a6d..c696189 100644 --- a/vscode/src/commands/commands.ts +++ b/vscode/src/commands/commands.ts @@ -52,7 +52,8 @@ export const extCommands = { loadWorkspaceTests: appendPrefixToCommand("load.workspace.tests"), projectDeleteEntry: appendPrefixToCommand("foundProjects.deleteEntry"), createNotebook: appendPrefixToCommand("notebook.new"), - openJshellInProject: appendPrefixToCommand("jshell.project") + openJshellInProject: appendPrefixToCommand("jshell.project"), + notebookChangeProjectContext: appendPrefixToCommand("notebook.change.project") } export const builtInCommands = { @@ -89,4 +90,5 @@ export const nbCommands = { executeNotebookCell: appendPrefixToCommand("jshell.execute.cell"), interruptNotebookCellExecution: appendPrefixToCommand("jshell.interrupt.cell"), openJshellInProject: appendPrefixToCommand("jshell.project.open"), + createNotebookProjectContext: appendPrefixToCommand("notebook.project.context") } \ No newline at end of file diff --git a/vscode/src/commands/create.ts b/vscode/src/commands/create.ts index f5a10d5..3f41b08 100644 --- a/vscode/src/commands/create.ts +++ b/vscode/src/commands/create.ts @@ -96,6 +96,7 @@ const newProject = async (ctx: any) => { } }; + export const registerCreateCommands: ICommand[] = [ { command: extCommands.newFromTemplate, diff --git a/vscode/src/commands/notebook.ts b/vscode/src/commands/notebook.ts index cba5132..d4ed3b7 100644 --- a/vscode/src/commands/notebook.ts +++ b/vscode/src/commands/notebook.ts @@ -2,7 +2,7 @@ import * as os from 'os'; import * as path from 'path'; import * as fs from 'fs'; import { LOGGER } from '../logger'; -import { commands, Uri, window, workspace } from 'vscode'; +import { commands, ConfigurationTarget, Uri, window, workspace } from 'vscode'; import { isError } from '../utils'; import { extCommands, nbCommands } from './commands'; import { ICommand } from './types'; @@ -12,8 +12,10 @@ import { getContextUri, isNbCommandRegistered } from './utils'; import { l10n } from '../localiser'; import { extConstants } from '../constants'; import { Notebook } from '../notebooks/notebook'; -import { ICodeCell } from '../notebooks/types'; +import { ICodeCell, INotebookToolbar } from '../notebooks/types'; import { randomUUID } from 'crypto'; +import { getConfigurationValue, updateConfigurationValue } from '../configurations/handlers'; +import { configKeys } from '../configurations/configuration'; const createNewNotebook = async (ctx?: any) => { try { @@ -35,7 +37,7 @@ const createNewNotebook = async (ctx?: any) => { if (defaultUri == null) { if (workspaceFolders && workspaceFolders.length > 1) { const userPref = await window.showWorkspaceFolderPick({ - placeHolder: "Select workspace folder in which notebook needs to be created", + placeHolder: l10n.value("jdk.notebook.create.select.workspace.folder"), ignoreFocusOut: true }); if (userPref) { @@ -53,8 +55,8 @@ const createNewNotebook = async (ctx?: any) => { canSelectFiles: false, canSelectMany: false, defaultUri, - openLabel: "Select Notebook creation folder", - title: "Select folder in which notebook needs to be created" + openLabel: l10n.value("jdk.notebook.create.select.workspace.folder.label"), + title: l10n.value("jdk.notebook.create.select.workspace.folder.title") }); if (nbFolderPath) { @@ -64,20 +66,20 @@ const createNewNotebook = async (ctx?: any) => { notebookDir = getContextUri(ctx) || null; } if (notebookDir == null) { - window.showErrorMessage("Path not selected for creating new notebook"); + window.showErrorMessage(l10n.value("jdk.notebook.create.error_msg.path.not.selected")); return; } const notebookName = await window.showInputBox({ - prompt: `Enter new Java notebook (${extConstants.NOTEBOOK_FILE_EXTENSION}) or (.ipynb) file name`, + prompt: l10n.value("jdk.notebook.create.new.notebook.input.name", { fileExtension: extConstants.NOTEBOOK_FILE_EXTENSION }), value: `Untitled.${extConstants.NOTEBOOK_FILE_EXTENSION}` }); if (!notebookName?.trim()) { - window.showErrorMessage("Invalid notebook file name"); + window.showErrorMessage(l10n.value("jdk.notebook.create.error_msg.invalid.notebook.name")); return; } - const notebookNameWithExt = notebookName.endsWith(extConstants.NOTEBOOK_FILE_EXTENSION) || notebookName.endsWith('.ipynb') ? + const notebookNameWithExt = notebookName.endsWith(extConstants.NOTEBOOK_FILE_EXTENSION) ? notebookName : `${notebookName}.${extConstants.NOTEBOOK_FILE_EXTENSION}`; const finalNotebookPath = path.join(notebookDir.fsPath, notebookNameWithExt); @@ -85,7 +87,7 @@ const createNewNotebook = async (ctx?: any) => { LOGGER.log(`Attempting to create notebook at: ${finalNotebookPath}`); if (fs.existsSync(finalNotebookPath)) { - window.showErrorMessage("Notebook already exists, please try creating with some different name"); + window.showErrorMessage(l10n.value("jdk.notebook.create.error_msg.invalid.notebook.path")); return; } @@ -110,7 +112,7 @@ const createNewNotebook = async (ctx?: any) => { } catch (error) { LOGGER.error(`Error occurred while creating new notebook: ${isError(error) ? error.message : error}`); - window.showErrorMessage(`Failed to create new notebook`); + window.showErrorMessage(l10n.value("jdk.notebook.create.error_msg.failed")); } }; @@ -118,7 +120,8 @@ const openJshellInContextOfProject = async (ctx: any) => { try { let client: LanguageClient = await globalState.getClientPromise().client; if (await isNbCommandRegistered(nbCommands.openJshellInProject)) { - const res: string[] = await commands.executeCommand(nbCommands.openJshellInProject, getContextUri(ctx)?.toString()); + const additionalContext = window.activeTextEditor?.document.uri.toString(); + const res = await commands.executeCommand(nbCommands.openJshellInProject, ctx?.toString(), additionalContext); const { envMap, finalArgs } = passArgsToTerminal(res); // Direct sendText is not working since it truncates the command exceeding a certain length. // Open issues on vscode: 130688, 134324 and many more @@ -132,7 +135,7 @@ const openJshellInContextOfProject = async (ctx: any) => { throw l10n.value("jdk.extension.error_msg.doesntSupportGoToTest", { client }); } } catch (error) { - window.showErrorMessage("Some error occurred while launching jshell"); + window.showErrorMessage(l10n.value("jdk.jshell.open.error_msg.failed")); LOGGER.error(`Error occurred while launching jshell in project context : ${isError(error) ? error.message : error}`); } } @@ -150,6 +153,30 @@ const passArgsToTerminal = (args: string[]): { envMap: { [key: string]: string } return { envMap, finalArgs }; } +const notebookChangeProjectContextHandler = async (ctx: INotebookToolbar) => { + try { + const uri: Uri = ctx.notebookEditor.notebookUri; + + let client: LanguageClient = await globalState.getClientPromise().client; + if (await isNbCommandRegistered(nbCommands.createNotebookProjectContext)) { + const res = await commands.executeCommand(nbCommands.createNotebookProjectContext, uri.toString()); + if (!res) { + return; + } + const oldValue = getConfigurationValue(configKeys.notebookProjectMapping, {}); + updateConfigurationValue(configKeys.notebookProjectMapping, + { ...oldValue, [uri.fsPath]: res }, + ConfigurationTarget.Workspace); + } else { + throw l10n.value("jdk.extension.error_msg.doesntSupportGoToTest", { client }); + } + } catch (error) { + LOGGER.error(`Error occurred while opening notebook : ${isError(error) ? error.message : error}`); + window.showErrorMessage(l10n.value("jdk.notebook.project.mapping.error_msg.failed")); + } +} + + export const registerNotebookCommands: ICommand[] = [ { command: extCommands.createNotebook, @@ -158,5 +185,9 @@ export const registerNotebookCommands: ICommand[] = [ { command: extCommands.openJshellInProject, handler: openJshellInContextOfProject + }, + { + command: extCommands.notebookChangeProjectContext, + handler: notebookChangeProjectContextHandler } ]; \ No newline at end of file diff --git a/vscode/src/configurations/configuration.ts b/vscode/src/configurations/configuration.ts index 66885f3..53c1621 100644 --- a/vscode/src/configurations/configuration.ts +++ b/vscode/src/configurations/configuration.ts @@ -37,7 +37,7 @@ export const configKeys = { notebookEnablePreview: "notebook.enablePreview", notebookImplicitImports: "notebook.implicitImports", telemetryEnabled: 'telemetry.enabled', - + notebookProjectMapping: "notebook.projects.mapping" }; export const builtInConfigKeys = { @@ -55,6 +55,7 @@ export const userConfigsListened: string[] = [ appendPrefixToCommand(configKeys.notebookAddModules), appendPrefixToCommand(configKeys.notebookEnablePreview), appendPrefixToCommand(configKeys.notebookImplicitImports), + appendPrefixToCommand(configKeys.notebookProjectMapping), builtInConfigKeys.vscodeTheme, ]; diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts index 4508043..6d02737 100644 --- a/vscode/src/extension.ts +++ b/vscode/src/extension.ts @@ -19,6 +19,8 @@ * under the License. */ +'use strict'; + import { ExtensionContext } from 'vscode'; import * as launchConfigurations from './launchConfigurations'; import { extConstants } from './constants'; diff --git a/vscode/src/lsp/listeners/requests/handlers.ts b/vscode/src/lsp/listeners/requests/handlers.ts index d29d3f5..3ccbf79 100644 --- a/vscode/src/lsp/listeners/requests/handlers.ts +++ b/vscode/src/lsp/listeners/requests/handlers.ts @@ -15,7 +15,7 @@ */ import { QuickPickItem, Uri, window, workspace, WorkspaceConfiguration } from "vscode"; import { notificationOrRequestListenerType } from "../../types"; -import { ExecInHtmlPageRequest, HtmlPageRequest, InputBoxRequest, InputBoxStep, MutliStepInputRequest, NotebookCellStateRequest, NotebookCellStateRequestParams, NotebookCellStateResponse, QuickPickRequest, QuickPickStep, SaveDocumentRequestParams, SaveDocumentsRequest, ShowInputBoxParams, TextEditorDecorationCreateRequest, UpdateConfigurationRequest } from "../../protocol"; +import { ExecInHtmlPageRequest, HtmlPageRequest, InputBoxRequest, InputBoxStep, MutliStepInputRequest, NotebookCellStateRequest, NotebookCellStateRequestParams, NotebookCellStateResponse, QuickPickRequest, QuickPickStep, SaveDocumentRequestParams, SaveDocumentsRequest, ShowInputBoxParams, ShowQuickPickParams, TextEditorDecorationCreateRequest, UpdateConfigurationRequest } from "../../protocol"; import { InputStep, MultiStepInput } from "../../../utils"; import { runConfigurationUpdateAll } from "../../../views/runConfiguration"; import { isError } from "../../../utils"; @@ -109,7 +109,7 @@ const updateConfigRequestHandler = async (param: any) => { } } -const quickPickRequestHandler = async (param: any) => { +const quickPickRequestHandler = async (param: ShowQuickPickParams) => { const selected = await window.showQuickPick(param.items, { title: param.title, placeHolder: param.placeHolder, canPickMany: param.canPickMany, ignoreFocusOut: true }); return selected ? Array.isArray(selected) ? selected : [selected] : undefined; } diff --git a/vscode/src/notebooks/codeCellExecution.ts b/vscode/src/notebooks/codeCellExecution.ts index 09b4781..a96a309 100644 --- a/vscode/src/notebooks/codeCellExecution.ts +++ b/vscode/src/notebooks/codeCellExecution.ts @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * 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. + */ + import { commands, NotebookCell, NotebookCellExecution, NotebookCellOutput, NotebookController } from "vscode"; import { LOGGER } from "../logger"; import { NotebookCellExecutionResult } from "../lsp/protocol"; @@ -12,6 +33,7 @@ export class CodeCellExecution { private isExecutionStarted: boolean = false; private mimeMap = new Map(); private output: NotebookCellOutput = new NotebookCellOutput([]); + private isError: boolean = false; constructor( private controllerId: string, @@ -24,7 +46,7 @@ export class CodeCellExecution { LOGGER.warn(`Received undefined controller ${this.getCellId()}`); return; } - LOGGER.log(`${this.getCellId()} queued for execution`); + LOGGER.debug(`${this.getCellId()} queued for execution`); this.controller = controller; } @@ -49,6 +71,7 @@ export class CodeCellExecution { } if (err) { + this.isError = true; const { data } = err; const newData = new TextDecoder().decode(Uint8Array.from(data)); this.handleOutput(newData, mimeTypes.ERROR, true); @@ -60,6 +83,7 @@ export class CodeCellExecution { } if (errorDiagnostics) { + this.isError = true; errorDiagnostics.forEach(diag => { this.handleOutput(diag + "\n", mimeTypes.ERROR, true); }); @@ -82,12 +106,13 @@ export class CodeCellExecution { } public executionCompleted = (status: boolean) => { + const finalExecStatus = status && !this.isError; if (this.isExecutionStarted) { - status ? - LOGGER.log(`${this.getCellId()} successfully executed`) + status && !this.isError ? + LOGGER.debug(`${this.getCellId()} successfully executed`) : LOGGER.error(`${this.getCellId()} failed while executing`); - this.execution!.end(status, Date.now()); + this.execution!.end(finalExecStatus, Date.now()); } } diff --git a/vscode/src/notebooks/constants.ts b/vscode/src/notebooks/constants.ts index 98083a7..6c280e6 100644 --- a/vscode/src/notebooks/constants.ts +++ b/vscode/src/notebooks/constants.ts @@ -1,3 +1,23 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * + * 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. + */ export namespace ijnbConstants { export const NOTEBOOK_TYPE = 'ijnb-notebook'; diff --git a/vscode/src/notebooks/notebook.ts b/vscode/src/notebooks/notebook.ts index d114e59..d0f9830 100644 --- a/vscode/src/notebooks/notebook.ts +++ b/vscode/src/notebooks/notebook.ts @@ -22,8 +22,9 @@ import * as vscode from 'vscode'; import { ICell, INotebook } from './types'; import { serializeCell } from './utils'; -import Ajv = require('ajv'); +import Ajv from "ajv"; import schema = require('./nbformat.v4.d7.schema.json'); +import { LOGGER } from '../logger'; export class NotebookVersionInfo { static readonly NBFORMAT = 4; @@ -35,7 +36,10 @@ export class Notebook { readonly nbformat_minor: number; readonly metadata: { language_info: { name: string } }; readonly cells: ICell[]; - static ajv = new Ajv(); + static ajv = new Ajv({ + allErrors: true, + strict: false + }); static validateFn = this.ajv.compile(schema); constructor(cells: ICell[], language: string = 'java') { @@ -81,10 +85,10 @@ export class Notebook { static assertValidNotebookJson(notebook: INotebook) { if (!Notebook.validateFn(notebook)) { const errors = (Notebook.validateFn.errors || []) - .map(e => `${e.dataPath || '/'} ${e.message}`) + .map(e => `${e.schemaPath || '/'} ${e.message}`) .join('\n'); throw new Error(`Notebook JSON validation failed:\n${errors}`); } - console.log("Notebook successfully validated."); + LOGGER.debug("Notebook successfully validated."); } } diff --git a/vscode/src/notebooks/types.ts b/vscode/src/notebooks/types.ts index ec6d061..92fa6a6 100644 --- a/vscode/src/notebooks/types.ts +++ b/vscode/src/notebooks/types.ts @@ -19,6 +19,8 @@ * under the License. */ +import { Uri } from "vscode"; + export interface INotebook { nbformat: number; nbformat_minor: number; @@ -102,4 +104,12 @@ export interface IMimeBundle { export interface IMetadata { [key: string]: any; +} + +export interface INotebookToolbar { + ui: boolean; + source: 'notebookToolbar'; + notebookEditor: { + notebookUri: Uri + } } \ No newline at end of file diff --git a/vscode/src/notebooks/utils.ts b/vscode/src/notebooks/utils.ts index 69b34bf..ceeb0ba 100644 --- a/vscode/src/notebooks/utils.ts +++ b/vscode/src/notebooks/utils.ts @@ -35,6 +35,7 @@ import { isString } from '../utils'; import { mimeTypes } from './constants'; import { MimeTypeHandler } from './mimeTypeHandler'; import { ExecutionSummary } from './executionSummary'; +import { LOGGER } from '../logger'; export function base64ToUint8Array(base64: string): Uint8Array { @@ -104,7 +105,7 @@ export function parseCell(cell: ICell): vscode.NotebookCellData { } } } - if (cell.id) console.log(`${cell.id.slice(0, 5)} Successfully parsed`); + if (cell.id) LOGGER.debug(`${cell.id.slice(0, 5)} Successfully parsed`); return cellData; } @@ -187,7 +188,7 @@ export function serializeCell(cell: vscode.NotebookCellData): ICell { execution_count: executionCount, outputs, }; - if (codeCell.id) console.log(`${codeCell.id.slice(0, 5)} Successfully serialized code cell`); + if (codeCell.id) LOGGER.debug(`${codeCell.id.slice(0, 5)} Successfully serialized code cell`); return codeCell; } const mdCell: IMarkdownCell = { From 93ba88ccf4482f3e30e3d473b011f6083f2f06c2 Mon Sep 17 00:00:00 2001 From: Achal Talati Date: Tue, 23 Sep 2025 03:30:45 +0530 Subject: [PATCH 8/9] Improvements in notebook configuration, stability and performance - Addressed review comments - Fixed some labels - Improvements to NotebookUtils for conversions between line-based positions and string offsets - Avoiding unnecessary string split and joins - Added unit tests - Notebook configurations related changes 1. Made classpath,modulepath and add modules configs as array type on frontend 2. Corresponding changes on backend to process the array 3. Updated notebook configs unit tests - Add enable-preview flag from the project context if not configured - Addressed minor gaps in notebook cell execution - Correctly printing the runtime errors stacktrace - Handling null output stream callback - Performing lineEnding normalization only where required in cell setContent. - Minor performance improvements Co-authored-by: Achal Talati Co-authored-by: Shivam Madan Co-authored-by: Siddharth Srinivasan --- THIRD_PARTY_LICENSES.txt | 5 +- build.xml | 16 +- nbcode/notebooks/nbproject/project.properties | 7 +- nbcode/notebooks/nbproject/project.xml | 17 +- .../nbcode/java/notebook/CellState.java | 28 ++- .../java/notebook/CodeCompletionProvider.java | 38 ++-- .../nbcode/java/notebook/CodeEval.java | 169 ++++++++++++------ .../java/notebook/CustomInputStream.java | 20 ++- .../java/notebook/JshellStreamsHandler.java | 36 ++-- .../nbcode/java/notebook/NotebookConfigs.java | 64 ++++--- .../NotebookDocumentServiceHandlerImpl.java | 42 ++++- .../NotebookDocumentStateManager.java | 144 ++++++++------- .../java/notebook/NotebookSessionManager.java | 32 ++-- .../nbcode/java/notebook/NotebookUtils.java | 155 +++++++++++----- .../java/notebook/StreamingOutputStream.java | 32 ++-- .../nbcode/java/project/CommandHandler.java | 35 ++-- .../project/ProjectConfigurationUtils.java | 72 +++++++- .../nbcode/java/project/ProjectContext.java | 18 +- .../ProjectModulePathConfigurationUtils.java | 4 +- .../nbcode/java/notebook/CellStateTest.java | 1 - .../java/notebook/CustomInputStreamTest.java | 13 +- .../nbcode/java/notebook/MockNbClient.java | 48 ++--- .../java/notebook/NotebookConfigsTest.java | 26 ++- .../NotebookDocumentStateManagerTest.java | 12 +- .../java/notebook/NotebookUtilsTest.java | 122 +++++++++++++ patches/java-notebooks.diff | 69 +------ patches/updated-show-input-params.diff | 67 +++++++ vscode/l10n/bundle.l10n.en.json | 36 ++-- vscode/l10n/bundle.l10n.ja.json | 36 ++-- vscode/l10n/bundle.l10n.zh-cn.json | 36 ++-- vscode/package.json | 24 ++- vscode/package.nls.ja.json | 13 +- vscode/package.nls.json | 13 +- vscode/package.nls.zh-cn.json | 13 +- vscode/src/commands/notebook.ts | 21 ++- vscode/src/configurations/handlers.ts | 3 - vscode/src/lsp/listeners/requests/handlers.ts | 2 +- vscode/src/lsp/protocol.ts | 8 +- vscode/src/notebooks/codeCellExecution.ts | 9 +- vscode/src/notebooks/executionSummary.ts | 2 +- vscode/src/notebooks/kernel.ts | 6 +- vscode/src/notebooks/mimeTypeHandler.ts | 5 +- vscode/src/notebooks/notebook.ts | 32 ++-- vscode/src/notebooks/register.ts | 2 +- vscode/src/notebooks/serializer.ts | 26 +-- vscode/src/notebooks/types.ts | 2 +- vscode/src/notebooks/utils.ts | 11 +- .../notebooks/mimeTypeHandler.unit.test.ts | 9 +- .../workspaceChange.event.unit.test.ts | 4 +- 49 files changed, 1054 insertions(+), 551 deletions(-) create mode 100644 nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtilsTest.java create mode 100644 patches/updated-show-input-params.diff diff --git a/THIRD_PARTY_LICENSES.txt b/THIRD_PARTY_LICENSES.txt index 5f27e0a..c9442ba 100644 --- a/THIRD_PARTY_LICENSES.txt +++ b/THIRD_PARTY_LICENSES.txt @@ -9835,8 +9835,11 @@ SOFTWARE. ------------------ END OF DEPENDENCY LICENSE -------------------- -Dependancy: nbformat +Dependency: nbformat.v4.5.schema.json ==================== +Accessible at: vscode/src/notebooks/nbformat.v4.5.d7.schema.json +Modified from source at: https://github.com/jupyter/nbformat/blob/main/nbformat/v4/nbformat.v4.5.schema.json +License: BSD 3-Clause License ------------------ START OF DEPENDENCY LICENSE -------------------- BSD 3-Clause License diff --git a/build.xml b/build.xml index 4c5bd3e..5eececd 100644 --- a/build.xml +++ b/build.xml @@ -79,8 +79,9 @@ patches/dev-dependency-licenses.diff patches/nb-telemetry.diff patches/change-method-parameters-refactoring-qualified-names.diff - patches/upgrade-lsp4j.diff - patches/java-notebooks.diff + patches/upgrade-lsp4j.diff + patches/updated-show-input-params.diff + patches/java-notebooks.diff @@ -289,8 +290,17 @@ + + + + + + + + + - + diff --git a/nbcode/notebooks/nbproject/project.properties b/nbcode/notebooks/nbproject/project.properties index 787b525..8b8f761 100644 --- a/nbcode/notebooks/nbproject/project.properties +++ b/nbcode/notebooks/nbproject/project.properties @@ -15,12 +15,7 @@ # javac.source=1.8 requires.nb.javac=true -javac.compilerargs=-Xlint -Xlint:-serial -test.run.args=--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ - --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED \ - --add-exports=jdk.compiler/com.sun.tools.javac.resources=ALL-UNNAMED \ - --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \ - +javac.compilerargs=-Xlint -Xlint:-serial test.unit.lib.cp= test.unit.run.cp.extra= license.file=../../LICENSE.txt diff --git a/nbcode/notebooks/nbproject/project.xml b/nbcode/notebooks/nbproject/project.xml index 2082093..fe98a92 100644 --- a/nbcode/notebooks/nbproject/project.xml +++ b/nbcode/notebooks/nbproject/project.xml @@ -29,6 +29,15 @@ 2.11.0 + + org.netbeans.api.annotations.common + + + + 1 + 1.57 + + org.netbeans.api.java @@ -153,10 +162,10 @@ - - org.netbeans.modules.java.lsp.server - - + + org.netbeans.modules.java.lsp.server + + diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CellState.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CellState.java index 4c04975..33e8f75 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CellState.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CellState.java @@ -18,6 +18,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; import java.util.logging.Logger; import org.eclipse.lsp4j.ExecutionSummary; import org.eclipse.lsp4j.NotebookCell; @@ -82,7 +83,6 @@ public String getNotebookUri() { } public void setContent(String newContent, int newVersion) throws InterruptedException, ExecutionException { - String normalizedContent = NotebookUtils.normalizeLineEndings(newContent); VersionAwareContent currentContent = content.get(); if (currentContent.getVersion() != newVersion - 1) { @@ -99,16 +99,17 @@ public void setContent(String newContent, int newVersion) throws InterruptedExce int receivedVersion = newCellState.getVersion(); if (receivedVersion > currentContent.getVersion()) { - VersionAwareContent newVersionContent = new VersionAwareContent(newCellState.getText(), receivedVersion); - content.set(newVersionContent); + VersionAwareContent newVersionContent = new VersionAwareContent(NotebookUtils.normalizeLineEndings(newCellState.getText()), receivedVersion); + content.updateAndGet(current -> current != currentContent && receivedVersion <= current.getVersion() ? current : newVersionContent); } else { - throw new IllegalStateException("Version mismatch: Received version to be greater than current version, received version: " + (receivedVersion) + ", current version: " + currentContent.getVersion()); + LOG.log(Level.WARNING, "Version mismatch: Received version to be greater than current version, received version: {0}, current version: {1}", new Object[]{receivedVersion, currentContent.getVersion()}); } } else { - VersionAwareContent newVersionContent = new VersionAwareContent(normalizedContent, newVersion); + // newContent is already normalized during applyChanges + VersionAwareContent newVersionContent = new VersionAwareContent(newContent, newVersion); if (!content.compareAndSet(currentContent, newVersionContent)) { - throw new IllegalStateException("Concurrent modification detected. Version expected: " + (newVersion - 1) + ", current: " + content.get().getVersion()); + LOG.log(Level.WARNING, "Concurrent modification detected. Version expected: {0}, current: {1}", new Object[]{newVersion - 1, content.get().getVersion()}); } } } @@ -122,7 +123,7 @@ public void requestContentAndSet() throws InterruptedException, ExecutionExcepti if (newCellState.getVersion() <= 0) { throw new IllegalStateException("Received incorrect version number: " + newCellState.getVersion()); } - VersionAwareContent newVersionContent = new VersionAwareContent(newCellState.getText(), newCellState.getVersion()); + VersionAwareContent newVersionContent = new VersionAwareContent(NotebookUtils.normalizeLineEndings(newCellState.getText()), newCellState.getVersion()); content.set(newVersionContent); } @@ -151,8 +152,8 @@ protected VersionAwareContent getVersionAwareContent() { protected class VersionAwareContent { - private String content; - private int version; + private final String content; + private final int version; public VersionAwareContent(String content, int version) { this.content = content; @@ -163,17 +164,8 @@ public String getContent() { return content; } - public void setContent(String content) { - this.content = content; - } - public int getVersion() { return version; } - - public void setVersion(int version) { - this.version = version; - } - } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeCompletionProvider.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeCompletionProvider.java index a0f86a5..13cf455 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeCompletionProvider.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeCompletionProvider.java @@ -16,9 +16,8 @@ package org.netbeans.modules.nbcode.java.notebook; import java.util.ArrayList; -import java.util.HashMap; +import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.logging.Level; import java.util.logging.Logger; @@ -63,34 +62,27 @@ public CompletableFuture, CompletionList>> getCodeCo } SourceCodeAnalysis sourceCodeAnalysis = instance.sourceCodeAnalysis(); - String textToComplete = getTextToComplete( + List suggestions = getSuggestions( params.getTextDocument().getUri(), params.getPosition(), state, sourceCodeAnalysis ); - List suggestions = sourceCodeAnalysis.completionSuggestions( - textToComplete, - textToComplete.length(), - new int[1] - ); - List completionItems = new ArrayList<>(); - Map visited = new HashMap<>(); + HashSet visited = new HashSet<>(); for (Suggestion suggestion : suggestions) { String continuation = suggestion.continuation(); - if (!visited.containsKey(continuation)) { + if (visited.add(continuation)) { completionItems.add(createCompletionItem(continuation)); - visited.put(continuation, Boolean.TRUE); } } return Either., CompletionList>forLeft(completionItems); } catch (Exception e) { - LOG.log(Level.WARNING, "Error getting code completions: {0}", e.getMessage()); + LOG.log(Level.WARNING, "Error getting code completions: {0}", e.toString()); return Either., CompletionList>forLeft(new ArrayList<>()); } }); @@ -103,14 +95,28 @@ private CompletionItem createCompletionItem(String label) { return item; } - private String getTextToComplete(String uri, Position position, NotebookDocumentStateManager state, SourceCodeAnalysis sourceCodeAnalysis) { + private List getSuggestions(String uri, Position position, NotebookDocumentStateManager state, SourceCodeAnalysis sourceCodeAnalysis) { CellState cellState = state.getCell(uri); String content = cellState.getContent(); int cursorOffset = NotebookUtils.getOffset(content, position); - + int[] anchor = new int[1]; String offsetText = content.substring(0, cursorOffset); List snippets = NotebookUtils.getCodeSnippets(sourceCodeAnalysis, offsetText); - return snippets.isEmpty() ? "" : snippets.getLast(); + String lastSnippet = snippets.isEmpty() ? "" : snippets.get(snippets.size()-1); + List suggestions = new ArrayList<>(); + suggestions.addAll(sourceCodeAnalysis.completionSuggestions( + lastSnippet, + lastSnippet.length(), + anchor + )); + if (snippets.size() > 1) { + suggestions.addAll(sourceCodeAnalysis.completionSuggestions( + offsetText, + offsetText.length(), + anchor + )); + } + return suggestions; } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeEval.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeEval.java index e2c7346..adc659d 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeEval.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeEval.java @@ -15,9 +15,10 @@ */ package org.netbeans.modules.nbcode.java.notebook; -import jdk.jshell.JShell; -import jdk.jshell.SnippetEvent; +import java.io.PrintWriter; +import java.io.Writer; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -28,22 +29,32 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import jdk.jshell.Diag; +import jdk.jshell.EvalException; +import jdk.jshell.JShell; +import jdk.jshell.JShellException; import jdk.jshell.SourceCodeAnalysis; +import jdk.jshell.SnippetEvent; import org.netbeans.modules.java.lsp.server.notebook.CellExecutionResult; import org.netbeans.modules.java.lsp.server.notebook.NotebookCellExecutionProgressResultParams; +import org.netbeans.modules.java.lsp.server.notebook.NotebookCellExecutionProgressResultParams.Builder; import org.netbeans.modules.java.lsp.server.notebook.NotebookCellExecutionProgressResultParams.EXECUTION_STATUS; import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient; +import org.openide.util.NbBundle; import org.openide.util.RequestProcessor; /** * * @author atalati */ +@NbBundle.Messages({ + "MSG_InterruptCodeCellExecSuccess=Code execution stopped successfully", + "MSG_InterruptCodeCellInfo=Code execution was interrupted" +}) public class CodeEval { private static final Logger LOG = Logger.getLogger(CodeEval.class.getName()); - private static final String CODE_EXEC_INTERRUPT_SUCCESS_MESSAGE = "Code execution stopped successfully"; - private static final String CODE_EXEC_INTERRUPTED_MESSAGE = "Code execution was interrupted"; + private static final String CODE_EXEC_INTERRUPT_SUCCESS_MESSAGE = Bundle.MSG_InterruptCodeCellExecSuccess(); + private static final String CODE_EXEC_INTERRUPTED_MESSAGE = Bundle.MSG_InterruptCodeCellInfo(); private static final Pattern LINEBREAK = Pattern.compile("\\R"); private final Map codeExecMap = new ConcurrentHashMap<>(); @@ -70,14 +81,14 @@ private static class Singleton { public String interrupt(List arguments) { if (arguments == null) { LOG.warning("Received null in interrupt execution request"); - return "Arguments list is null"; + throw new IllegalArgumentException("Recevied null arguments"); } String notebookId = NotebookUtils.getArgument(arguments, 0, String.class); if (notebookId == null) { LOG.warning("Received empty notebookId in interrupt execution request"); - return "Empty notebookId received"; + throw new IllegalArgumentException("Empty notebookId received"); } return interruptCodeExecution(notebookId); @@ -150,13 +161,12 @@ public CompletableFuture evaluate(List arguments) { private void codeEvalTaskRunnable(CompletableFuture future, JShell jshell, String notebookId, String cellId, String sourceCode) { try { - activeCellExecutionMapping.put(notebookId, cellId); - sendNotification(notebookId, EXECUTION_STATUS.EXECUTING); - if (jshell == null) { - future.completeExceptionally(new ExceptionInInitializerError("notebook session not found or closed")); + future.completeExceptionally(new IllegalStateException("notebook session not found or closed")); return; } + activeCellExecutionMapping.put(notebookId, cellId); + sendNotification(notebookId, EXECUTION_STATUS.EXECUTING); runCode(jshell, sourceCode, notebookId); flushStreams(notebookId); @@ -220,25 +230,103 @@ private List getCompilationErrors(JShell jshell, SnippetEvent event) { private List getRuntimeErrors(SnippetEvent event) { List runtimeErrors = new ArrayList<>(); - if (event.exception() != null) { - if (!event.exception().getMessage().isBlank()) { - runtimeErrors.add(event.exception().getMessage()); + JShellException jshellException = event.exception(); + if (jshellException != null) { + String msg = jshellException.getMessage(); + boolean msgAdded = false; + if (msg != null && !msg.isBlank()) { + runtimeErrors.add(msg); + msgAdded = true; } - if (!event.exception().fillInStackTrace().toString().isBlank()) { - runtimeErrors.add(event.exception().fillInStackTrace().toString()); + // Getting the exception stacktrace/details: + // stacktrace for EvalException provides the exception that the snippet code generated + // stacktrace for non-EvalException is not helpful as it is only internal details + String stacktrace = jshellException instanceof EvalException + ? getStackTrace((EvalException) jshellException) + : msgAdded ? "" : jshellException.toString(); + if (!stacktrace.isBlank()) { + runtimeErrors.add(stacktrace); } } return runtimeErrors; } - private List getSnippetValue(SnippetEvent event) { - List snippetValues = new ArrayList<>(); - if (event.value() != null) { - snippetValues.add(event.value()); + private String getStackTrace(EvalException exception) { + return printStackTrace(null, exception).toString(); + } + + private StringBuilder printStackTrace(StringBuilder output, EvalException exception) { + StringBuilder sb = printStackTrace(output, (Throwable) exception); + + return correctExceptionName(sb, 0, exception); + } + + private StringBuilder correctExceptionName(StringBuilder output, int startIndex, EvalException exception) { + // EvalException has the peculiarity that it replaces the actual cause, + // while retaining the name, stacktrace and subsequent causes. + // This is unhelpful since it hides the actual exception in the output. + // Note: jdk.internal.jshell.tool.JShellTool.displayEvalException() uses + // elaborate code to perform the user-friendly printing on console. + String actualName = exception.getExceptionClassName(); + String wrapperName = exception.getClass().getName(); + if (actualName != null && !wrapperName.equals(actualName)) { + int foundAt = output.indexOf(wrapperName, startIndex); + if (foundAt >= 0) { + output.replace(foundAt, foundAt + wrapperName.length(), actualName); + foundAt += actualName.length(); + if (foundAt < output.length()) { + Throwable cause = exception; + Throwable cycleDetector = cause; + do { + cause = cause.getCause(); + + if (cycleDetector != null) { + // Check for loops in cause using a tortoise-hare detector. + cycleDetector = cycleDetector.getCause(); + if (cycleDetector != null) { + cycleDetector = cycleDetector.getCause(); + if (cycleDetector == cause) { + cause = null; // Cycle has been detected; break + } + } + } + } while (cause != null && !(cause instanceof EvalException)); + if (cause != null) { + correctExceptionName(output, foundAt, (EvalException) cause); + } + } + } } + return output; + } + + private StringBuilder printStackTrace(StringBuilder output, Throwable exception) { + if (exception == null) { + return output != null ? output : new StringBuilder(0); + } + StringBuilder sb = output != null ? output : new StringBuilder(); + PrintWriter stackWriter = new PrintWriter(new Writer() { + @Override + public void write(char[] cbuf, int off, int len) { + sb.append(cbuf, off, len); + } + + @Override + public void flush() { + } + + @Override + public void close() { + } + }); + exception.printStackTrace(stackWriter); + + return sb; + } - return snippetValues; + private List getSnippetValue(SnippetEvent event) { + return event.value() != null ? List.of(event.value()) : Collections.emptyList(); } private void flushStreams(String notebookId) { @@ -248,11 +336,11 @@ private void flushStreams(String notebookId) { } } - // This method is directly taken from JShell tool implementation in jdk with some minor modifications + // Note: This method is taken from jdk.internal.jshell.tool.JShellTool with some simplifications private List displayableDiagnostic(String source, Diag diag) { List toDisplay = new ArrayList<>(); - for (String line : diag.getMessage(null).split("\\r?\\n")) { + for (String line : diag.getMessage(null).split("\\R")) { if (!line.trim().startsWith("location:")) { toDisplay.add(line); } @@ -335,42 +423,19 @@ private void sendNotification(String notebookId, String cellId, byte[] msg, List } } - NotebookCellExecutionProgressResultParams params; + Builder b = NotebookCellExecutionProgressResultParams.builder(notebookId, cellId).status(status); if (msg == null) { if (diags != null) { - params = NotebookCellExecutionProgressResultParams - .builder(notebookId, cellId) - .diagnostics(diags) - .status(status) - .build(); + b.diagnostics(diags); } else if (errorDiags != null) { - params = NotebookCellExecutionProgressResultParams - .builder(notebookId, cellId) - .errorDiagnostics(errorDiags) - .status(status) - .build(); - } else { - params = NotebookCellExecutionProgressResultParams - .builder(notebookId, cellId) - .status(status) - .build(); + b.errorDiagnostics(errorDiags); } } else { - if (isError) { - params = NotebookCellExecutionProgressResultParams - .builder(notebookId, cellId) - .status(status) - .errorStream(CellExecutionResult.text(msg)) - .build(); - } else { - params = NotebookCellExecutionProgressResultParams - .builder(notebookId, cellId) - .status(status) - .outputStream(CellExecutionResult.text(msg)) - .build(); - } + b = isError + ? b.errorStream(CellExecutionResult.text(msg)) + : b.outputStream(CellExecutionResult.text(msg)); } - + NotebookCellExecutionProgressResultParams params = b.build(); NbCodeLanguageClient client = LanguageClientInstance.getInstance().getClient(); if (client != null) { client.notifyNotebookCellExecutionProgress(params); diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CustomInputStream.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CustomInputStream.java index 967aba7..031f5f0 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CustomInputStream.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CustomInputStream.java @@ -26,17 +26,21 @@ import java.util.logging.Logger; import org.netbeans.modules.java.lsp.server.input.ShowInputBoxParams; import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient; +import org.openide.util.NbBundle; /** * * @author atalati */ +@NbBundle.Messages({ + "PROMPT_GetUserInput=Please provide scanner input here" +}) public class CustomInputStream extends InputStream { private static final Logger LOG = Logger.getLogger(CustomInputStream.class.getName()); private ByteArrayInputStream currentStream; - WeakReference client; - private static final String USER_PROMPT_REQUEST = "Please provide scanner input here"; + private final WeakReference client; + private static final String USER_PROMPT_REQUEST = Bundle.PROMPT_GetUserInput(); public CustomInputStream(NbCodeLanguageClient client) { this.client = new WeakReference<>(client); @@ -45,13 +49,13 @@ public CustomInputStream(NbCodeLanguageClient client) { @Override public synchronized int read(byte[] b, int off, int len) throws IOException { try { - if (client == null || client.get() == null) { - LOG.log(Level.WARNING, "client is null"); - return -1; - } - if (currentStream == null || currentStream.available() == 0) { - CompletableFuture future = client.get().showInputBox(new ShowInputBoxParams(USER_PROMPT_REQUEST, "", true)); + NbCodeLanguageClient client = this.client.get(); + if (client == null) { + LOG.log(Level.WARNING, "client is null"); + return -1; + } + CompletableFuture future = client.showInputBox(new ShowInputBoxParams(USER_PROMPT_REQUEST, "", true)); String userInput = future.get(); if (userInput == null) { diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/JshellStreamsHandler.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/JshellStreamsHandler.java index cc8d1c1..16f8c3e 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/JshellStreamsHandler.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/JshellStreamsHandler.java @@ -61,14 +61,6 @@ private Consumer createCallback(BiConsumer callback) { return callback != null ? output -> callback.accept(notebookId, output) : null; } - public void setOutStreamCallback(BiConsumer callback) { - outStream.setCallback(createCallback(callback)); - } - - public void setErrStreamCallback(BiConsumer callback) { - errStream.setCallback(createCallback(callback)); - } - public PrintStream getPrintOutStream() { return printOutStream; } @@ -88,9 +80,13 @@ public String getNotebookId() { public void flushOutputStreams() { try { outStream.flush(); + } catch (IOException exception) { + LOG.log(Level.WARNING, "IOException occurred while flushing out stream: {0}", exception.toString()); + } + try { errStream.flush(); - } catch (IOException ignored) { - // nothing can be done + } catch (IOException exception) { + LOG.log(Level.WARNING, "IOException occurred while flushing error stream: {0}", exception.toString()); } } @@ -98,12 +94,28 @@ public void flushOutputStreams() { public void close() { try { printOutStream.close(); + } catch (Exception exception) { + LOG.log(Level.WARNING, "Exception occurred while closing print out stream: {0}", exception.toString()); + } + try { printErrStream.close(); + } catch (Exception exception) { + LOG.log(Level.WARNING, "Exception occurred while closing print err stream: {0}", exception.toString()); + } + try { outStream.close(); + } catch (IOException exception) { + LOG.log(Level.WARNING, "IOException occurred while closing out stream: {0}", exception.toString()); + } + try { errStream.close(); + } catch (IOException exception) { + LOG.log(Level.WARNING, "IOException occurred while closing error stream: {0}", exception.toString()); + } + try { inputStream.close(); - } catch (IOException ex) { - LOG.log(Level.WARNING, "IOException occurred while closing the streams {0}", ex.getMessage()); + } catch (IOException exception) { + LOG.log(Level.WARNING, "IOException occurred while closing input stream: {0}", exception.toString()); } } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java index 0b027c5..7428080 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java @@ -17,21 +17,22 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; +import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.logging.Logger; import org.eclipse.lsp4j.ConfigurationItem; import org.eclipse.lsp4j.ConfigurationParams; import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient; -import org.openide.util.Exceptions; /** * * @author atalati */ public class NotebookConfigs { + private static final Logger LOG = Logger.getLogger(NotebookConfigs.class.getName()); private static final String[] NOTEBOOK_CONFIG_LABELS = {"notebook.classpath", "notebook.modulepath", @@ -39,13 +40,13 @@ public class NotebookConfigs { "notebook.enablePreview", "notebook.implicitImports", "notebook.projects.mapping"}; - private String classPath = null; - private String modulePath = null; - private String addModules = null; - private boolean enablePreview = false; - private JsonObject notebookProjectMapping = new JsonObject(); - private List implicitImports = null; - private CompletableFuture initialized; + private volatile String classPath = null; + private volatile String modulePath = null; + private volatile String addModules = null; + private volatile boolean enablePreview = false; + private volatile JsonObject notebookProjectMapping = new JsonObject(); + private volatile List implicitImports = null; + private volatile CompletableFuture initialized; public CompletableFuture getInitialized() { return initialized; @@ -91,8 +92,8 @@ private static class Singleton { public void initConfigs() { try { this.initialized = initializeConfigs(); - } catch (InterruptedException | ExecutionException ex) { - Exceptions.printStackTrace(ex); + } catch (Exception ex) { + LOG.log(Level.WARNING, "Exception occurred while init configs for notebooks: {0}", ex.getMessage()); } } @@ -109,31 +110,40 @@ private List getConfigItems() { return items; } - private CompletableFuture initializeConfigs() throws InterruptedException, ExecutionException { + private CompletableFuture initializeConfigs() { NbCodeLanguageClient client = LanguageClientInstance.getInstance().getClient(); if (client != null) { CompletableFuture> configValues = client.configuration(new ConfigurationParams(getConfigItems())); return configValues.thenAccept((c) -> { if (c != null) { - if (c.get(0) != null) { - classPath = ((JsonPrimitive) c.get(0)).getAsString(); + JsonArray classPathConfig = NotebookUtils.getArgument(c, 0, JsonArray.class); + if (classPathConfig != null) { + classPath = String.join(File.pathSeparator,classPathConfig.asList().stream().map((elem) -> elem.getAsString()).toList()); } - if (c.get(1) != null) { - modulePath = ((JsonPrimitive) c.get(1)).getAsString(); + + JsonArray modulePathConfig = NotebookUtils.getArgument(c, 1, JsonArray.class); + if (modulePathConfig != null) { + modulePath = String.join(File.pathSeparator,modulePathConfig.asList().stream().map((elem) -> elem.getAsString()).toList()); } - if (c.get(2) != null) { - addModules = ((JsonPrimitive) c.get(2)).getAsString(); + + JsonArray addModulesConfig = NotebookUtils.getArgument(c, 2, JsonArray.class); + if (addModulesConfig != null) { + addModules = String.join(",",addModulesConfig.asList().stream().map((elem) -> elem.getAsString()).toList()); } - if (c.get(3) != null) { - enablePreview = ((JsonPrimitive) c.get(3)).getAsBoolean(); + + Boolean enablePreviewConfig = NotebookUtils.getArgument(c, 3, Boolean.class); + if (enablePreviewConfig != null) { + enablePreview = enablePreviewConfig; } - if (c.get(4) != null) { - implicitImports = ((JsonArray) c.get(4)).asList().stream().map((elem) -> elem.getAsString()).toList(); - + + JsonArray implicitImportsConfig = NotebookUtils.getArgument(c, 4, JsonArray.class); + if (implicitImportsConfig != null) { + implicitImports = implicitImportsConfig.asList().stream().map((elem) -> elem.getAsString()).toList(); } - if (c.get(5) != null) { - notebookProjectMapping = (JsonObject) c.get(5); + JsonObject notebookProjectMappingConfig = NotebookUtils.getArgument(c, 5, JsonObject.class); + if (notebookProjectMappingConfig != null) { + notebookProjectMapping = notebookProjectMappingConfig; } } }); @@ -148,7 +158,7 @@ public String getJdkVersion() { } public void notebookConfigsChangeListener(JsonObject settings) { - // depends on #8514 PR open in Netbeans + // TODO: Cache configurations using changes done in #8514 PR open in Netbeans } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java index 5d2476d..1d1535c 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java @@ -15,6 +15,7 @@ */ package org.netbeans.modules.nbcode.java.notebook; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -34,9 +35,6 @@ import org.eclipse.lsp4j.CompletionList; import org.eclipse.lsp4j.CompletionParams; import org.eclipse.lsp4j.DefinitionParams; -import org.eclipse.lsp4j.services.LanguageClient; -import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient; -import org.netbeans.modules.java.lsp.server.notebook.NotebookDocumentServiceHandler; import org.eclipse.lsp4j.DidChangeNotebookDocumentParams; import org.eclipse.lsp4j.DidCloseNotebookDocumentParams; import org.eclipse.lsp4j.DidOpenNotebookDocumentParams; @@ -48,6 +46,10 @@ import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.HoverParams; import org.eclipse.lsp4j.ImplementationParams; +import org.eclipse.lsp4j.InlayHint; +import org.eclipse.lsp4j.InlayHintParams; +import org.eclipse.lsp4j.InlineValue; +import org.eclipse.lsp4j.InlineValueParams; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.LocationLink; import org.eclipse.lsp4j.MessageParams; @@ -66,7 +68,11 @@ import org.eclipse.lsp4j.WillSaveTextDocumentParams; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.lsp4j.jsonrpc.messages.Either3; +import org.eclipse.lsp4j.services.LanguageClient; +import org.netbeans.modules.java.lsp.server.notebook.NotebookDocumentServiceHandler; +import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient; import org.netbeans.modules.java.lsp.server.protocol.ShowStatusMessageParams; +import org.openide.util.NbBundle; import org.openide.util.lookup.ServiceProvider; /** @@ -74,6 +80,12 @@ * @author atalati */ @ServiceProvider(service = NotebookDocumentServiceHandler.class) +@NbBundle.Messages({ + "MSG_KernelInitializing=Intializing Java kernel for notebook", + "MSG_KernelInitializeSuccess=Java kernel initialized successfully.", + "# {0} - error message", + "MSG_KernelInitializeFailed=Java kernel initialization for the notebook failed. Error {0}" +}) public class NotebookDocumentServiceHandlerImpl implements NotebookDocumentServiceHandler { private static final Logger LOG = Logger.getLogger(NotebookDocumentServiceHandler.class.getName()); @@ -88,13 +100,13 @@ public void didOpen(DidOpenNotebookDocumentParams params) { if (client == null) { return; } - client.showStatusBarMessage(new ShowStatusMessageParams(MessageType.Info, "Intializing Java kernel for notebook.")); + client.showStatusBarMessage(new ShowStatusMessageParams(MessageType.Info, Bundle.MSG_KernelInitializing())); NotebookSessionManager.getInstance().createSession(params.getNotebookDocument()).whenComplete((JShell jshell, Throwable t) -> { if (t == null) { - client.showStatusBarMessage(new ShowStatusMessageParams(MessageType.Info, "Java kernel initialized successfully")); + client.showStatusBarMessage(new ShowStatusMessageParams(MessageType.Info, Bundle.MSG_KernelInitializeSuccess())); } else { // if package import fails user is not informed ? - client.showMessage(new MessageParams(MessageType.Error, "Error could not initialize Java kernel for the notebook.")); + client.showMessage(new MessageParams(MessageType.Error, Bundle.MSG_KernelInitializeFailed(t.getMessage()))); LOG.log(Level.SEVERE, "Error could not initialize Java kernel for the notebook. : {0}", t.getMessage()); } }); @@ -122,7 +134,14 @@ public void didSave(DidSaveNotebookDocumentParams params) { @Override public void didClose(DidCloseNotebookDocumentParams params) { - NotebookSessionManager.getInstance().closeSession(params.getNotebookDocument().getUri()); + String notebookUri = params.getNotebookDocument().getUri(); + NotebookSessionManager.getInstance().closeSession(notebookUri); + NotebookDocumentStateManager state = notebookStateMap.remove(notebookUri); + if (state != null) { + state.getCellsMap().keySet().forEach(notebookCellMap::remove); + } else { + notebookCellMap.values().removeIf(notebookUri::equals); + } } @Override @@ -231,4 +250,13 @@ public CompletableFuture hover(HoverParams params) { return CompletableFuture.completedFuture(null); } + @Override + public CompletableFuture> inlayHint(InlayHintParams params) { + return CompletableFuture.completedFuture(new ArrayList<>()); + } + + @Override + public CompletableFuture> inlineValue(InlineValueParams params) { + return CompletableFuture.completedFuture(new ArrayList<>()); + } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java index af42261..e382138 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java @@ -16,8 +16,12 @@ package org.netbeans.modules.nbcode.java.notebook; import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.ListIterator; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -26,9 +30,9 @@ import org.eclipse.lsp4j.NotebookDocumentChangeEvent; import org.eclipse.lsp4j.NotebookDocumentChangeEventCellStructure; import org.eclipse.lsp4j.NotebookDocumentChangeEventCellTextContent; -import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.TextDocumentContentChangeEvent; +import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.TextDocumentItem; import org.eclipse.lsp4j.VersionedNotebookDocumentIdentifier; @@ -43,13 +47,26 @@ public class NotebookDocumentStateManager { private final NotebookDocument notebookDoc; private final Map cellsMap = new ConcurrentHashMap<>(); private final List cellsOrder; + private final CellStateCreator cellStateCreator; public NotebookDocumentStateManager(NotebookDocument notebookDoc, List cells) { + this(notebookDoc, cells, null); + } + + public NotebookDocumentStateManager(NotebookDocument notebookDoc, List cells, CellStateCreator cellStateCreator) { + this.cellStateCreator = cellStateCreator != null ? cellStateCreator : CellState::new; this.notebookDoc = notebookDoc; this.cellsOrder = new ArrayList<>(); - for (int i = 0; i < cells.size(); i++) { - addNewCellState(notebookDoc.getCells().get(i), cells.get(i)); - this.cellsOrder.add(cells.get(i).getUri()); + Iterator notebookCellsIterator = notebookDoc.getCells().iterator(); + + for (TextDocumentItem cellItem : cells) { + if (notebookCellsIterator.hasNext()) { + addNewCellState(notebookCellsIterator.next(), cellItem); + this.cellsOrder.add(cellItem.getUri()); + } else { + LOG.log(Level.SEVERE, "Mismatched number of cells and cell items during initialization."); + break; + } } } @@ -83,41 +100,67 @@ private void updateNotebookCellStructure(NotebookDocumentChangeEventCellStructur if (updatedStructure == null) { return; } - // Handle deleted cells - int deletedCells = updatedStructure.getArray().getDeleteCount(); - if (deletedCells > 0 && updatedStructure.getDidClose() != null) { - updatedStructure.getDidClose().forEach(cell -> { - String uri = cell.getUri(); + + Set closedCellUris = new HashSet<>(); + Set openedCellUris = new HashSet<>(); + + if (updatedStructure.getDidClose() != null) { + for (TextDocumentIdentifier closedCell : updatedStructure.getDidClose()) { + String uri = closedCell.getUri(); + closedCellUris.add(uri); CellState removed = cellsMap.remove(uri); cellsNotebookMap.remove(uri); - cellsOrder.remove(uri); if (removed != null) { - LOG.log(Level.FINE, "Removed cell: {0}", uri); + LOG.log(Level.FINE, "Removed cell from map: {0}", uri); } - }); + } } - // Handle added cells - int startIdx = updatedStructure.getArray().getStart(); List cellsItem = updatedStructure.getDidOpen(); List cellsDetail = updatedStructure.getArray().getCells(); - if (cellsItem != null && cellsDetail != null && cellsDetail.size() == cellsItem.size()) { - for (int i = 0; i < cellsDetail.size(); i++) { - addNewCellState(cellsDetail.get(i), cellsItem.get(i)); - cellsNotebookMap.put(cellsItem.get(i).getUri(), notebookDoc.getUri()); - if (startIdx + i <= cellsOrder.size()) { - cellsOrder.add(startIdx + i, cellsItem.get(i).getUri()); - } else { - LOG.warning("unable to add cell in the list of cells"); + if (cellsItem != null && cellsDetail != null) { + Iterator details = cellsDetail.iterator(); + for (TextDocumentItem cellItem: cellsItem) { + String uri = cellItem.getUri(); + openedCellUris.add(uri); + cellsNotebookMap.put(uri, notebookDoc.getUri()); + if (details.hasNext()) { + addNewCellState(details.next(), cellItem); } } - } else { - LOG.severe("cell details is null or array size mismatch is present"); - throw new IllegalStateException("Error while adding cell to the notebook state"); } + synchronized (cellsOrder) { + int startIdx = updatedStructure.getArray().getStart(); + int deleteCount = updatedStructure.getArray().getDeleteCount(); + + ListIterator iterator = cellsOrder.listIterator(startIdx); + + for (int i = 0; i < deleteCount && iterator.hasNext(); i++) { + String removedUri = iterator.next(); + iterator.remove(); + + if (!closedCellUris.contains(removedUri)) { + LOG.log(Level.WARNING, "Removed URI {0} not found in didClose list", removedUri); + } + LOG.log(Level.FINE, "Removed cell from order: {0}", removedUri); + } + + if (cellsItem != null) { + for (TextDocumentItem cellItem : cellsItem) { + String uri = cellItem.getUri(); + iterator.add(uri); + + if (!openedCellUris.contains(uri)) { + LOG.log(Level.WARNING, "Added URI {0} not found in didOpen list", uri); + } + + LOG.log(Level.FINE, "Added cell to order: {0}", uri); + } + } + } } private void updateNotebookCellData(List data) { @@ -156,7 +199,7 @@ private void updateNotebookCellContent(NotebookDocumentChangeEventCellTextConten cellState.setContent(updatedContent, newVersion); LOG.log(Level.FINE, "Updated content for cell: {0}, version: {1}", new Object[]{uri, newVersion}); } catch (Exception e) { - LOG.log(Level.WARNING, "applyContentChanges failed, requesting full content: " + uri, e); + LOG.log(Level.WARNING, "applyContentChanges failed, requesting full content for cell: {0}. Error - {1}", new Object[]{uri, e}); try { cellState.requestContentAndSet(); } catch (Exception ex) { @@ -185,60 +228,33 @@ private String applyContentChanges(String originalContent, List= lines.length - || end.getLine() < 0 || end.getLine() >= lines.length) { - throw new IllegalArgumentException("Invalid range positions"); - } - - StringBuilder result = new StringBuilder(); - - for (int i = 0; i < start.getLine(); i++) { - result.append(lines[i]); - if (i < lines.length - 1) { - result.append("\n"); - } - } - - String startLine = lines[start.getLine()]; - String beforeChange = startLine.substring(0, Math.min(start.getCharacter(), startLine.length())); - result.append(beforeChange); - - result.append(change.getText()); - - String endLine = lines[end.getLine()]; - String afterChange = endLine.substring(Math.min(end.getCharacter(), endLine.length())); - result.append(afterChange); - - for (int i = end.getLine() + 1; i < lines.length; i++) { - result.append("\n").append(lines[i]); - } - - return result.toString(); + return NotebookUtils.applyChange(content, range.getStart(), range.getEnd(), change.getText()); } - // protected methods for ease of unit testing - protected void addNewCellState(NotebookCell cell, TextDocumentItem item) { + + private void addNewCellState(NotebookCell cell, TextDocumentItem item) { if (cell == null || item == null) { LOG.log(Level.WARNING, "Attempted to add null cell or item"); return; } + CellState cellState; try { - CellState cellState = new CellState(cell, item, notebookDoc.getUri()); - cellsMap.put(item.getUri(), cellState); + cellState = cellStateCreator.create(cell, item, notebookDoc.getUri()); LOG.log(Level.FINE, "Added new cell state: {0}", item.getUri()); } catch (Exception e) { LOG.log(Level.SEVERE, "Failed to create cell state for: " + item.getUri(), e); throw new RuntimeException("Failed to create cell state", e); } + cellsMap.put(item.getUri(), cellState); } protected Map getCellsMap() { return cellsMap; } + + // protected methods for ease of unit testing + protected interface CellStateCreator { + CellState create(NotebookCell cell, TextDocumentItem item, String notebookDocUri); + } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java index d1b423c..f4842a0 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java @@ -130,12 +130,7 @@ private List getCompilerOptions(Project prj) { addOption.accept(CLASS_PATH, configs.getClassPath()); addOption.accept(MODULE_PATH, configs.getModulePath()); addOption.accept(ADD_MODULES, configs.getAddModules()); - - if (configs.isEnablePreview()) { - compilerOptions.add(ENABLE_PREVIEW); - compilerOptions.add(SOURCE_FLAG); - compilerOptions.add(configs.getJdkVersion()); - } + boolean enablePreview = configs.isEnablePreview(); if (prj != null) { List projOptions = ProjectConfigurationUtils.compilerOptions(prj); @@ -153,6 +148,13 @@ private List getCompilerOptions(Project prj) { if (checkEmptyString(configs.getAddModules()) && prjConfigMap.containsKey(ADD_MODULES)) { addOption.accept(ADD_MODULES, prjConfigMap.get(ADD_MODULES)); } + enablePreview = enablePreview || projOptions.contains(ENABLE_PREVIEW); + } + + if (enablePreview) { + compilerOptions.add(ENABLE_PREVIEW); + compilerOptions.add(SOURCE_FLAG); + compilerOptions.add(configs.getJdkVersion()); } return compilerOptions; @@ -161,7 +163,7 @@ private List getCompilerOptions(Project prj) { private List getRemoteVmOptions(Project prj) { List remoteOptions = new ArrayList<>(); NotebookConfigs configs = NotebookConfigs.getInstance(); - boolean isEnablePreview = configs.isEnablePreview(); + boolean enablePreview = configs.isEnablePreview(); BiConsumer addOption = (flag, value) -> { if (!checkEmptyString(value)) { @@ -174,10 +176,6 @@ private List getRemoteVmOptions(Project prj) { addOption.accept(MODULE_PATH, configs.getModulePath()); addOption.accept(ADD_MODULES, configs.getAddModules()); - if (isEnablePreview) { - remoteOptions.add(ENABLE_PREVIEW); - } - if (prj != null) { List projOptions = ProjectConfigurationUtils.launchVMOptions(prj); Map prjConfigMap = new HashMap<>(); @@ -194,6 +192,10 @@ private List getRemoteVmOptions(Project prj) { if (checkEmptyString(configs.getAddModules()) && prjConfigMap.containsKey(ADD_MODULES)) { addOption.accept(ADD_MODULES, prjConfigMap.get(ADD_MODULES)); } + enablePreview = enablePreview || projOptions.contains(ENABLE_PREVIEW); + } + if (enablePreview) { + remoteOptions.add(ENABLE_PREVIEW); } return remoteOptions; } @@ -201,12 +203,12 @@ private List getRemoteVmOptions(Project prj) { private void onJshellInit(String notebookId, JShell jshell) { jshell.onShutdown(shell -> closeSession(notebookId)); - List packages = NotebookConfigs.getInstance().getImplicitImports(); - if (packages != null && !packages.isEmpty()) { - packages.forEach(pkg -> CodeEval.getInstance().runCode(jshell, "import " + pkg)); + List elements = NotebookConfigs.getInstance().getImplicitImports(); + if (elements != null && !elements.isEmpty()) { + elements.forEach(el -> CodeEval.getInstance().runCode(jshell, "import " + el)); } else { List.of("java.util", "java.io", "java.math") - .forEach(pkg -> CodeEval.getInstance().runCode(jshell, "import " + pkg + ".*")); + .forEach(el -> CodeEval.getInstance().runCode(jshell, "import " + el + ".*")); } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtils.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtils.java index 48a8263..8ac7d84 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtils.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtils.java @@ -22,40 +22,20 @@ import com.google.gson.JsonPrimitive; import java.util.ArrayList; import java.util.List; +import java.util.regex.Pattern; import jdk.jshell.SourceCodeAnalysis; import org.eclipse.lsp4j.Position; +import org.netbeans.api.annotations.common.NonNull; /** * * @author atalati */ public class NotebookUtils { + private static final Pattern LINE_ENDINGS = Pattern.compile("\\R"); public static String normalizeLineEndings(String text) { - if (text == null) { - return null; - } - - if (text.indexOf('\r') == -1) { - return text; - } - - StringBuilder normalized = new StringBuilder(text.length()); - int len = text.length(); - - for (int i = 0; i < len; i++) { - char c = text.charAt(i); - if (c == '\r') { - if (i + 1 < len && text.charAt(i + 1) == '\n') { - i++; - } - normalized.append('\n'); - } else { - normalized.append(c); - } - } - - return normalized.toString(); + return text == null ? null : LINE_ENDINGS.matcher(text).replaceAll("\n"); } public static int getOffset(String content, Position position) { @@ -63,43 +43,48 @@ public static int getOffset(String content, Position position) { return 0; } - String[] lines = content.split("\n", -1); - int offset = 0; int targetLine = position.getLine(); - int targetChar = position.getCharacter(); - if (targetLine < 0) { return 0; } - if (targetLine >= lines.length) { - return content.length(); - } + int targetChar = Math.max(0, position.getCharacter()); - for (int i = 0; i < targetLine; i++) { - offset += lines[i].length() + 1; - } + int lineStartIndex = -1; + int lineEndIndex = -1; + int line = -1; - String currentLine = lines[targetLine]; - int charPosition = Math.min(Math.max(targetChar, 0), currentLine.length()); - offset += charPosition; + // find the start line in content + do { + lineStartIndex = lineEndIndex + 1; + lineEndIndex = content.indexOf('\n', lineStartIndex); + line++; + } while (line < targetLine && lineEndIndex >= 0); - return Math.min(offset, content.length()); + return line < targetLine ? content.length() : Math.min(lineStartIndex + targetChar, lineEndIndex < 0 ? content.length() : lineEndIndex); } - public static Position getPosition(String content, int offset) { - if (content == null || offset <= 0) { + public static Position getPosition(String text, int offset) { + if (text == null || offset < 0) { return new Position(0, 0); } - int clampedOffset = Math.min(offset, content.length()); - - String textUpToOffset = content.substring(0, clampedOffset); - String[] lines = textUpToOffset.split("\n", -1); - - int line = lines.length - 1; - int character = lines[line].length(); - - return new Position(line, character); + offset = Math.min(offset, text.length()); + int lineStartIndex = -1; + int lineEndIndex = -1; + int line = -1; + + // count line endings in content upto offset + do { + lineStartIndex = lineEndIndex + 1; + lineEndIndex = text.indexOf('\n', lineStartIndex); + line++; + } while (lineEndIndex >= 0 && offset > lineEndIndex); + + if (offset == lineEndIndex) { + return new Position(line + 1, 0); + } else { + return new Position(line, offset - lineStartIndex); + } } public static boolean checkEmptyString(String input) { @@ -158,4 +143,76 @@ public static List getCodeSnippets(SourceCodeAnalysis analysis, String c return codeSnippets; } + + /** + * Applies the supplied change, that is encoded as a diff + * i.e. `{range-start, range-end, text-replacement}`, to the supplied text. + * + * This diff format can encode additions, deletions and modifications at a + * single range in the text. + * + * The supplied text is expected to contain normalized line endings, and, the + * new text adheres to the line ending normalization. + * + * @param text existing text + * @param start start of the range of replaced text + * @param end end of the range of replaced text + * @param replacement text to be added at the supplied position in text + * @throws IllegalArgumentException - when the supplied diff range is invalid + */ + public static String applyChange(@NonNull String text, @NonNull Position start, @NonNull Position end, @NonNull String replacement) throws IllegalArgumentException { + int startLine = start.getLine(); + int startLineOffset = start.getCharacter(); + int endLine = end.getLine(); + int endLineOffset = end.getCharacter(); + + if (startLine < 0 || endLine < startLine || (endLine == startLine && endLineOffset < startLineOffset)) { + throw new IllegalArgumentException("Invalid range positions"); + } + + if (replacement.length() == 0 && startLine == endLine && startLineOffset == endLineOffset) { + return text; // Nothing to be done; no addition nor deletion + } + + final int textLength = text.length(); + + int lineStartIndex = -1; + int lineEndIndex = -1; + int line = -1; + + // find the start line in content + do { + lineStartIndex = lineEndIndex + 1; + lineEndIndex = text.indexOf('\n', lineStartIndex); + line++; + } while (line < startLine && lineEndIndex >= 0); + + if (line < startLine) { + throw new IllegalArgumentException("Invalid range start out of bounds"); + } + + StringBuilder result = new StringBuilder(textLength + replacement.length()); + + // append content before the change + result.append(text, 0, Math.min(lineStartIndex + startLineOffset, lineEndIndex < 0 ? textLength : lineEndIndex)); + // append added text, with line ending normalization + result.append(LINE_ENDINGS.matcher(replacement).replaceAll("\n")); + + // find the end line in content + while (line < endLine && lineEndIndex >= 0) { + lineStartIndex = lineEndIndex + 1; + lineEndIndex = text.indexOf('\n', lineStartIndex); + line++; + } + + if (line < endLine) { + throw new IllegalArgumentException("Invalid range end out of bounds"); + } + + if (lineStartIndex >= 0) { + // append content after the change + result.append(text, Math.min(lineStartIndex + endLineOffset, lineEndIndex < 0 ? textLength : lineEndIndex), textLength); + } + return result.toString(); + } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/StreamingOutputStream.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/StreamingOutputStream.java index 09dbd13..f7df32e 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/StreamingOutputStream.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/StreamingOutputStream.java @@ -30,9 +30,10 @@ public class StreamingOutputStream extends OutputStream { private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - private Consumer callback; + private final Consumer callback; private static final int MAX_BUFFER_SIZE = 1024; private final AtomicBoolean isPeriodicFlushOutputStream; + private final boolean noop; static RequestProcessor getRequestProcessor() { return RPSingleton.instance; @@ -49,52 +50,63 @@ private static final class RPSingleton { } public StreamingOutputStream(Consumer callback) { + this.noop = callback == null; this.callback = callback; - this.isPeriodicFlushOutputStream = new AtomicBoolean(true); + this.isPeriodicFlushOutputStream = new AtomicBoolean(!noop); createAndScheduleTask(); } @Override public synchronized void write(int b) throws IOException { + if (noop) return; buffer.write(b); ifBufferOverflowFlush(); } @Override public synchronized void write(byte[] b, int off, int len) throws IOException { + if (noop) return; + if (len >= MAX_BUFFER_SIZE) { + flushToCallback(); + byte[] chunk = new byte[len]; + System.arraycopy(b, off, chunk, 0, len); + callback.accept(chunk); + return; + } buffer.write(b, off, len); ifBufferOverflowFlush(); } @Override public synchronized void flush() throws IOException { + if (noop) return; flushToCallback(); } @Override public synchronized void write(byte[] b) throws IOException { - buffer.write(b); - ifBufferOverflowFlush(); + if (noop) return; + write(b, 0, b.length); } @Override public synchronized void close() throws IOException { - flushToCallback(); - isPeriodicFlushOutputStream.set(false); + if (!noop) { + flushToCallback(); + isPeriodicFlushOutputStream.set(false); + } super.close(); } - public void setCallback(Consumer cb) { - this.callback = cb; - } - private void ifBufferOverflowFlush() { + if (noop) return; if (buffer.size() > MAX_BUFFER_SIZE) { flushToCallback(); } } private synchronized void flushToCallback() { + if (noop) return; if (buffer.size() > 0) { byte[] output = buffer.toByteArray(); buffer.reset(); diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/CommandHandler.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/CommandHandler.java index 92c85b7..65bd952 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/CommandHandler.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/CommandHandler.java @@ -16,6 +16,7 @@ package org.netbeans.modules.nbcode.java.project; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.logging.Level; @@ -23,6 +24,7 @@ import org.netbeans.api.project.Project; import org.netbeans.modules.nbcode.java.notebook.NotebookSessionManager; import org.netbeans.modules.nbcode.java.notebook.NotebookUtils; +import org.openide.filesystems.FileObject; /** * @@ -32,7 +34,7 @@ public class CommandHandler { private static final Logger LOG = Logger.getLogger(CommandHandler.class.getName()); - public static CompletableFuture> openJshellInProjectContext(List args) { + public static CompletableFuture openJshellInProjectContext(List args) { LOG.log(Level.FINER, "Request received for opening Jshell instance with project context {0}", args); String context = NotebookUtils.getArgument(args, 0, String.class); @@ -48,21 +50,23 @@ public static CompletableFuture> openJshellInProjectContext(List { + return prjFuture.thenCompose(prj -> { + Collection installLocations = ProjectConfigurationUtils.findPlatform(prj).getInstallFolders(); + FileObject installationFolder = installLocations.isEmpty() ? null : installLocations.toArray(new FileObject[0])[0]; + String installationPath = installationFolder != null ? installationFolder.getPath() : null; + if (prj == null) { - return CompletableFuture.completedFuture(new ArrayList<>()); + return CompletableFuture.completedFuture(new OpenJshellResponse(installationPath, new ArrayList<>())); } return ProjectConfigurationUtils.buildProject(prj) .thenCompose(isBuildSuccess -> { if (isBuildSuccess) { - List vmOptions = ProjectConfigurationUtils.launchVMOptions(prj); - LOG.log(Level.INFO, "Opened Jshell instance with project context {0}", context); - return CompletableFuture.completedFuture(vmOptions); + LOG.log(Level.INFO, "Opened Jshell instance with build success status"); } else { - CompletableFuture> failed = new CompletableFuture<>(); - failed.completeExceptionally(new RuntimeException("Build failed")); - return failed; - } + LOG.log(Level.WARNING, "Opened Jshell instance with build failed status"); + } + List vmOptions = ProjectConfigurationUtils.launchVMOptions(prj); + return CompletableFuture.completedFuture(new OpenJshellResponse(installationPath, vmOptions)); }); }); } @@ -74,5 +78,14 @@ public static CompletableFuture getNotebookProjectMappingPath(List prj == null ? null : prj.getProjectDirectory().getPath()); } - + + public static class OpenJshellResponse { + private final List vmOptions; + private final String jdkPath; + + public OpenJshellResponse(String jdkPath, ListvmOptions) { + this.jdkPath = jdkPath; + this.vmOptions = vmOptions; + } + } } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectConfigurationUtils.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectConfigurationUtils.java index cc7dc11..a242d22 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectConfigurationUtils.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectConfigurationUtils.java @@ -23,12 +23,13 @@ import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; -import org.eclipse.lsp4j.jsonrpc.validation.NonNull; +import org.netbeans.api.annotations.common.NonNull; import org.netbeans.api.java.classpath.ClassPath; import org.netbeans.api.java.platform.JavaPlatform; import org.netbeans.api.java.platform.JavaPlatformManager; import org.netbeans.api.java.platform.Specification; import org.netbeans.api.java.project.JavaProjectConstants; +import org.netbeans.api.java.queries.CompilerOptionsQuery; import org.netbeans.api.java.queries.UnitTestForSourceQuery; import org.netbeans.api.project.Project; import org.netbeans.api.project.SourceGroup; @@ -50,6 +51,7 @@ public class ProjectConfigurationUtils { public final static String MODULE_PATH = "--module-path"; public final static String ADD_MODULES = "--add-modules"; public final static String ADD_EXPORTS = "--add-exports"; + public final static String ENABLE_PREVIEW = "--enable-preview"; public static boolean isNonTestRoot(SourceGroup sg) { return UnitTestForSourceQuery.findSources(sg.getRootFolder()).length == 0; @@ -106,7 +108,7 @@ public static List getNonTestRoots(Project project) { public static JavaPlatform findPlatform(Project project) { List ref = findProjectRoots(project); if (ref.isEmpty()) { - return null; + return JavaPlatform.getDefault(); } JavaPlatform platform = findPlatform(ClassPath.getClassPath(ref.get(0), ClassPath.BOOT)); return platform != null ? platform : JavaPlatform.getDefault(); @@ -123,6 +125,50 @@ private static JavaPlatform findPlatform(ClassPath bootCP) { return null; } + static boolean isPreviewEnabled(@NonNull Project project, List sourceRoots) { + boolean previewEnabled = isPreviewEnabledForAnyProjectSourceRoot(project, sourceRoots); + previewEnabled = previewEnabled || isPreviewEnabledForAnyContainedProjects(project); + return previewEnabled; + } + + private static boolean isPreviewEnabledForAnyContainedProjects(@NonNull Project project) { + Set subProjects = ProjectUtils.getContainedProjects(project, false); + if (subProjects != null) { + for (Project subProject : subProjects) { + if (isPreviewEnabledForAnyProjectSourceRoot(subProject, getNonTestRoots(subProject))) { + return true; + } + } + for (Project subProject : subProjects) { + if (isPreviewEnabledForAnyContainedProjects(subProject)) { + return true; + } + } + } + return false; + } + + private static boolean isPreviewEnabledForAnyProjectSourceRoot(@NonNull Project project, List sourceRoots) { + if (sourceRoots == null || sourceRoots.isEmpty()) { + FileObject root = project.getProjectDirectory(); + if (root != null && isPreviewEnabledForSource(root)) { + return true; + } + } else { + for (FileObject root : sourceRoots) { + if (root != null && isPreviewEnabledForSource(root)) { + return true; + } + } + } + return false; + } + + private static boolean isPreviewEnabledForSource(@NonNull FileObject source) { + CompilerOptionsQuery.Result result = CompilerOptionsQuery.getOptions(source); + return result.getArguments().contains(ENABLE_PREVIEW); + } + @NonNull public static List launchVMOptions(Project project) { if (project == null) { @@ -130,14 +176,21 @@ public static List launchVMOptions(Project project) { } boolean isModular = ProjectModulePathConfigurationUtils.isModularProject(project); if (isModular) { - return ProjectModulePathConfigurationUtils.getVmOptions(project); + List vmOptions = ProjectModulePathConfigurationUtils.getVmOptions(project); + if (isPreviewEnabled(project, getNonTestRoots(project))) { + vmOptions.add(ENABLE_PREVIEW); + } + return vmOptions; } List vmOptions = new ArrayList<>(); List roots = getNonTestRoots(project); if (!roots.isEmpty()) { - ClassPath cp = ClassPath.getClassPath(roots.getFirst(), ClassPath.EXECUTE); + ClassPath cp = ClassPath.getClassPath(roots.get(0), ClassPath.EXECUTE); vmOptions.addAll(Arrays.asList(CLASS_PATH, addRoots("", cp))); } + if (isPreviewEnabled(project, roots)) { + vmOptions.add(ENABLE_PREVIEW); + } return vmOptions; } @@ -148,14 +201,21 @@ public static List compilerOptions(Project project) { } boolean isModular = ProjectModulePathConfigurationUtils.isModularProject(project); if (isModular) { - return ProjectModulePathConfigurationUtils.getCompileOptions(project); + List compileOptions = ProjectModulePathConfigurationUtils.getCompileOptions(project); + if (isPreviewEnabled(project, getNonTestRoots(project))) { + compileOptions.add(ENABLE_PREVIEW); + } + return compileOptions; } List compileOptions = new ArrayList<>(); List roots = getNonTestRoots(project); if (!roots.isEmpty()) { - ClassPath cp = ClassPath.getClassPath(roots.getFirst(), ClassPath.COMPILE); + ClassPath cp = ClassPath.getClassPath(roots.get(0), ClassPath.COMPILE); compileOptions.addAll(Arrays.asList(CLASS_PATH, addRoots("", cp))); } + if (isPreviewEnabled(project, roots)) { + compileOptions.add(ENABLE_PREVIEW); + } return compileOptions; } diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectContext.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectContext.java index 3a9bd57..5dbb70a 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectContext.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectContext.java @@ -33,11 +33,19 @@ import org.openide.filesystems.FileObject; import org.openide.util.Exceptions; import org.openide.util.Lookup; +import org.openide.util.NbBundle; /** * * @author atalati */ +@NbBundle.Messages({ + "PROMPT_SelectProjectTitle=Select Project", + "# {0} - project name", + "LBL_CurrentProjectContext=Current project context: {0}", + "MSG_NoProjectFound=No projects found", + "MSG_NoProjectContextFound=No project context" +}) public class ProjectContext { public static Project getProject(String uri) { @@ -67,7 +75,7 @@ public static CompletableFuture getProject(boolean forceShowQuickPick, if (forceShowQuickPick) { return serverState.openedProjects() .thenCompose(prjs -> selectFromMultipleProjects(prjs, prjCxtInfo).thenApply(res - -> res.isEmpty() ? null : res.getFirst())); + -> res.isEmpty() ? null : res.get(0))); } return serverState.openedProjects().thenCompose(prjs -> { switch (prjs.length) { @@ -77,7 +85,7 @@ public static CompletableFuture getProject(boolean forceShowQuickPick, return CompletableFuture.completedFuture(prjs[0]); default: return selectFromMultipleProjects(prjs, prjCxtInfo).thenApply(res - -> res.isEmpty() ? null : res.getFirst()); + -> res.isEmpty() ? null : res.get(0)); } }); } @@ -87,7 +95,7 @@ private static CompletableFuture> selectFromMultipleProjects(Proje if (client == null) { return CompletableFuture.completedFuture(new ArrayList<>()); } - String title = "Select Project"; + String title = Bundle.PROMPT_SelectProjectTitle(); List items = new ArrayList<>(); Map prjMap = new HashMap<>(); for (Project prj : prjs) { @@ -96,8 +104,8 @@ private static CompletableFuture> selectFromMultipleProjects(Proje prjMap.put(displayName, prj); items.add(item); } - String placeholder = defaultPrjSelected != null ? "Current project context: " + defaultPrjSelected.getName() - : items.isEmpty() ? "No projects found" : "No project context"; + String placeholder = defaultPrjSelected != null ? Bundle.LBL_CurrentProjectContext(defaultPrjSelected.getName()) + : items.isEmpty() ? Bundle.MSG_NoProjectFound() : Bundle.MSG_NoProjectFound(); ShowQuickPickParams params = new ShowQuickPickParams(title, placeholder, false, items); return client.showQuickPick(params).thenApply(selected -> { diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectModulePathConfigurationUtils.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectModulePathConfigurationUtils.java index 6aeb4a4..81e50e2 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectModulePathConfigurationUtils.java +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectModulePathConfigurationUtils.java @@ -159,7 +159,7 @@ private static Collection getPackages(FileObject root) { private static ClassPath getRuntimeModulePath(Project project) { List roots = ProjectConfigurationUtils.getNonTestRoots(project); if (!roots.isEmpty()) { - return ClassPath.getClassPath(roots.getFirst(), JavaClassPathConstants.MODULE_EXECUTE_PATH); + return ClassPath.getClassPath(roots.get(0), JavaClassPathConstants.MODULE_EXECUTE_PATH); } return null; } @@ -168,7 +168,7 @@ private static ClassPath getCompileTimeModulePath(Project project) { List roots = ProjectConfigurationUtils.findProjectRoots(project); if (!roots.isEmpty()) { - ClasspathInfo cpi = ClasspathInfo.create(roots.getFirst()); + ClasspathInfo cpi = ClasspathInfo.create(roots.get(0)); return cpi.getClassPath(ClasspathInfo.PathKind.COMPILE); } return null; diff --git a/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/CellStateTest.java b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/CellStateTest.java index 57c4b52..83e46b2 100644 --- a/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/CellStateTest.java +++ b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/CellStateTest.java @@ -141,7 +141,6 @@ public void testSetContentWithVersionGapMismatchOnFetch() throws InterruptedExce staleContentFromServer, staleVersionFromServer ); - assertThrows(IllegalStateException.class, () -> cellState.setContent("a newer content", 3)); assertEquals("Content should remain unchanged after failed fetch", INITIAL_CONTENT, cellState.getContent()); assertEquals("Version should remain unchanged after failed fetch", 1, cellState.getVersionAwareContent().getVersion()); } diff --git a/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/CustomInputStreamTest.java b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/CustomInputStreamTest.java index 957ad6e..23b391d 100644 --- a/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/CustomInputStreamTest.java +++ b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/CustomInputStreamTest.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -31,7 +32,7 @@ public void tearDown() { @Test public void testReadNoClient() throws IOException { - inputStream.client = null; + inputStream = new CustomInputStream(null); assertEquals(-1, inputStream.read()); byte[] buffer = new byte[10]; @@ -75,16 +76,6 @@ public void testMultipleInputs() throws IOException { assertEquals("second" + System.lineSeparator(), readString2); } - @Test - public void testNullFutureInput() throws IOException { - mockClient.setNextInput(null); - - assertEquals(-1, inputStream.read()); - - byte[] buffer = new byte[10]; - assertEquals(-1, inputStream.read(buffer, 0, 10)); - } - @Test public void testEmptyInput() throws IOException { mockClient.setNextInput(""); diff --git a/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/MockNbClient.java b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/MockNbClient.java index 004e5de..01e123f 100644 --- a/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/MockNbClient.java +++ b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/MockNbClient.java @@ -61,122 +61,122 @@ public NbCodeClientCapabilities getNbCodeCapabilities() { @Override public CompletableFuture configurationUpdate(UpdateConfigParams ucp) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public CompletableFuture> configuration(ConfigurationParams configurationParams) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public void showStatusBarMessage(ShowStatusMessageParams ssmp) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public CompletableFuture showHtmlPage(HtmlPageParams hpp) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public CompletableFuture execInHtmlPage(HtmlPageParams hpp) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public CompletableFuture> showQuickPick(ShowQuickPickParams sqpp) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public CompletableFuture showInputBox(ShowInputBoxParams sibp) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public CompletableFuture, String>>> showMultiStepInput(ShowMutliStepInputParams smsip) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public void notifyTestProgress(TestProgressParams tpp) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public CompletableFuture createTextEditorDecoration(DecorationRenderOptions dro) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public void setTextEditorDecoration(SetTextEditorDecorationParams stedp) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public void disposeTextEditorDecoration(String params) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public void notifyNodeChange(NodeChangedParams ncp) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public CompletableFuture requestDocumentSave(SaveDocumentRequestParams sdrp) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public CompletableFuture writeOutput(OutputMessage om) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public CompletableFuture showOutput(String outputName) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public CompletableFuture closeOutput(String outputName) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public CompletableFuture resetOutput(String outputName) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public void telemetryEvent(Object object) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public void publishDiagnostics(PublishDiagnosticsParams diagnostics) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public void showMessage(MessageParams messageParams) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public CompletableFuture showMessageRequest(ShowMessageRequestParams requestParams) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public void logMessage(MessageParams message) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override public void notifyNotebookCellExecutionProgress(NotebookCellExecutionProgressResultParams params) { - throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + throw new UnsupportedOperationException("Not supported yet."); } @Override diff --git a/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigsTest.java b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigsTest.java index 5350c74..2369c42 100644 --- a/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigsTest.java +++ b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigsTest.java @@ -19,6 +19,7 @@ import com.google.gson.JsonObject; import com.google.gson.JsonArray; import com.google.gson.JsonPrimitive; +import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -38,13 +39,14 @@ /* Version 1 21/08/25 +Version 2 25/09/25 Inline with frontend sending arrays for CP,MP,Add modules */ /** * Mock LSP Client sending sample configurations * Verifies that the NotebookConfigs class * parses and handles configurations appropriately - * + * * @author shimadan */ public class NotebookConfigsTest { @@ -61,7 +63,7 @@ public class NotebookConfigsTest { public NotebookConfigsTest() { } - @Before + @Before public void setUp() { setConfigObject(); LanguageClientInstance.getInstance(). @@ -97,7 +99,7 @@ public void testGetClassPath() { System.out.println("getClassPath"); try { initialized.get(5, TimeUnit.SECONDS); - String expResult = configsObj.get(CLASSPATH_KEY).getAsString(); + String expResult = String.join(File.pathSeparator, (configsObj.get(CLASSPATH_KEY).getAsJsonArray()).asList().stream().map((elem) -> elem.getAsString()).toList()); String result = instance.getClassPath(); assertEquals(expResult, result); } catch (Exception ex) { @@ -114,7 +116,7 @@ public void testGetModulePath() { try { initialized.get(5, TimeUnit.SECONDS); - String expResult = configsObj.get(MODULEPATH_KEY).getAsString(); + String expResult = String.join(File.pathSeparator, (configsObj.get(MODULEPATH_KEY).getAsJsonArray()).asList().stream().map((elem) -> elem.getAsString()).toList()); String result = instance.getModulePath(); assertEquals(expResult, result); } catch (Exception ex) { @@ -130,7 +132,7 @@ public void testGetAddModules() { System.out.println("getAddModules"); try { initialized.get(5, TimeUnit.SECONDS); - String expResult = configsObj.get(ADD_MODULES_KEY).getAsString(); + String expResult = String.join(",",(configsObj.get(ADD_MODULES_KEY).getAsJsonArray()).asList().stream().map((elem) -> elem.getAsString()).toList()); String result = instance.getAddModules(); assertEquals(expResult, result); } catch (Exception ex) { @@ -177,10 +179,18 @@ private void setConfigObject() { imports.add(new JsonPrimitive("java.math.*")); imports.add(new JsonPrimitive("javafx.scene.control.*")); configsObj.add(IMPLICIT_IMPORTS_KEY, imports); - configsObj.add(CLASSPATH_KEY, new JsonPrimitive("path/to/javafx-sdk-24.0.1/lib/javafx.base.jar")); - configsObj.add(MODULEPATH_KEY, new JsonPrimitive("/path/to/javafx-sdk/lib")); + JsonArray classpath = new JsonArray(); + classpath.add(new JsonPrimitive( + "path/to/javafx-sdk-24.0.1/lib/javafx.base.jar")); + configsObj.add(CLASSPATH_KEY, classpath); + JsonArray modulepath = new JsonArray(); + modulepath.add(new JsonPrimitive("/path/to/javafx-sdk/lib")); + configsObj.add(MODULEPATH_KEY, modulepath); configsObj.add(ENABLE_PREVIEW_KEY, new JsonPrimitive(false)); - configsObj.add(ADD_MODULES_KEY, new JsonPrimitive("javafx.controls,javafx.graphics")); + JsonArray addModules = new JsonArray(); + addModules.add(new JsonPrimitive("javafx.controls")); + addModules.add(new JsonPrimitive("javafx.graphics")); + configsObj.add(ADD_MODULES_KEY, addModules); } diff --git a/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManagerTest.java b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManagerTest.java index 74ad8e6..28e18aa 100644 --- a/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManagerTest.java +++ b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManagerTest.java @@ -201,18 +201,8 @@ public void testSyncState_UpdateCellContent_FailureAndFallback() throws Interrup } private static class TestableNotebookDocumentStateManager extends NotebookDocumentStateManager { - public TestableNotebookDocumentStateManager(NotebookDocument notebookDoc, List cells) { - super(notebookDoc, cells); - } - - @Override - protected void addNewCellState(NotebookCell cell, TextDocumentItem item) { - if (cell == null || item == null) { - return; - } - CellState cellState = new TestableCellState(cell, item, getNotebookDocument().getUri()); - getCellsMap().put(item.getUri(), cellState); + super(notebookDoc, cells, TestableCellState::new); } } diff --git a/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtilsTest.java b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtilsTest.java new file mode 100644 index 0000000..cf1e8bc --- /dev/null +++ b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtilsTest.java @@ -0,0 +1,122 @@ +package org.netbeans.modules.nbcode.java.notebook; + +import org.eclipse.lsp4j.Position; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author sidsrini + */ +public class NotebookUtilsTest { + /** + * Test of normalizeLineEndings method, of class NotebookUtils. + */ + @Test + public void testNormalizeLineEndings() { + assertNull(NotebookUtils.normalizeLineEndings(null)); + assertEquals("", NotebookUtils.normalizeLineEndings("")); + String expected = "a\nb\nc\n"; + assertEquals(expected, NotebookUtils.normalizeLineEndings(expected)); + assertEquals(expected, NotebookUtils.normalizeLineEndings("a\r\nb\nc\r\n")); + assertEquals(expected, NotebookUtils.normalizeLineEndings("a\u2028b\rc\u2029")); + assertEquals(expected, NotebookUtils.normalizeLineEndings("a\rb\rc\r")); + } + + /** + * Test of getOffset method, of class NotebookUtils. + */ + @Test + public void testGetOffset() { + assertEquals(0, NotebookUtils.getOffset(null, null)); + assertEquals(0, NotebookUtils.getOffset("", null)); + assertEquals(0, NotebookUtils.getOffset(null, new Position(0, 10))); + assertEquals(0, NotebookUtils.getOffset("abc", new Position(-1, 0))); + assertEquals(3, NotebookUtils.getOffset("abc", new Position(0, 10))); + assertEquals(0, NotebookUtils.getOffset("", new Position(0, 0))); + assertEquals(0, NotebookUtils.getOffset("", new Position(0, 2))); + assertEquals(0, NotebookUtils.getOffset("abc", new Position(0, 0))); + assertEquals(1, NotebookUtils.getOffset("abc", new Position(0, 1))); + assertEquals(2, NotebookUtils.getOffset("abc", new Position(0, 2))); + assertEquals(3, NotebookUtils.getOffset("abc", new Position(0, 3))); + assertEquals(2, NotebookUtils.getOffset("abc\n", new Position(0, 2))); + assertEquals(3, NotebookUtils.getOffset("abc\n", new Position(0, 3))); + assertEquals(3, NotebookUtils.getOffset("abc\n", new Position(0, 4))); + assertEquals(4, NotebookUtils.getOffset("abc\n", new Position(1, 0))); + assertEquals(4, NotebookUtils.getOffset("abc\n", new Position(1, 1))); + String multiLine = "abc\n def \nghi jkl\n"; + assertEquals(18, multiLine.length()); + assertEquals(2, NotebookUtils.getOffset(multiLine, new Position(0, 2))); + assertEquals(4, NotebookUtils.getOffset(multiLine, new Position(1, 0))); + assertEquals(5, NotebookUtils.getOffset(multiLine, new Position(1, 1))); + assertEquals(8, NotebookUtils.getOffset(multiLine, new Position(1, 4))); + assertEquals(9, NotebookUtils.getOffset(multiLine, new Position(1, 5))); + assertEquals(9, NotebookUtils.getOffset(multiLine, new Position(1, 6))); + assertEquals(10, NotebookUtils.getOffset(multiLine, new Position(2, -1))); + assertEquals(10, NotebookUtils.getOffset(multiLine, new Position(2, 0))); + assertEquals(16, NotebookUtils.getOffset(multiLine, new Position(2, 6))); + assertEquals(17, NotebookUtils.getOffset(multiLine, new Position(2, 7))); + assertEquals(17, NotebookUtils.getOffset(multiLine, new Position(2, 8))); + assertEquals(18, NotebookUtils.getOffset(multiLine, new Position(3, 0))); + assertEquals(18, NotebookUtils.getOffset(multiLine, new Position(3, 1))); + } + + /** + * Test of getPosition method, of class NotebookUtils. + */ + @Test + public void testGetPosition() { + assertEquals(new Position(0, 0), NotebookUtils.getPosition(null, 10)); + assertEquals(new Position(0, 0), NotebookUtils.getPosition("abc", -1)); + assertEquals(new Position(0, 0), NotebookUtils.getPosition("", 0)); + assertEquals(new Position(0, 0), NotebookUtils.getPosition("", 2)); + assertEquals(new Position(0, 0), NotebookUtils.getPosition("abc", 0)); + assertEquals(new Position(0, 1), NotebookUtils.getPosition("abc", 1)); + assertEquals(new Position(0, 2), NotebookUtils.getPosition("abc", 2)); + assertEquals(new Position(0, 3), NotebookUtils.getPosition("abc", 3)); + assertEquals(new Position(0, 2), NotebookUtils.getPosition("abc\n", 2)); + assertEquals(new Position(1, 0), NotebookUtils.getPosition("abc\n", 3)); + assertEquals(new Position(1, 0), NotebookUtils.getPosition("abc\n", 4)); + String multiLine = "abc\n def \nghi jkl\n"; + assertEquals(18, multiLine.length()); + assertEquals(new Position(0, 2), NotebookUtils.getPosition(multiLine, 2)); + assertEquals(new Position(1, 0), NotebookUtils.getPosition(multiLine, 4)); + assertEquals(new Position(1, 1), NotebookUtils.getPosition(multiLine, 5)); + assertEquals(new Position(1, 4), NotebookUtils.getPosition(multiLine, 8)); + assertEquals(new Position(2, 0), NotebookUtils.getPosition(multiLine, 9)); + assertEquals(new Position(2, 0), NotebookUtils.getPosition(multiLine, 10)); + assertEquals(new Position(2, 6), NotebookUtils.getPosition(multiLine, 16)); + assertEquals(new Position(3, 0), NotebookUtils.getPosition(multiLine, 17)); + assertEquals(new Position(3, 0), NotebookUtils.getPosition(multiLine, 18)); + } + + + /** + * Test of applyChange method, of class NotebookUtils. + */ + @Test + public void testApplyChange() { + assertEquals("Invalid range positions", assertThrows(IllegalArgumentException.class, () -> NotebookUtils.applyChange("", new Position(-1, 0), new Position(-1, 0), "")).getMessage()); + assertEquals("Invalid range positions", assertThrows(IllegalArgumentException.class, () -> NotebookUtils.applyChange("", new Position(1, 0), new Position(0, 0), "")).getMessage()); + assertEquals("Invalid range positions", assertThrows(IllegalArgumentException.class, () -> NotebookUtils.applyChange("", new Position(0, 10), new Position(0, 0), "")).getMessage()); + assertEquals("Invalid range start out of bounds", assertThrows(IllegalArgumentException.class, () -> NotebookUtils.applyChange("", new Position(1, 0), new Position(2, 0), "")).getMessage()); + assertEquals("Invalid range end out of bounds", assertThrows(IllegalArgumentException.class, () -> NotebookUtils.applyChange("abc", new Position(0, 0), new Position(1, 0), "")).getMessage()); + + String txt = new StringBuilder("abc").toString(); + assertSame(txt, NotebookUtils.applyChange(txt, new Position(0, 1), new Position(0, 1), "")); + + assertEquals("abcd", NotebookUtils.applyChange("abc", new Position(0, 3), new Position(0, 3), "d")); + assertEquals("abcd\n", NotebookUtils.applyChange("abc\n", new Position(0, 3), new Position(0, 3), "d")); + assertEquals("abcd\n", NotebookUtils.applyChange("abc\n", new Position(0, 4), new Position(0, 6), "d")); + assertEquals("abc\nd", NotebookUtils.applyChange("abc\n", new Position(1, 0), new Position(1, 0), "d")); + assertEquals("abc\nd", NotebookUtils.applyChange("abc\n", new Position(1, 0), new Position(1, 3), "d")); + assertEquals("abcd", NotebookUtils.applyChange("abc\n", new Position(0, 3), new Position(1, 0), "d")); + assertEquals("abd\n\n", NotebookUtils.applyChange("abc\n", new Position(0, 2), new Position(0, 3), "d\r\n")); + String multiLine = "abc\n def \nghi jkl\n"; + assertEquals("abc\n def \nghim\njkl\n", NotebookUtils.applyChange(multiLine, new Position(2, 3), new Position(2, 4), "m\n")); + assertEquals("abc\n def \nghi jkl\nm\n", NotebookUtils.applyChange(multiLine, new Position(3, 0), new Position(3, 0), "m\r\n")); + assertEquals("abc\n xyz\ndef \nghi\njkl\n", NotebookUtils.applyChange(multiLine, new Position(1, 1), new Position(2, 4), "xyz\ndef \nghi\n")); + assertEquals("abc\n def \nghi jkl\nmo", NotebookUtils.applyChange(multiLine + "mno", new Position(3, 1), new Position(3, 2), "")); + } + +} diff --git a/patches/java-notebooks.diff b/patches/java-notebooks.diff index 4175dba..b71783d 100644 --- a/patches/java-notebooks.diff +++ b/patches/java-notebooks.diff @@ -707,73 +707,6 @@ private static Launcher createLauncher(NbCodeLanguageClient client, InputStream in, OutputStream out, Function processor) { return new LSPLauncher.Builder() ---- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/input/ShowInputBoxParams.java -+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/input/ShowInputBoxParams.java -@@ -53,6 +53,11 @@ - */ - private boolean password = false; - -+ /** -+ * Controls if focus is changed from the input box whether it should close or not. -+ */ -+ private boolean ignoreFocusOut = false; -+ - public ShowInputBoxParams() { - this("", ""); - } -@@ -68,6 +73,11 @@ - this.password = password; - } - -+ public ShowInputBoxParams(@NonNull final String prompt, @NonNull final String value, final boolean ignoreFocusOut) { -+ this(prompt, value); -+ this.ignoreFocusOut = ignoreFocusOut; -+ } -+ - /** - * An optional title of the input box. - */ -@@ -131,6 +141,22 @@ - this.password = password; - } - -+ /** -+ * Controls if focus is changed from the input box whether it should close or not. -+ */ -+ @Pure -+ @NonNull -+ public boolean isIgnoreFocusOut() { -+ return ignoreFocusOut; -+ } -+ -+ /** -+ * Controls if focus is changed from the input box whether it should close or not. -+ */ -+ public void setIgnoreFocusOut(boolean ignoreFocusOut) { -+ this.ignoreFocusOut = ignoreFocusOut; -+ } -+ - @Override - @Pure - public String toString() { -@@ -139,6 +165,7 @@ - b.add("prompt", prompt); - b.add("value", value); - b.add("password" , password); -+ b.add("ignoreFocusOut", ignoreFocusOut); - return b.toString(); - } - -@@ -169,6 +196,9 @@ - if (this.password != other.password) { - return false; - } -+ if (this.ignoreFocusOut != other.ignoreFocusOut) { -+ return false; -+ } - if (!Objects.equals(this.title, other.title)) { - return false; - } diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java index 747d151600..bbf1f263d1 100644 --- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java @@ -868,7 +801,7 @@ index 747d151600..bbf1f263d1 100644 private static final RequestProcessor BACKGROUND_TASKS = new RequestProcessor(TextDocumentServiceImpl.class.getName(), 1, false, false); private static final RequestProcessor WORKER = new RequestProcessor(TextDocumentServiceImpl.class.getName(), 1, false, false); -+ private NotebookDocumentServiceHandler notebookDocumentServiceDelegator = null; ++ private volatile NotebookDocumentServiceHandler notebookDocumentServiceDelegator = null; + /** * File URIs touched / queried by the client. diff --git a/patches/updated-show-input-params.diff b/patches/updated-show-input-params.diff new file mode 100644 index 0000000..f786ce5 --- /dev/null +++ b/patches/updated-show-input-params.diff @@ -0,0 +1,67 @@ +--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/input/ShowInputBoxParams.java ++++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/input/ShowInputBoxParams.java +@@ -53,6 +53,11 @@ + */ + private boolean password = false; + ++ /** ++ * Controls if focus is changed from the input box whether it should close or not. ++ */ ++ private boolean ignoreFocusOut = false; ++ + public ShowInputBoxParams() { + this("", ""); + } +@@ -68,6 +73,11 @@ + this.password = password; + } + ++ public ShowInputBoxParams(@NonNull final String prompt, @NonNull final String value, final boolean ignoreFocusOut) { ++ this(prompt, value); ++ this.ignoreFocusOut = ignoreFocusOut; ++ } ++ + /** + * An optional title of the input box. + */ +@@ -131,6 +141,22 @@ + this.password = password; + } + ++ /** ++ * Controls if focus is changed from the input box whether it should close or not. ++ */ ++ @Pure ++ @NonNull ++ public boolean isIgnoreFocusOut() { ++ return ignoreFocusOut; ++ } ++ ++ /** ++ * Controls if focus is changed from the input box whether it should close or not. ++ */ ++ public void setIgnoreFocusOut(boolean ignoreFocusOut) { ++ this.ignoreFocusOut = ignoreFocusOut; ++ } ++ + @Override + @Pure + public String toString() { +@@ -139,6 +165,7 @@ + b.add("prompt", prompt); + b.add("value", value); + b.add("password" , password); ++ b.add("ignoreFocusOut", ignoreFocusOut); + return b.toString(); + } + +@@ -169,6 +196,9 @@ + if (this.password != other.password) { + return false; + } ++ if (this.ignoreFocusOut != other.ignoreFocusOut) { ++ return false; ++ } + if (!Objects.equals(this.title, other.title)) { + return false; + } diff --git a/vscode/l10n/bundle.l10n.en.json b/vscode/l10n/bundle.l10n.en.json index b67a2c0..afbe70c 100644 --- a/vscode/l10n/bundle.l10n.en.json +++ b/vscode/l10n/bundle.l10n.en.json @@ -88,7 +88,8 @@ "jdk.extension.error_msg.doesntSupportNewTeamplate":"Client {client} doesn't support creating a new file from template", "jdk.extension.error_msg.doesntSupportNewProject":"Client {client} doesn't support new project", "jdk.extension.error_msg.doesntSupportGoToTest":"Client {client} doesn't support go to test", - "jdk.extension.error_msg.doesntSupportNoteboookCellExecution":"Client {client} doesn't support notebook cell execution", + "jdk.extension.error_msg.doesntSupportNotebookCellExecution":"Language Server for {client} doesn't support notebook cell execution", + "jdk.extension.error_msg.doesntSupportJShellExecution":"Language Server for {client} doesn't support JShell execution", "jdk.extension.error_msg.noSuperImpl":"No super implementation found", "jdk.extension.error_msg.cacheDeletionError":"Error deleting the cache", "jdk.extension.message.cacheDeleted":"Cache deleted successfully", @@ -100,14 +101,29 @@ "jdk.telemetry.consent": "Allow anonymous telemetry data to be reported to Oracle? You may opt-out or in at any time from the Settings for jdk.telemetry.enabled.", "jdk.configChanged": "Configuration updated for the Oracle Java extension. Please reload the window to enable it.", "jdk.configChangedFailed": "Error while updating the configuration for the Oracle Java extension. Please reload the window to restart the extension.", - "jdk.notebook.create.select.workspace.folder": "Select workspace folder in which notebook needs to be created", - "jdk.notebook.create.select.workspace.folder.label": "Select Notebook creation folder", - "jdk.notebook.create.select.workspace.folder.title": "Select folder in which notebook needs to be created", - "jdk.notebook.create.error_msg.path.not.selected": "Path not selected for creating new notebook", + "jdk.notebook.create.select.workspace.folder": "Select the workspace folder where a new notebook will be created", + "jdk.notebook.create.select.workspace.folder.label": "Select Notebook Folder", + "jdk.notebook.create.select.workspace.folder.title": "Select the folder where a new notebook will be created", + "jdk.notebook.create.error_msg.path.not.selected": "Folder not selected for creating a notebook", + "jdk.notebook.create.error_msg.dir.not.found": "Folder not found", "jdk.notebook.create.error_msg.invalid.notebook.name": "Invalid notebook file name", - "jdk.notebook.create.error_msg.invalid.notebook.path": "Notebook already exists, please try creating with some different name", - "jdk.notebook.create.error_msg.failed": "Failed to create new notebook", - "jdk.jshell.open.error_msg.failed": "Some error occurred while launching jshell", - "jdk.notebook.project.mapping.error_msg.failed": "Error occurred while changing notebook project context", - "jdk.notebook.create.new.notebook.input.name": "Enter new Java notebook ({fileExtension}) file name" + "jdk.notebook.create.error_msg.invalid.notebook.path": "A notebook already exists with the same name. Please use a different name or folder.", + "jdk.notebook.create.error_msg.failed": "Failed to create a notebook", + "jdk.jshell.open.error_msg.failed": "Some error occurred while launching JShell", + "jdk.notebook.project.mapping.error_msg.failed": "An error occurred while changing the project context of the notebook", + "jdk.notebook.create.new.notebook.input.name": "Enter a file name for the new Java notebook", + "jdk.notebook.parsing.empty.file.error_msg.title": "Empty Notebook", + "jdk.notebook.parsing.empty.file.error_msg.desc": "The notebook file appears to be empty.", + "jdk.notebook.parsing.error_msg.title": "Error Opening Notebook", + "jdk.notebook.parsing.error_msg.desc": "Failed to open notebook: {message}", + "jdk.notebook.parsing.invalid.structure.error_msg.title": "Invalid Notebook Structure", + "jdk.notebook.parsing.invalid.structure.error_msg.desc": "Missing or invalid cells array.", + "jdk.notebook.cell.parsing.error_msg.title": "Cell parsing error", + "jdk.notebook.cell.type.error_msg": "Invalid cell type: {cellType}", + "jdk.notebook.cell.missing.error_msg": "Missing field: {fieldName}", + "jdk.notebook.serializer.error_msg": "Failed to serialize notebook: {errorMessage}", + "jdk.notebook.cell.serializer.error_msg": "Failed to serialize one or more cells", + "jdk.notebook.validation.failed.error_msg": "Notebook JSON validation failed", + "jdk.notebook.mime_type.not.found.cell.output": "Mime Type: {mimeType}, Content Length: {contentLength}", + "jdk.notebook.cell.language.not.found": "Doesn't support {languageId} execution" } diff --git a/vscode/l10n/bundle.l10n.ja.json b/vscode/l10n/bundle.l10n.ja.json index 1fccb2d..3b81352 100755 --- a/vscode/l10n/bundle.l10n.ja.json +++ b/vscode/l10n/bundle.l10n.ja.json @@ -88,7 +88,8 @@ "jdk.extension.error_msg.doesntSupportNewTeamplate":"クライアント{client}では、「テンプレートからファイル新規作成」はサポートされていません", "jdk.extension.error_msg.doesntSupportNewProject":"クライアント{client}では、新規プロジェクトはサポートされていません", "jdk.extension.error_msg.doesntSupportGoToTest":"クライアント{client}では、「テストへ移動」はサポートされていません", - "jdk.extension.error_msg.doesntSupportNoteboookCellExecution":"Client {client} doesn't support notebook cell execution", + "jdk.extension.error_msg.doesntSupportNotebookCellExecution":"Language Server for {client} doesn't support notebook cell execution", + "jdk.extension.error_msg.doesntSupportJShellExecution":"Language Server for {client} doesn't support JShell execution", "jdk.extension.error_msg.noSuperImpl":"スーパークラスの実装が見つかりません", "jdk.extension.error_msg.cacheDeletionError":"キャッシュの削除中にエラーが発生しました", "jdk.extension.message.cacheDeleted":"キャッシュが正常に削除されました", @@ -100,14 +101,29 @@ "jdk.telemetry.consent": "匿名テレメトリ・データをOracleにレポートすることを許可しますか。jdk.telemetry.enabledの設定からいつでもオプトアウトまたはオプトインできます。", "jdk.configChanged": "Configuration updated for the Oracle Java extension. Please reload the window to enable it.", "jdk.configChangedFailed": "Error while updating the configuration for the Oracle Java extension. Please reload the window to restart the extension.", - "jdk.notebook.create.select.workspace.folder": "Select workspace folder in which notebook needs to be created", - "jdk.notebook.create.select.workspace.folder.label": "Select Notebook creation folder", - "jdk.notebook.create.select.workspace.folder.title": "Select folder in which notebook needs to be created", - "jdk.notebook.create.error_msg.path.not.selected": "Path not selected for creating new notebook", + "jdk.notebook.create.select.workspace.folder": "Select the workspace folder where a new notebook will be created", + "jdk.notebook.create.select.workspace.folder.label": "Select Notebook Folder", + "jdk.notebook.create.select.workspace.folder.title": "Select the folder where a new notebook will be created", + "jdk.notebook.create.error_msg.path.not.selected": "Folder not selected for creating a notebook", + "jdk.notebook.create.error_msg.dir.not.found": "Folder not found", "jdk.notebook.create.error_msg.invalid.notebook.name": "Invalid notebook file name", - "jdk.notebook.create.error_msg.invalid.notebook.path": "Notebook already exists, please try creating with some different name", - "jdk.notebook.create.error_msg.failed": "Failed to create new notebook", - "jdk.jshell.open.error_msg.failed": "Some error occurred while launching jshell", - "jdk.notebook.project.mapping.error_msg.failed": "Error occurred while changing notebook project context", - "jdk.notebook.create.new.notebook.input.name": "Enter new Java notebook ({fileExtension}) file name" + "jdk.notebook.create.error_msg.invalid.notebook.path": "A notebook already exists with the same name. Please use a different name or folder.", + "jdk.notebook.create.error_msg.failed": "Failed to create a notebook", + "jdk.jshell.open.error_msg.failed": "Some error occurred while launching JShell", + "jdk.notebook.project.mapping.error_msg.failed": "An error occurred while changing the project context of the notebook", + "jdk.notebook.create.new.notebook.input.name": "Enter a file name for the new Java notebook", + "jdk.notebook.parsing.empty.file.error_msg.title": "Empty Notebook", + "jdk.notebook.parsing.empty.file.error_msg.desc": "The notebook file appears to be empty.", + "jdk.notebook.parsing.error_msg.title": "Error Opening Notebook", + "jdk.notebook.parsing.error_msg.desc": "Failed to open notebook: {message}", + "jdk.notebook.parsing.invalid.structure.error_msg.title": "Invalid Notebook Structure", + "jdk.notebook.parsing.invalid.structure.error_msg.desc": "Missing or invalid cells array.", + "jdk.notebook.cell.parsing.error_msg.title": "Cell parsing error", + "jdk.notebook.cell.type.error_msg": "Invalid cell type: {cellType}", + "jdk.notebook.cell.missing.error_msg": "Missing field: {fieldName}", + "jdk.notebook.serializer.error_msg": "Failed to serialize notebook: {errorMessage}", + "jdk.notebook.cell.serializer.error_msg": "Failed to serialize one or more cells", + "jdk.notebook.validation.failed.error_msg": "Notebook JSON validation failed", + "jdk.notebook.mime_type.not.found.cell.output": "Mime Type: {mimeType}, Content Length: {contentLength}", + "jdk.notebook.cell.language.not.found": "Doesn't support {languageId} execution" } diff --git a/vscode/l10n/bundle.l10n.zh-cn.json b/vscode/l10n/bundle.l10n.zh-cn.json index 4378d70..4e023a8 100755 --- a/vscode/l10n/bundle.l10n.zh-cn.json +++ b/vscode/l10n/bundle.l10n.zh-cn.json @@ -88,7 +88,8 @@ "jdk.extension.error_msg.doesntSupportNewTeamplate":"客户端 {client} 不支持从模板新建文件", "jdk.extension.error_msg.doesntSupportNewProject":"客户端 {client} 不支持新项目", "jdk.extension.error_msg.doesntSupportGoToTest":"客户端 {client} 不支持转至测试", - "jdk.extension.error_msg.doesntSupportNoteboookCellExecution":"Client {client} doesn't support notebook cell execution", + "jdk.extension.error_msg.doesntSupportNotebookCellExecution":"Language Server for {client} doesn't support notebook cell execution", + "jdk.extension.error_msg.doesntSupportJShellExecution":"Language Server for {client} doesn't support JShell execution", "jdk.extension.error_msg.noSuperImpl":"未找到超类实现", "jdk.extension.error_msg.cacheDeletionError":"删除高速缓存时出错", "jdk.extension.message.cacheDeleted":"已成功删除高速缓存", @@ -100,14 +101,29 @@ "jdk.telemetry.consent": "是否允许向 Oracle 报告匿名遥测数据?您随时可以通过 jdk.telemetry.enabled 对应的设置选择退出或加入。", "jdk.configChanged": "Configuration updated for the Oracle Java extension. Please reload the window to enable it.", "jdk.configChangedFailed": "Error while updating the configuration for the Oracle Java extension. Please reload the window to restart the extension.", - "jdk.notebook.create.select.workspace.folder": "Select workspace folder in which notebook needs to be created", - "jdk.notebook.create.select.workspace.folder.label": "Select Notebook creation folder", - "jdk.notebook.create.select.workspace.folder.title": "Select folder in which notebook needs to be created", - "jdk.notebook.create.error_msg.path.not.selected": "Path not selected for creating new notebook", + "jdk.notebook.create.select.workspace.folder": "Select the workspace folder where a new notebook will be created", + "jdk.notebook.create.select.workspace.folder.label": "Select Notebook Folder", + "jdk.notebook.create.select.workspace.folder.title": "Select the folder where a new notebook will be created", + "jdk.notebook.create.error_msg.path.not.selected": "Folder not selected for creating a notebook", + "jdk.notebook.create.error_msg.dir.not.found": "Folder not found", "jdk.notebook.create.error_msg.invalid.notebook.name": "Invalid notebook file name", - "jdk.notebook.create.error_msg.invalid.notebook.path": "Notebook already exists, please try creating with some different name", - "jdk.notebook.create.error_msg.failed": "Failed to create new notebook", - "jdk.jshell.open.error_msg.failed": "Some error occurred while launching jshell", - "jdk.notebook.project.mapping.error_msg.failed": "Error occurred while changing notebook project context", - "jdk.notebook.create.new.notebook.input.name": "Enter new Java notebook ({fileExtension}) file name" + "jdk.notebook.create.error_msg.invalid.notebook.path": "A notebook already exists with the same name. Please use a different name or folder.", + "jdk.notebook.create.error_msg.failed": "Failed to create a notebook", + "jdk.jshell.open.error_msg.failed": "Some error occurred while launching JShell", + "jdk.notebook.project.mapping.error_msg.failed": "An error occurred while changing the project context of the notebook", + "jdk.notebook.create.new.notebook.input.name": "Enter a file name for the new Java notebook", + "jdk.notebook.parsing.empty.file.error_msg.title": "Empty Notebook", + "jdk.notebook.parsing.empty.file.error_msg.desc": "The notebook file appears to be empty.", + "jdk.notebook.parsing.error_msg.title": "Error Opening Notebook", + "jdk.notebook.parsing.error_msg.desc": "Failed to open notebook: {message}", + "jdk.notebook.parsing.invalid.structure.error_msg.title": "Invalid Notebook Structure", + "jdk.notebook.parsing.invalid.structure.error_msg.desc": "Missing or invalid cells array.", + "jdk.notebook.cell.parsing.error_msg.title": "Cell parsing error", + "jdk.notebook.cell.type.error_msg": "Invalid cell type: {cellType}", + "jdk.notebook.cell.missing.error_msg": "Missing field: {fieldName}", + "jdk.notebook.serializer.error_msg": "Failed to serialize notebook: {errorMessage}", + "jdk.notebook.cell.serializer.error_msg": "Failed to serialize one or more cells", + "jdk.notebook.validation.failed.error_msg": "Notebook JSON validation failed", + "jdk.notebook.mime_type.not.found.cell.output": "Mime Type: {mimeType}, Content Length: {contentLength}", + "jdk.notebook.cell.language.not.found": "Doesn't support {languageId} execution" } diff --git a/vscode/package.json b/vscode/package.json index f0b289e..6d80e89 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -255,18 +255,27 @@ "description": "%jdk.debugger.configuration.completion.warning.time.description%" }, "jdk.notebook.classpath": { - "type": "string", - "default": "", + "type": "array", + "items": { + "type": "string" + }, + "default": [], "description": "%jdk.notebook.classpath.description%" }, "jdk.notebook.modulepath": { - "type": "string", - "default": "", + "type": "array", + "items": { + "type": "string" + }, + "default": [], "description": "%jdk.notebook.modulepath.description%" }, "jdk.notebook.addmodules": { - "type": "string", - "default": "", + "type": "array", + "items": { + "type": "string" + }, + "default": [], "description": "%jdk.notebook.addmodules.description%" }, "jdk.notebook.enablePreview": { @@ -276,6 +285,9 @@ }, "jdk.notebook.implicitImports": { "type": "array", + "items": { + "type": "string" + }, "default": [ "java.util.*", "java.io.*", diff --git a/vscode/package.nls.ja.json b/vscode/package.nls.ja.json index 901bf1d..f457366 100755 --- a/vscode/package.nls.ja.json +++ b/vscode/package.nls.ja.json @@ -68,12 +68,13 @@ "jdk.debugger.configuration.attach.listen.description": "接続するデバッグ対象をリスニング", "jdk.debugger.configuration.attach.timeout.description": "接続を待つ間のタイムアウト", "jdk.debugger.configuration.completion.warning.time.description": "コード補完にここで指定した時間(ミリ秒単位)よりも長くかかる場合、警告が生成されます(-1で無効化)", - "jdk.notebook.classpath.description": "Classpath that needs to be set for notebooks", - "jdk.notebook.modulepath.description": "Modulepath that needs to be set for notebooks", - "jdk.notebook.addmodules.description": "Modules that needs to be added for notebooks", - "jdk.notebook.enablePreview.description": "Flag to enable jdk preview features for notebooks", - "jdk.notebook.implicitImports.description": "List of packages to import implicitly", - "jdk.notebook.projects.mapping.description": "Mapping of notebook to project", + "jdk.notebook.classpath.description": "The specific class-paths for use in Java notebooks. Defaults to class-paths of the project context, when empty.", + "jdk.notebook.modulepath.description": "The specific module-paths for use in Java notebooks. Defaults to module-paths of the project context, when empty.", + "jdk.notebook.addmodules.description": "The specific modules for use in Java notebooks. Defaults to modules of the project context, when empty.", + "jdk.notebook.enablePreview.description": "Enable the use of Java preview features in Java notebooks", + "jdk.notebook.implicitImports.description": "List of elements to implicitly import in Java notebooks. Defaults to star-imports of java.util, java.io and java.math packages, when empty.", + "jdk.notebook.implicitImports.markdownDescription": "List of elements to implicitly import in Java notebooks. Defaults to star-imports of `java.util`, `java.io` and `java.math` packages, when empty.", + "jdk.notebook.projects.mapping.description": "Mapping of Java notebook paths to the path of the project that provides it context.", "jdk.configuration.java.completion.commit.chars": "コード補完の提案の受入れをトリガーする文字を指定します。たとえば、ピリオド(.)を入力したときに提案を受け入れるには、これを[\".\"]に設定します", "jdk.initialConfigurations.launchJavaApp.name": "Javaアプリケーションの起動", "jdk.configurationSnippets.name": "Javaアプリケーションの起動", diff --git a/vscode/package.nls.json b/vscode/package.nls.json index ae25d18..c93d910 100644 --- a/vscode/package.nls.json +++ b/vscode/package.nls.json @@ -68,12 +68,13 @@ "jdk.debugger.configuration.attach.listen.description": "Listen for the debuggee to attach", "jdk.debugger.configuration.attach.timeout.description": "Timeout while waiting to attach", "jdk.debugger.configuration.completion.warning.time.description": "When code completion takes longer than this specified time (in milliseconds), there will be a warning produced (-1 to disable)", - "jdk.notebook.classpath.description": "Classpath that needs to be set for notebooks", - "jdk.notebook.modulepath.description": "Modulepath that needs to be set for notebooks", - "jdk.notebook.addmodules.description": "Modules that needs to be added for notebooks", - "jdk.notebook.enablePreview.description": "Flag to enable jdk preview features for notebooks", - "jdk.notebook.implicitImports.description": "List of packages to import implicitly", - "jdk.notebook.projects.mapping.description": "Mapping of notebook to project", + "jdk.notebook.classpath.description": "The specific class-paths for use in Java notebooks. Defaults to class-paths of the project context, when empty.", + "jdk.notebook.modulepath.description": "The specific module-paths for use in Java notebooks. Defaults to module-paths of the project context, when empty.", + "jdk.notebook.addmodules.description": "The specific modules for use in Java notebooks. Defaults to modules of the project context, when empty.", + "jdk.notebook.enablePreview.description": "Enable the use of Java preview features in Java notebooks", + "jdk.notebook.implicitImports.description": "List of elements to implicitly import in Java notebooks. Defaults to star-imports of java.util, java.io and java.math packages, when empty.", + "jdk.notebook.implicitImports.markdownDescription": "List of elements to implicitly import in Java notebooks. Defaults to star-imports of `java.util`, `java.io` and `java.math` packages, when empty.", + "jdk.notebook.projects.mapping.description": "Mapping of Java notebook paths to the path of the project that provides it context.", "jdk.configuration.java.completion.commit.chars": "Specifies the characters that trigger accepting a code completion suggestion. For example, to accept suggestions when typing a dot (.), set this to [\".\"]", "jdk.initialConfigurations.launchJavaApp.name": "Launch Java App", "jdk.configurationSnippets.name": "Launch Java App", diff --git a/vscode/package.nls.zh-cn.json b/vscode/package.nls.zh-cn.json index 4938282..a41a6d3 100755 --- a/vscode/package.nls.zh-cn.json +++ b/vscode/package.nls.zh-cn.json @@ -68,12 +68,13 @@ "jdk.debugger.configuration.attach.listen.description": "监听要附加的被调试程序", "jdk.debugger.configuration.attach.timeout.description": "等待附加操作时的超时", "jdk.debugger.configuration.completion.warning.time.description": "当代码完成所用时间超过此指定时间(毫秒)时,将生成警告(-1 表示禁用)", - "jdk.notebook.classpath.description": "Classpath that needs to be set for notebooks", - "jdk.notebook.modulepath.description": "Modulepath that needs to be set for notebooks", - "jdk.notebook.addmodules.description": "Modules that needs to be added for notebooks", - "jdk.notebook.enablePreview.description": "Flag to enable jdk preview features for notebooks", - "jdk.notebook.implicitImports.description": "List of packages to import implicitly", - "jdk.notebook.projects.mapping.description": "Mapping of notebook to project", + "jdk.notebook.classpath.description": "The specific class-paths for use in Java notebooks. Defaults to class-paths of the project context, when empty.", + "jdk.notebook.modulepath.description": "The specific module-paths for use in Java notebooks. Defaults to module-paths of the project context, when empty.", + "jdk.notebook.addmodules.description": "The specific modules for use in Java notebooks. Defaults to modules of the project context, when empty.", + "jdk.notebook.enablePreview.description": "Enable the use of Java preview features in Java notebooks", + "jdk.notebook.implicitImports.description": "List of elements to implicitly import in Java notebooks. Defaults to star-imports of java.util, java.io and java.math packages, when empty.", + "jdk.notebook.implicitImports.markdownDescription": "List of elements to implicitly import in Java notebooks. Defaults to star-imports of `java.util`, `java.io` and `java.math` packages, when empty.", + "jdk.notebook.projects.mapping.description": "Mapping of Java notebook paths to the path of the project that provides it context.", "jdk.configuration.java.completion.commit.chars": "指定用于触发接受代码补全建议的字符。例如,要在键入点 (.) 时接受建议,请将该字符设为 [\".\"]", "jdk.initialConfigurations.launchJavaApp.name": "启动 Java 应用程序", "jdk.configurationSnippets.name": "启动 Java 应用程序", diff --git a/vscode/src/commands/notebook.ts b/vscode/src/commands/notebook.ts index d4ed3b7..b2fe713 100644 --- a/vscode/src/commands/notebook.ts +++ b/vscode/src/commands/notebook.ts @@ -68,10 +68,13 @@ const createNewNotebook = async (ctx?: any) => { if (notebookDir == null) { window.showErrorMessage(l10n.value("jdk.notebook.create.error_msg.path.not.selected")); return; + } else if(!fs.existsSync(notebookDir.fsPath)){ + window.showErrorMessage(l10n.value("jdk.notebook.create.error_msg.dir.not.found")); + return; } const notebookName = await window.showInputBox({ - prompt: l10n.value("jdk.notebook.create.new.notebook.input.name", { fileExtension: extConstants.NOTEBOOK_FILE_EXTENSION }), + prompt: l10n.value("jdk.notebook.create.new.notebook.input.name"), value: `Untitled.${extConstants.NOTEBOOK_FILE_EXTENSION}` }); @@ -116,23 +119,29 @@ const createNewNotebook = async (ctx?: any) => { } }; +type openJshellNbResponse = { + jdkPath: string, + vmOptions: string[] +} + const openJshellInContextOfProject = async (ctx: any) => { try { let client: LanguageClient = await globalState.getClientPromise().client; if (await isNbCommandRegistered(nbCommands.openJshellInProject)) { const additionalContext = window.activeTextEditor?.document.uri.toString(); - const res = await commands.executeCommand(nbCommands.openJshellInProject, ctx?.toString(), additionalContext); - const { envMap, finalArgs } = passArgsToTerminal(res); + const res = await commands.executeCommand(nbCommands.openJshellInProject, ctx?.toString(), additionalContext); + const { envMap, finalArgs } = passArgsToTerminal(res.vmOptions); + const jshellPath = res.jdkPath ? path.join(res.jdkPath, "bin", "jshell") : "jshell"; // Direct sendText is not working since it truncates the command exceeding a certain length. // Open issues on vscode: 130688, 134324 and many more // So, workaround by setting env variables. const terminal = window.createTerminal({ name: "Jshell instance", env: envMap }); - terminal.sendText(`jshell ${finalArgs.join(' ')}`, true); + terminal.sendText(`${jshellPath} ${finalArgs.join(' ')}`, true); terminal.show(); } else { - throw l10n.value("jdk.extension.error_msg.doesntSupportGoToTest", { client }); + throw l10n.value("jdk.extension.error_msg.doesntSupportJShellExecution", { client: client?.name }); } } catch (error) { window.showErrorMessage(l10n.value("jdk.jshell.open.error_msg.failed")); @@ -168,7 +177,7 @@ const notebookChangeProjectContextHandler = async (ctx: INotebookToolbar) => { { ...oldValue, [uri.fsPath]: res }, ConfigurationTarget.Workspace); } else { - throw l10n.value("jdk.extension.error_msg.doesntSupportGoToTest", { client }); + throw l10n.value("jdk.extension.error_msg.doesntSupportNotebookCellExecution", { client: client?.name }); } } catch (error) { LOGGER.error(`Error occurred while opening notebook : ${isError(error) ? error.message : error}`); diff --git a/vscode/src/configurations/handlers.ts b/vscode/src/configurations/handlers.ts index f77b1e3..45e4f5e 100644 --- a/vscode/src/configurations/handlers.ts +++ b/vscode/src/configurations/handlers.ts @@ -156,7 +156,4 @@ export const isNbJavacDisabledHandler = (): boolean => { export const isNetbeansVerboseEnabled = (): boolean => { return getConfigurationValue(configKeys.verbose, false); -} -export const isEnablePreview = (): boolean =>{ - return getConfigurationValue(configKeys.notebookEnablePreview,false); } \ No newline at end of file diff --git a/vscode/src/lsp/listeners/requests/handlers.ts b/vscode/src/lsp/listeners/requests/handlers.ts index 3ccbf79..57c4d5b 100644 --- a/vscode/src/lsp/listeners/requests/handlers.ts +++ b/vscode/src/lsp/listeners/requests/handlers.ts @@ -70,7 +70,7 @@ const multiStepInputRequestHandler = async (param: any) => { } const inputBoxRequestHandler = async (param: ShowInputBoxParams) => { - return await window.showInputBox({ title: param?.title, prompt: param.prompt, value: param.value, password: param?.password, ignoreFocusOut: param?.ignoreFocusOut }); + return await window.showInputBox({ title: param?.title, prompt: param?.prompt, value: param?.value, password: param?.password, ignoreFocusOut: param?.ignoreFocusOut }); } const saveDocumentRequestHandler = async (request: SaveDocumentRequestParams) => { diff --git a/vscode/src/lsp/protocol.ts b/vscode/src/lsp/protocol.ts index ee8a3b9..ac04f41 100644 --- a/vscode/src/lsp/protocol.ts +++ b/vscode/src/lsp/protocol.ts @@ -366,10 +366,10 @@ export namespace NotebookCellExecutionResult { notebookUri: string; cellUri: string; status: STATUS; - errorStream: Result; - diagnostics: string[]; - errorDiagnostics: string[]; - outputStream: Result; + errorStream?: Result; + diagnostics?: string[]; + errorDiagnostics?: string[]; + outputStream?: Result; metadata?: any; } export const type = new ProtocolNotificationType('notebook/execution/progress'); diff --git a/vscode/src/notebooks/codeCellExecution.ts b/vscode/src/notebooks/codeCellExecution.ts index a96a309..5857727 100644 --- a/vscode/src/notebooks/codeCellExecution.ts +++ b/vscode/src/notebooks/codeCellExecution.ts @@ -19,7 +19,7 @@ * under the License. */ -import { commands, NotebookCell, NotebookCellExecution, NotebookCellOutput, NotebookController } from "vscode"; +import { commands, NotebookCell, NotebookCellExecution, NotebookCellOutput, NotebookController, window, workspace } from "vscode"; import { LOGGER } from "../logger"; import { NotebookCellExecutionResult } from "../lsp/protocol"; import { createErrorOutputItem } from "./utils"; @@ -132,7 +132,12 @@ export class CodeCellExecution { this.execution.clearOutput(); await this.execution.replaceOutput(this.output); this.execution.token.onCancellationRequested(async () => { - await commands.executeCommand(nbCommands.interruptNotebookCellExecution, this.notebookId); + try { + await commands.executeCommand(nbCommands.interruptNotebookCellExecution, this.notebookId); + } catch (error) { + LOGGER.error("Some Error occurred while interrupting code cell: " + error); + window.showErrorMessage("Cannot interrupt code cell"); + } }); return; } diff --git a/vscode/src/notebooks/executionSummary.ts b/vscode/src/notebooks/executionSummary.ts index 034aa41..773e87e 100644 --- a/vscode/src/notebooks/executionSummary.ts +++ b/vscode/src/notebooks/executionSummary.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. + * Copyright (c) 2025, Oracle and/or its affiliates. * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file diff --git a/vscode/src/notebooks/kernel.ts b/vscode/src/notebooks/kernel.ts index 409f86d..aea72b5 100644 --- a/vscode/src/notebooks/kernel.ts +++ b/vscode/src/notebooks/kernel.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. + * Copyright (c) 2025, Oracle and/or its affiliates. * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -93,7 +93,7 @@ export class IJNBKernel implements Disposable { const client: LanguageClient = await globalState.getClientPromise().client; if (!(await isNbCommandRegistered(nbCommands.executeNotebookCell))) { - throw l10n.value("jdk.extension.error_msg.doesntSupportNoteboookCellExecution", { client }); + throw l10n.value("jdk.extension.error_msg.doesntSupportNotebookCellExecution", { client: client?.name }); } const response = await commands.executeCommand(nbCommands.executeNotebookCell, @@ -145,7 +145,7 @@ export class IJNBKernel implements Disposable { const exec = controller.createNotebookCellExecution(cell); exec.executionOrder = this.getIncrementedExecutionCounter(notebookId); exec.start(Date.now()); - await exec.replaceOutput(createErrorOutput(new Error(`Doesn't support ${cell.document.languageId} execution`))); + await exec.replaceOutput(createErrorOutput(new Error(l10n.value("jdk.notebook.cell.language.not.found", { languageId: cell.document.languageId })))); exec.end(false, Date.now()); } diff --git a/vscode/src/notebooks/mimeTypeHandler.ts b/vscode/src/notebooks/mimeTypeHandler.ts index 4df267e..b439d3a 100644 --- a/vscode/src/notebooks/mimeTypeHandler.ts +++ b/vscode/src/notebooks/mimeTypeHandler.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. + * Copyright (c) 2025, Oracle and/or its affiliates. * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -23,6 +23,7 @@ import { Buffer } from 'buffer'; import * as vscode from 'vscode'; import { IMimeBundle } from './types'; import { mimeTypes } from './constants'; +import { l10n } from '../localiser'; export type DataOrBytes = string | Uint8Array; @@ -65,7 +66,7 @@ export class MimeTypeHandler { const payload = Array.isArray(data) ? data.join('') : data; return [mt.makeOutputItem(payload)]; } - return []; + return vscode.NotebookCellOutputItem.text(l10n.value("jdk.notebook.mime_type.not.found.cell.output", {mimeType: mime, contentLength: data.length})); }); } } diff --git a/vscode/src/notebooks/notebook.ts b/vscode/src/notebooks/notebook.ts index d0f9830..6d16a3a 100644 --- a/vscode/src/notebooks/notebook.ts +++ b/vscode/src/notebooks/notebook.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. + * Copyright (c) 2025, Oracle and/or its affiliates. * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -25,10 +25,11 @@ import { serializeCell } from './utils'; import Ajv from "ajv"; import schema = require('./nbformat.v4.d7.schema.json'); import { LOGGER } from '../logger'; +import { l10n } from '../localiser'; export class NotebookVersionInfo { - static readonly NBFORMAT = 4; - static readonly NBFORMAT_MINOR = 5; + static readonly NBFORMAT = 4; + static readonly NBFORMAT_MINOR = 5; } export class Notebook { @@ -55,10 +56,10 @@ export class Notebook { ): Notebook { const cells = data.cells.map((cell) => { try { - return serializeCell(cell) + return serializeCell(cell) } catch (cellError) { - console.error('Error serializing cell: ', cell, cellError); - throw new Error('Failed to serialize one or more cells') + LOGGER.error('Error serializing cell: ' + cell + cellError); + throw new Error(l10n.value("jdk.notebook.cell.serializer.error_msg")) } }) return new Notebook(cells, language); @@ -66,10 +67,10 @@ export class Notebook { toJSON(): INotebook { return { - nbformat: this.nbformat, - nbformat_minor: this.nbformat_minor, - metadata: this.metadata, - cells: this.cells, + nbformat: this.nbformat, + nbformat_minor: this.nbformat_minor, + metadata: this.metadata, + cells: this.cells, }; } @@ -77,17 +78,18 @@ export class Notebook { const json = JSON.stringify(this.toJSON(), null, 2); return new TextEncoder().encode(json); } - - assertValidNotebook(){ + + assertValidNotebook() { Notebook.assertValidNotebookJson(this.toJSON()); } static assertValidNotebookJson(notebook: INotebook) { if (!Notebook.validateFn(notebook)) { const errors = (Notebook.validateFn.errors || []) - .map(e => `${e.schemaPath || '/'} ${e.message}`) - .join('\n'); - throw new Error(`Notebook JSON validation failed:\n${errors}`); + .map(e => `${e.schemaPath || '/'} ${e.message}`) + .join('\n'); + LOGGER.error(`Notebook JSON validation failed:\n${errors}`); + throw new Error(l10n.value("jdk.notebook.validation.failed.error_msg")); } LOGGER.debug("Notebook successfully validated."); } diff --git a/vscode/src/notebooks/register.ts b/vscode/src/notebooks/register.ts index 37a4f14..47f39ab 100644 --- a/vscode/src/notebooks/register.ts +++ b/vscode/src/notebooks/register.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. + * Copyright (c) 2025, Oracle and/or its affiliates. * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file diff --git a/vscode/src/notebooks/serializer.ts b/vscode/src/notebooks/serializer.ts index 10f1a9d..a6d4470 100644 --- a/vscode/src/notebooks/serializer.ts +++ b/vscode/src/notebooks/serializer.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. + * Copyright (c) 2025, Oracle and/or its affiliates. * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -23,6 +23,9 @@ import * as vscode from 'vscode'; import { errorNotebook, parseCell } from './utils'; import { ICell, INotebook } from './types'; import { Notebook } from './notebook'; +import { LOGGER } from '../logger'; +import { l10n } from '../localiser'; +import { isError } from '../utils'; class IJNBNotebookSerializer implements vscode.NotebookSerializer { private readonly decoder = new TextDecoder(); @@ -33,7 +36,8 @@ class IJNBNotebookSerializer implements vscode.NotebookSerializer { ): Promise { const raw = this.decoder.decode(content).trim(); if (!raw) { - return errorNotebook('Empty Notebook', 'The notebook file appears to be empty.'); + return errorNotebook(l10n.value("jdk.notebook.parsing.empty.file.error_msg.title"), + l10n.value("jdk.notebook.parsing.empty.file.error_msg.desc")); } let parsed: INotebook; @@ -41,16 +45,17 @@ class IJNBNotebookSerializer implements vscode.NotebookSerializer { parsed = JSON.parse(raw) as INotebook; Notebook.assertValidNotebookJson(parsed); } catch (err) { - console.error('Failed to parse notebook content:', err); - vscode.window.showErrorMessage(`Failed to open notebook: ${(err as Error).message}`); + LOGGER.error('Failed to parse notebook content: ' + err); + vscode.window.showErrorMessage(l10n.value("jdk.notebook.parsing.error_msg.title")); return errorNotebook( - 'Error Opening Notebook', - `Failed to parse notebook: ${(err as Error).message}` + l10n.value("jdk.notebook.parsing.error_msg.title"), + l10n.value("jdk.notebook.parsing.error_msg.desc", { message: err }) ); } if (!parsed || !Array.isArray(parsed.cells)) { - return errorNotebook('Invalid Notebook Structure', 'Missing or invalid `cells` array.'); + return errorNotebook(l10n.value("jdk.notebook.parsing.invalid.structure.error_msg.title"), + l10n.value("jdk.notebook.parsing.invalid.structure.error_msg.desc")); } let cells: vscode.NotebookCellData[]; @@ -58,7 +63,7 @@ class IJNBNotebookSerializer implements vscode.NotebookSerializer { cells = parsed.cells.map((cell: ICell) => parseCell(cell)); } catch (cellError) { return errorNotebook( - 'Cell parsing error', + l10n.value("jdk.notebook.cell.parsing.error_msg.title"), (cellError as Error).message ); } @@ -74,8 +79,9 @@ class IJNBNotebookSerializer implements vscode.NotebookSerializer { notebook.assertValidNotebook(); return notebook.toUint8Array(); } catch (err) { - console.error('Unhandled error in serializeNotebook:', err); - vscode.window.showErrorMessage(`Failed to serialize notebook: ${(err as Error).message}`); + LOGGER.error('Unhandled error in serializeNotebook: ' + err); + const errorMessage = isError(err) ? err.message : err; + vscode.window.showErrorMessage(l10n.value("jdk.notebook.serializer.error_msg", { errorMessage })); throw err; } } diff --git a/vscode/src/notebooks/types.ts b/vscode/src/notebooks/types.ts index 92fa6a6..dad8e15 100644 --- a/vscode/src/notebooks/types.ts +++ b/vscode/src/notebooks/types.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. + * Copyright (c) 2025, Oracle and/or its affiliates. * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file diff --git a/vscode/src/notebooks/utils.ts b/vscode/src/notebooks/utils.ts index ceeb0ba..798747d 100644 --- a/vscode/src/notebooks/utils.ts +++ b/vscode/src/notebooks/utils.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. + * Copyright (c) 2025, Oracle and/or its affiliates. * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -36,6 +36,7 @@ import { mimeTypes } from './constants'; import { MimeTypeHandler } from './mimeTypeHandler'; import { ExecutionSummary } from './executionSummary'; import { LOGGER } from '../logger'; +import { l10n } from '../localiser'; export function base64ToUint8Array(base64: string): Uint8Array { @@ -65,14 +66,14 @@ export const createErrorOutput = (err: string | Error) => { } export const createErrorOutputItem = (err: string | Error) => { - return vscode.NotebookCellOutputItem.text(isString(err) ? err: err.message); + return vscode.NotebookCellOutputItem.text(isString(err) ? err : err.message); } export function parseCell(cell: ICell): vscode.NotebookCellData { if (cell.cell_type !== 'code' && cell.cell_type !== 'markdown') - throw new Error(`Invalid cell_type: ${cell.cell_type}`); + throw new Error(l10n.value("jdk.notebook.cell.type.error_msg", { cellType: cell.cell_type })); if (cell.source === undefined || cell.source === null) - throw new Error('Missing cell.source'); + throw new Error(l10n.value("jdk.notebook.cell.missing.error_msg", { fieldName: "cell.source" })); const kind = cell.cell_type === 'code' ? vscode.NotebookCellKind.Code : vscode.NotebookCellKind.Markup; const language = kind === vscode.NotebookCellKind.Code ? 'java' : 'markdown'; @@ -205,7 +206,7 @@ export function serializeCell(cell: vscode.NotebookCellData): ICell { } export function errorNotebook(title: string, message: string, consoleMessage: string = ''): vscode.NotebookData { - console.error(title, ': ', message, ': ', consoleMessage); + LOGGER.error(title + ': ' + message + ': ' + consoleMessage); return new vscode.NotebookData([ new vscode.NotebookCellData( vscode.NotebookCellKind.Markup, diff --git a/vscode/src/test/unit/notebooks/mimeTypeHandler.unit.test.ts b/vscode/src/test/unit/notebooks/mimeTypeHandler.unit.test.ts index e4aea7f..3680d00 100644 --- a/vscode/src/test/unit/notebooks/mimeTypeHandler.unit.test.ts +++ b/vscode/src/test/unit/notebooks/mimeTypeHandler.unit.test.ts @@ -136,7 +136,7 @@ describe('MimeTypeHandler', () => { }; const items = MimeTypeHandler.itemsFromBundle(bundle); - expect(items).to.have.length(2); + expect(items).to.have.length(3); const textItem = items.find(i => (i as any).mime === mimeTypes.TEXT)!; expect(decodeData(textItem)).to.equal('foo'); @@ -144,6 +144,9 @@ describe('MimeTypeHandler', () => { const imgItem = items.find(i => (i as any).mime === 'image/png')!; const gotBytes = (imgItem as any).data as Uint8Array; expect(Array.from(gotBytes)).to.deep.equal(Array.from(rawImg)); + + const jsonItem = items.find(i => (i as any).mime === undefined)!; + expect(decodeData(jsonItem)).to.equal('jdk.notebook.mime_type.not.found.cell.output'); }); it('joins string-array values before creating the output item', () => { @@ -180,7 +183,9 @@ describe('MimeTypeHandler', () => { 'application/json': '[1,2,3]', 'video/mp4': 'abcd', }; - expect(MimeTypeHandler.itemsFromBundle(bundle)).to.be.empty; + const items = MimeTypeHandler.itemsFromBundle(bundle); + expect(items).to.have.length(2); + expect(items.filter(i => (i as any).mime === undefined)).to.have.length(2); }); }); }); diff --git a/vscode/src/test/unit/telemetry/workspaceChange.event.unit.test.ts b/vscode/src/test/unit/telemetry/workspaceChange.event.unit.test.ts index 44954e1..848a868 100644 --- a/vscode/src/test/unit/telemetry/workspaceChange.event.unit.test.ts +++ b/vscode/src/test/unit/telemetry/workspaceChange.event.unit.test.ts @@ -86,12 +86,12 @@ describe('WorkspaceChangeEvent', () => { cacheServiceGetStub.withArgs(oldId).returns(undefined); randomUUIDStub.returns(uuid); - const event = new WorkspaceChangeEvent(payload); + const expectedCacheValue = new ProjectCacheValue(uuid); const result = event.getPayload; expect(result.projectInfo[0].id).to.equal(uuid); - sinon.assert.calledWith(cacheServicePutStub, oldId, new ProjectCacheValue(uuid)); + sinon.assert.calledWith(cacheServicePutStub, oldId, expectedCacheValue); assertProject(result.projectInfo[0], { ...project, id: uuid }); }); From 4ee6f5f0a112cefddd04aa339d49ea47a452a698 Mon Sep 17 00:00:00 2001 From: Balyam muralidhar narendra kumar Date: Fri, 26 Sep 2025 18:23:35 +0530 Subject: [PATCH 9/9] Adding notebooks-l10n translation support --- build.xml | 1 + .../nbcode/java/notebook/Bundle.properties | 3 +- patches/l10n/25.0.0-notebooks-l10n.diff | 116 ++++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 patches/l10n/25.0.0-notebooks-l10n.diff diff --git a/build.xml b/build.xml index 5eececd..d92bd7f 100644 --- a/build.xml +++ b/build.xml @@ -37,6 +37,7 @@ patches/l10n/adding-java.lsp.server-ja-and-zh_CN.diff patches/l10n/24.1.0-release-content-changes.diff patches/l10n/24.1.0-previous-issues-fix.diff + patches/l10n/25.0.0-notebooks-l10n.diff diff --git a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/Bundle.properties b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/Bundle.properties index 2419dbc..c46b4f5 100644 --- a/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/Bundle.properties +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/Bundle.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2024-2025, Oracle and/or its affiliates. +# Copyright (c) 2025, Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,5 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # + OpenIDE-Module-Display-Category=Java OpenIDE-Module-Name=Java notebooks diff --git a/patches/l10n/25.0.0-notebooks-l10n.diff b/patches/l10n/25.0.0-notebooks-l10n.diff new file mode 100644 index 0000000..e19d6a8 --- /dev/null +++ b/patches/l10n/25.0.0-notebooks-l10n.diff @@ -0,0 +1,116 @@ +diff --git a/netbeans-l10n-zip/src/ja/java/nbcode-java-notebook/nbcode-java-notebook/org/netbeans/modules/nbcode/java/notebook/Bundle_ja.properties b/netbeans-l10n-zip/src/ja/java/nbcode-java-notebook/nbcode-java-notebook/org/netbeans/modules/nbcode/java/notebook/Bundle_ja.properties +new file mode 100644 +index 000000000..13780eb50 +--- /dev/null ++++ b/netbeans-l10n-zip/src/ja/java/nbcode-java-notebook/nbcode-java-notebook/org/netbeans/modules/nbcode/java/notebook/Bundle_ja.properties +@@ -0,0 +1,23 @@ ++# ++# Copyright (c) 2025, Oracle and/or its affiliates. ++# ++# Licensed 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 ++# ++# https://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. ++# ++ ++MSG_InterruptCodeCellExecSuccess=Code execution stopped successfully ++MSG_InterruptCodeCellInfo=Code execution was interrupted ++# {0} - error message ++MSG_KernelInitializeFailed=Java kernel initialization for the notebook failed. Error {0} ++MSG_KernelInitializeSuccess=Java kernel initialized successfully. ++MSG_KernelInitializing=Intializing Java kernel for notebook ++PROMPT_GetUserInput=Please provide scanner input here +\ No newline at end of file +diff --git a/netbeans-l10n-zip/src/ja/java/nbcode-java-notebook/nbcode-java-notebook/org/netbeans/modules/nbcode/java/project/Bundle_ja.properties b/netbeans-l10n-zip/src/ja/java/nbcode-java-notebook/nbcode-java-notebook/org/netbeans/modules/nbcode/java/project/Bundle_ja.properties +new file mode 100644 +index 000000000..95e2fb8fa +--- /dev/null ++++ b/netbeans-l10n-zip/src/ja/java/nbcode-java-notebook/nbcode-java-notebook/org/netbeans/modules/nbcode/java/project/Bundle_ja.properties +@@ -0,0 +1,22 @@ ++# ++# Copyright (c) 2025, Oracle and/or its affiliates. ++# ++# Licensed 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 ++# ++# https://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. ++# ++ ++# {0} - project name ++LBL_CurrentProjectContext=Current project context: {0} ++MSG_NoProjectContextFound=No project context ++MSG_NoProjectFound=No projects found ++PROMPT_SelectProjectTitle=Select Project ++ +diff --git a/netbeans-l10n-zip/src/zh_CN/java/nbcode-java-notebook/nbcode-java-notebook/org/netbeans/modules/nbcode/java/notebook/Bundle_zh_CN.properties b/netbeans-l10n-zip/src/zh_CN/java/nbcode-java-notebook/nbcode-java-notebook/org/netbeans/modules/nbcode/java/notebook/Bundle_zh_CN.properties +new file mode 100644 +index 000000000..13780eb50 +--- /dev/null ++++ b/netbeans-l10n-zip/src/zh_CN/java/nbcode-java-notebook/nbcode-java-notebook/org/netbeans/modules/nbcode/java/notebook/Bundle_zh_CN.properties +@@ -0,0 +1,23 @@ ++# ++# Copyright (c) 2025, Oracle and/or its affiliates. ++# ++# Licensed 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 ++# ++# https://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. ++# ++ ++MSG_InterruptCodeCellExecSuccess=Code execution stopped successfully ++MSG_InterruptCodeCellInfo=Code execution was interrupted ++# {0} - error message ++MSG_KernelInitializeFailed=Java kernel initialization for the notebook failed. Error {0} ++MSG_KernelInitializeSuccess=Java kernel initialized successfully. ++MSG_KernelInitializing=Intializing Java kernel for notebook ++PROMPT_GetUserInput=Please provide scanner input here +\ No newline at end of file +diff --git a/netbeans-l10n-zip/src/zh_CN/java/nbcode-java-notebook/nbcode-java-notebook/org/netbeans/modules/nbcode/java/project/Bundle_zh_CN.properties b/netbeans-l10n-zip/src/zh_CN/java/nbcode-java-notebook/nbcode-java-notebook/org/netbeans/modules/nbcode/java/project/Bundle_zh_CN.properties +new file mode 100644 +index 000000000..95e2fb8fa +--- /dev/null ++++ b/netbeans-l10n-zip/src/zh_CN/java/nbcode-java-notebook/nbcode-java-notebook/org/netbeans/modules/nbcode/java/project/Bundle_zh_CN.properties +@@ -0,0 +1,22 @@ ++# ++# Copyright (c) 2025, Oracle and/or its affiliates. ++# ++# Licensed 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 ++# ++# https://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. ++# ++ ++# {0} - project name ++LBL_CurrentProjectContext=Current project context: {0} ++MSG_NoProjectContextFound=No project context ++MSG_NoProjectFound=No projects found ++PROMPT_SelectProjectTitle=Select Project ++