diff --git a/THIRD_PARTY_LICENSES.txt b/THIRD_PARTY_LICENSES.txt index b3743494..c9442baf 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,73 @@ 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 -------------------- + +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 + +- 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/build.xml b/build.xml index 11642ba2..d92bd7fe 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 @@ -79,6 +80,9 @@ patches/dev-dependency-licenses.diff patches/nb-telemetry.diff patches/change-method-parameters-refactoring-qualified-names.diff + patches/upgrade-lsp4j.diff + patches/updated-show-input-params.diff + patches/java-notebooks.diff @@ -287,8 +291,17 @@ + + + + + + + + + - + diff --git a/nbcode/nbproject/project.properties b/nbcode/nbproject/project.properties index f3e09060..e748b59b 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 00000000..7edfeace --- /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 00000000..4e401701 --- /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 00000000..dade1bd4 --- /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 00000000..4f8a333c --- /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 00000000..8b8f7612 --- /dev/null +++ b/nbcode/notebooks/nbproject/project.properties @@ -0,0 +1,25 @@ +# +# 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.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 00000000..fe98a92e --- /dev/null +++ b/nbcode/notebooks/nbproject/project.xml @@ -0,0 +1,174 @@ + + + + org.netbeans.modules.apisupport.project + + + org.netbeans.modules.nbcode.java.notebook + + + + com.google.gson + + + + 2.11.0 + + + + org.netbeans.api.annotations.common + + + + 1 + 1.57 + + + + org.netbeans.api.java + + + + 1 + 1.94 + + + + org.netbeans.api.java.classpath + + + + 1 + 1.81 + + + + org.netbeans.api.lsp + + + + 1 + 1.28 + + + + 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.modules + + + + 7.75 + + + + org.openide.util + + + + 9.33 + + + + org.openide.util.lookup + + + + 8.59 + + + + + + unit + + org.netbeans.libs.junit4 + + + + org.netbeans.modules.nbjunit + + + + + org.netbeans.modules.java.lsp.server + + + + + + + + diff --git a/nbcode/notebooks/nbproject/suite.properties b/nbcode/notebooks/nbproject/suite.properties new file mode 100644 index 00000000..0b44bb95 --- /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 00000000..c46b4f57 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/Bundle.properties @@ -0,0 +1,18 @@ +# +# 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. +# + +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 00000000..33e8f751 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CellState.java @@ -0,0 +1,171 @@ +/* + * 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 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; +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; + +/** + * + * @author atalati + */ +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 executionSummary; + private static final Logger LOG = Logger.getLogger(CellState.class.getName()); + + CellState(NotebookCell notebookCell, TextDocumentItem details, String notebookUri) { + String normalizedText = NotebookUtils.normalizeLineEndings(details.getText()); + 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()); + 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 String getCellUri() { + return cellUri; + } + + public String getNotebookUri() { + return notebookUri; + } + + public void setContent(String newContent, int newVersion) throws InterruptedException, ExecutionException { + VersionAwareContent currentContent = content.get(); + + if (currentContent.getVersion() != newVersion - 1) { + 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(NotebookUtils.normalizeLineEndings(newCellState.getText()), receivedVersion); + content.updateAndGet(current -> current != currentContent && receivedVersion <= current.getVersion() ? current : newVersionContent); + } else { + 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 { + // newContent is already normalized during applyChanges + VersionAwareContent newVersionContent = new VersionAwareContent(newContent, newVersion); + + if (!content.compareAndSet(currentContent, newVersionContent)) { + LOG.log(Level.WARNING, "Concurrent modification detected. Version expected: {0}, current: {1}", new Object[]{newVersion - 1, 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(NotebookUtils.normalizeLineEndings(newCellState.getText()), newCellState.getVersion()); + content.set(newVersionContent); + } + + public void setExecutionSummary(ExecutionSummary executionSummary) { + this.executionSummary.set(executionSummary); + } + + public void setMetadata(Object metadata) { + this.metadata.set(metadata); + } + + // 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 final String content; + private final int version; + + public VersionAwareContent(String content, int version) { + this.content = content; + this.version = version; + } + + public String getContent() { + return content; + } + + public int getVersion() { + return 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 00000000..13cf455e --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeCompletionProvider.java @@ -0,0 +1,122 @@ +/* + * 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.HashSet; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +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; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.jsonrpc.messages.Either; + +/** + * + * @author atalati + */ +public class CodeCompletionProvider { + + private static final Logger LOG = Logger.getLogger(CodeCompletionProvider.class.getName()); + + 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) { + + return CompletableFuture.supplyAsync(() -> { + try { + if (instance == null || state == null) { + return Either., CompletionList>forLeft(new ArrayList<>()); + } + + SourceCodeAnalysis sourceCodeAnalysis = instance.sourceCodeAnalysis(); + List suggestions = getSuggestions( + params.getTextDocument().getUri(), + params.getPosition(), + state, + sourceCodeAnalysis + ); + + List completionItems = new ArrayList<>(); + HashSet visited = new HashSet<>(); + + for (Suggestion suggestion : suggestions) { + String continuation = suggestion.continuation(); + if (visited.add(continuation)) { + completionItems.add(createCompletionItem(continuation)); + } + } + + return Either., CompletionList>forLeft(completionItems); + + } catch (Exception e) { + LOG.log(Level.WARNING, "Error getting code completions: {0}", e.toString()); + return Either., CompletionList>forLeft(new ArrayList<>()); + } + }); + } + + private CompletionItem createCompletionItem(String label) { + CompletionItem item = new CompletionItem(); + item.setLabel(label); + + return item; + } + + 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); + + 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 new file mode 100644 index 00000000..adc659d2 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeEval.java @@ -0,0 +1,447 @@ +/* + * 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.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; +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.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 = 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<>(); + private final Map>> pendingTasks = new ConcurrentHashMap<>(); + private final Map activeCellExecutionMapping = new ConcurrentHashMap<>(); + + public static CodeEval getInstance() { + return Singleton.instance; + } + + private static class Singleton { + + private static final CodeEval instance = new CodeEval(); + } + + public BiConsumer outStreamFlushCb = (notebookId, msg) -> { + sendNotification(notebookId, msg, EXECUTION_STATUS.EXECUTING, false); + }; + + 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"); + 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"); + throw new IllegalArgumentException("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(); + } + 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); + } + + 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; + }); + } + + private void codeEvalTaskRunnable(CompletableFuture future, JShell jshell, String notebookId, String cellId, String sourceCode) { + try { + if (jshell == null) { + 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); + sendNotification(notebookId, EXECUTION_STATUS.SUCCESS); + + future.complete(true); + } catch (Exception e) { + 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); + } + } + + public void runCode(JShell jshell, String code) { + runCode(jshell, code, null); + } + + public void runCode(JShell jshell, String code, String notebookId) { + try { + SourceCodeAnalysis analysis = jshell.sourceCodeAnalysis(); + List snippets = NotebookUtils.getCodeSnippets(analysis, code); + + 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); + } + } + } + } + } catch (IllegalStateException e) { + LOG.log(Level.SEVERE, "Error while evaluation of the code : {0}", 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<>(); + JShellException jshellException = event.exception(); + if (jshellException != null) { + String msg = jshellException.getMessage(); + boolean msgAdded = false; + if (msg != null && !msg.isBlank()) { + runtimeErrors.add(msg); + msgAdded = true; + } + // 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 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; + } + + private List getSnippetValue(SnippetEvent event) { + return event.value() != null ? List.of(event.value()) : Collections.emptyList(); + } + + private void flushStreams(String notebookId) { + JshellStreamsHandler streamHandler = NotebookSessionManager.getInstance().getJshellStreamsHandler(notebookId); + if (streamHandler != null) { + streamHandler.flushOutputStreams(); + } + } + + // 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")) { + 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"); + } + } + + Builder b = NotebookCellExecutionProgressResultParams.builder(notebookId, cellId).status(status); + if (msg == null) { + if (diags != null) { + b.diagnostics(diags); + } else if (errorDiags != null) { + b.errorDiagnostics(errorDiags); + } + } else { + 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); + } + } 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 00000000..031f5f01 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CustomInputStream.java @@ -0,0 +1,85 @@ +/* + * 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; +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; + private final WeakReference client; + private static final String USER_PROMPT_REQUEST = Bundle.PROMPT_GetUserInput(); + + public CustomInputStream(NbCodeLanguageClient client) { + this.client = new WeakReference<>(client); + } + + @Override + public synchronized int read(byte[] b, int off, int len) throws IOException { + try { + if (currentStream == null || currentStream.available() == 0) { + 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) { + 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 00000000..16f8c3ed --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/JshellStreamsHandler.java @@ -0,0 +1,121 @@ +/* + * 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.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 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(); + } catch (IOException exception) { + LOG.log(Level.WARNING, "IOException occurred while flushing out stream: {0}", exception.toString()); + } + try { + errStream.flush(); + } catch (IOException exception) { + LOG.log(Level.WARNING, "IOException occurred while flushing error stream: {0}", exception.toString()); + } + } + + @Override + 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 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/LanguageClientInstance.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/LanguageClientInstance.java new file mode 100644 index 00000000..e42a2965 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/LanguageClientInstance.java @@ -0,0 +1,48 @@ +/* + * 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 new file mode 100644 index 00000000..6d85c408 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookCommandsHandler.java @@ -0,0 +1,65 @@ +/* + * 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.modules.nbcode.java.project.CommandHandler; +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_INTERRUPT = "nbls.jshell.interrupt.cell"; + private static final String NBLS_OPEN_PROJECT_JSHELL = "nbls.jshell.project.open"; + 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() { + return COMMANDS; + } + + @Override + public CompletableFuture runCommand(String command, List arguments) { + try { + + switch (command) { + case NBLS_JSHELL_EXEC: + 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)); + } + } 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 00000000..74280808 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigs.java @@ -0,0 +1,164 @@ +/* + * 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.JsonArray; +import com.google.gson.JsonObject; +import java.io.File; +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.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 Logger LOG = Logger.getLogger(NotebookConfigs.class.getName()); + + private static final String[] NOTEBOOK_CONFIG_LABELS = {"notebook.classpath", + "notebook.modulepath", + "notebook.addmodules", + "notebook.enablePreview", + "notebook.implicitImports", + "notebook.projects.mapping"}; + 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; + } + + 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; + } + + public JsonObject getNotebookProjectMapping() { + return notebookProjectMapping; + } + + private NotebookConfigs() { + + } + + public static NotebookConfigs getInstance() { + return Singleton.instance; + } + + private static class Singleton { + + private static final NotebookConfigs instance = new NotebookConfigs(); + } + + public void initConfigs() { + try { + this.initialized = initializeConfigs(); + } catch (Exception ex) { + LOG.log(Level.WARNING, "Exception occurred while init configs for notebooks: {0}", ex.getMessage()); + } + } + + private List getConfigItems() { + List items = new ArrayList<>(); + for (String label : NOTEBOOK_CONFIG_LABELS) { + ConfigurationItem item = new ConfigurationItem(); + NbCodeLanguageClient client = LanguageClientInstance.getInstance().getClient(); + if (client != null) { + item.setSection(client.getNbCodeCapabilities().getConfigurationPrefix() + label); + items.add(item); + } + } + return items; + } + + 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) { + JsonArray classPathConfig = NotebookUtils.getArgument(c, 0, JsonArray.class); + if (classPathConfig != null) { + classPath = String.join(File.pathSeparator,classPathConfig.asList().stream().map((elem) -> elem.getAsString()).toList()); + } + + JsonArray modulePathConfig = NotebookUtils.getArgument(c, 1, JsonArray.class); + if (modulePathConfig != null) { + modulePath = String.join(File.pathSeparator,modulePathConfig.asList().stream().map((elem) -> elem.getAsString()).toList()); + } + + JsonArray addModulesConfig = NotebookUtils.getArgument(c, 2, JsonArray.class); + if (addModulesConfig != null) { + addModules = String.join(",",addModulesConfig.asList().stream().map((elem) -> elem.getAsString()).toList()); + } + + Boolean enablePreviewConfig = NotebookUtils.getArgument(c, 3, Boolean.class); + if (enablePreviewConfig != null) { + enablePreview = enablePreviewConfig; + } + + JsonArray implicitImportsConfig = NotebookUtils.getArgument(c, 4, JsonArray.class); + if (implicitImportsConfig != null) { + implicitImports = implicitImportsConfig.asList().stream().map((elem) -> elem.getAsString()).toList(); + } + + JsonObject notebookProjectMappingConfig = NotebookUtils.getArgument(c, 5, JsonObject.class); + if (notebookProjectMappingConfig != null) { + notebookProjectMapping = notebookProjectMappingConfig; + } + } + }); + + } + return CompletableFuture.completedFuture(null); + + } + + public String getJdkVersion() { + return System.getProperty("java.version").split("\\.")[0]; + } + + public void notebookConfigsChangeListener(JsonObject settings) { + // 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 new file mode 100644 index 00000000..1d1535cd --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java @@ -0,0 +1,262 @@ +/* + * 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.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.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.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; +import org.eclipse.lsp4j.MessageType; +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.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; + +/** + * + * @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()); + 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 { + NbCodeLanguageClient client = LanguageClientInstance.getInstance().getClient(); + if (client == null) { + return; + } + 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, Bundle.MSG_KernelInitializeSuccess())); + } else { + // if package import fails user is not informed ? + 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()); + } + }); + 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) { + 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 + 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) { + LanguageClientInstance.getInstance().setClient((NbCodeLanguageClient) client); + NotebookConfigs.getInstance().initConfigs(); + + } + + @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); + } + + @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 new file mode 100644 index 00000000..e382138f --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java @@ -0,0 +1,260 @@ +/* + * 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.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; +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.Range; +import org.eclipse.lsp4j.TextDocumentContentChangeEvent; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.eclipse.lsp4j.TextDocumentItem; +import org.eclipse.lsp4j.VersionedNotebookDocumentIdentifier; + +/** + * + * @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; + 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<>(); + 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; + } + } + } + + 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; + } + + 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); + if (removed != null) { + LOG.log(Level.FINE, "Removed cell from map: {0}", uri); + } + } + } + + List cellsItem = updatedStructure.getDidOpen(); + List cellsDetail = updatedStructure.getArray().getCells(); + + 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); + } + } + } + + 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) { + 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(); + + 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 for cell: {0}. Error - {1}", new Object[]{uri, e}); + try { + cellState.requestContentAndSet(); + } catch (Exception ex) { + LOG.log(Level.SEVERE, "Failed to refresh content for cell: " + uri, ex); + } + } + } + + 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(); + return NotebookUtils.applyChange(content, range.getStart(), range.getEnd(), change.getText()); + } + + + 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 = 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 new file mode 100644 index 00000000..f4842a0a --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookSessionManager.java @@ -0,0 +1,276 @@ +/* + * 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 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; + +/** + * + * @author atalati + */ +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 = "--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 jshellStreamsMap = new ConcurrentHashMap<>(); + private final Map notebookPrjMap = new ConcurrentHashMap<>(); + + private NotebookSessionManager() { + } + + public static NotebookSessionManager getInstance() { + return Singleton.instance; + } + + private static class Singleton { + + private static final NotebookSessionManager instance = new NotebookSessionManager(); + } + + 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(); + + return sessions.computeIfAbsent(notebookId, id -> { + JshellStreamsHandler handler = new JshellStreamsHandler(id, CodeEval.getInstance().outStreamFlushCb, CodeEval.getInstance().errStreamFlushCb); + jshellStreamsMap.put(id, handler); + + CompletableFuture future = jshellBuilder(notebookDoc.getUri(), handler); + + 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(Project prj) { + List compilerOptions = new ArrayList<>(); + 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()); + boolean enablePreview = configs.isEnablePreview(); + + 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)); + } + enablePreview = enablePreview || projOptions.contains(ENABLE_PREVIEW); + } + + if (enablePreview) { + compilerOptions.add(ENABLE_PREVIEW); + compilerOptions.add(SOURCE_FLAG); + compilerOptions.add(configs.getJdkVersion()); + } + + return compilerOptions; + } + + private List getRemoteVmOptions(Project prj) { + List remoteOptions = new ArrayList<>(); + NotebookConfigs configs = NotebookConfigs.getInstance(); + boolean enablePreview = 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 (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)); + } + enablePreview = enablePreview || projOptions.contains(ENABLE_PREVIEW); + } + if (enablePreview) { + remoteOptions.add(ENABLE_PREVIEW); + } + return remoteOptions; + } + + private void onJshellInit(String notebookId, JShell jshell) { + jshell.onShutdown(shell -> closeSession(notebookId)); + + 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(el -> CodeEval.getInstance().runCode(jshell, "import " + el + ".*")); + } + } + + 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 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); + if (jshell != null) { + jshell.close(); + } + JshellStreamsHandler handler = jshellStreamsMap.remove(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/notebook/NotebookUtils.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtils.java new file mode 100644 index 00000000..8ac7d841 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookUtils.java @@ -0,0 +1,218 @@ +/* + * 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.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 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) { + return text == null ? null : LINE_ENDINGS.matcher(text).replaceAll("\n"); + } + + public static int getOffset(String content, Position position) { + if (content == null || position == null) { + return 0; + } + + int targetLine = position.getLine(); + if (targetLine < 0) { + return 0; + } + int targetChar = Math.max(0, position.getCharacter()); + + int lineStartIndex = -1; + int lineEndIndex = -1; + int line = -1; + + // find the start line in content + do { + lineStartIndex = lineEndIndex + 1; + lineEndIndex = content.indexOf('\n', lineStartIndex); + line++; + } while (line < targetLine && lineEndIndex >= 0); + + return line < targetLine ? content.length() : Math.min(lineStartIndex + targetChar, lineEndIndex < 0 ? content.length() : lineEndIndex); + } + + public static Position getPosition(String text, int offset) { + if (text == null || offset < 0) { + return new Position(0, 0); + } + + 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) { + 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; + } + + /** + * 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 new file mode 100644 index 00000000..f7df32e9 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/StreamingOutputStream.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 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 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; + } + + 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.noop = callback == null; + this.callback = callback; + 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 { + if (noop) return; + write(b, 0, b.length); + } + + @Override + public synchronized void close() throws IOException { + if (!noop) { + flushToCallback(); + isPeriodicFlushOutputStream.set(false); + } + super.close(); + } + + 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(); + 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 new file mode 100644 index 00000000..65bd9528 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/CommandHandler.java @@ -0,0 +1,91 @@ +/* + * 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.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import java.util.logging.Logger; +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; + +/** + * + * @author atalati + */ +public class CommandHandler { + + private static final Logger LOG = Logger.getLogger(CommandHandler.class.getName()); + + 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); + 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(); + } + + 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 OpenJshellResponse(installationPath, new ArrayList<>())); + } + return ProjectConfigurationUtils.buildProject(prj) + .thenCompose(isBuildSuccess -> { + if (isBuildSuccess) { + LOG.log(Level.INFO, "Opened Jshell instance with build success status"); + } else { + LOG.log(Level.WARNING, "Opened Jshell instance with build failed status"); + } + List vmOptions = ProjectConfigurationUtils.launchVMOptions(prj); + return CompletableFuture.completedFuture(new OpenJshellResponse(installationPath, vmOptions)); + }); + }); + } + + 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 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 new file mode 100644 index 00000000..a242d226 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectConfigurationUtils.java @@ -0,0 +1,258 @@ +/* + * 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.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; +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 final static String ENABLE_PREVIEW = "--enable-preview"; + + 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 JavaPlatform.getDefault(); + } + 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; + } + + 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) { + return new ArrayList<>(); + } + boolean isModular = ProjectModulePathConfigurationUtils.isModularProject(project); + if (isModular) { + 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.get(0), ClassPath.EXECUTE); + vmOptions.addAll(Arrays.asList(CLASS_PATH, addRoots("", cp))); + } + if (isPreviewEnabled(project, roots)) { + vmOptions.add(ENABLE_PREVIEW); + } + return vmOptions; + } + + @NonNull + public static List compilerOptions(Project project) { + if (project == null) { + return new ArrayList<>(); + } + boolean isModular = ProjectModulePathConfigurationUtils.isModularProject(project); + if (isModular) { + 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.get(0), ClassPath.COMPILE); + compileOptions.addAll(Arrays.asList(CLASS_PATH, addRoots("", cp))); + } + if (isPreviewEnabled(project, roots)) { + compileOptions.add(ENABLE_PREVIEW); + } + 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/ProjectContext.java b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectContext.java new file mode 100644 index 00000000..5dbb70a6 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectContext.java @@ -0,0 +1,124 @@ +/* +/* + * 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; +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) { + 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.get(0))); + } + 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.get(0)); + } + }); + } + + 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 = Bundle.PROMPT_SelectProjectTitle(); + 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 ? 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 -> { + 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 00000000..991659c2 --- /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 new file mode 100644 index 00000000..81e50e23 --- /dev/null +++ b/nbcode/notebooks/src/org/netbeans/modules/nbcode/java/project/ProjectModulePathConfigurationUtils.java @@ -0,0 +1,274 @@ +/* + * 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.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; + +/** + * 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.get(0), 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.get(0)); + 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/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 00000000..83e46b2e --- /dev/null +++ b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/CellStateTest.java @@ -0,0 +1,221 @@ +/* + * 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 + ); + + 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/CustomInputStreamTest.java b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/CustomInputStreamTest.java new file mode 100644 index 00000000..23b391d6 --- /dev/null +++ b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/CustomInputStreamTest.java @@ -0,0 +1,150 @@ +package org.netbeans.modules.nbcode.java.notebook; + +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; + +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 = new CustomInputStream(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 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/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 00000000..01e123f9 --- /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."); + } + + @Override + public CompletableFuture> configuration(ConfigurationParams configurationParams) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void showStatusBarMessage(ShowStatusMessageParams ssmp) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CompletableFuture showHtmlPage(HtmlPageParams hpp) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CompletableFuture execInHtmlPage(HtmlPageParams hpp) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CompletableFuture> showQuickPick(ShowQuickPickParams sqpp) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CompletableFuture showInputBox(ShowInputBoxParams sibp) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CompletableFuture, String>>> showMultiStepInput(ShowMutliStepInputParams smsip) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void notifyTestProgress(TestProgressParams tpp) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CompletableFuture createTextEditorDecoration(DecorationRenderOptions dro) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setTextEditorDecoration(SetTextEditorDecorationParams stedp) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void disposeTextEditorDecoration(String params) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void notifyNodeChange(NodeChangedParams ncp) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CompletableFuture requestDocumentSave(SaveDocumentRequestParams sdrp) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CompletableFuture writeOutput(OutputMessage om) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CompletableFuture showOutput(String outputName) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CompletableFuture closeOutput(String outputName) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CompletableFuture resetOutput(String outputName) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void telemetryEvent(Object object) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void publishDiagnostics(PublishDiagnosticsParams diagnostics) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void showMessage(MessageParams messageParams) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CompletableFuture showMessageRequest(ShowMessageRequestParams requestParams) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void logMessage(MessageParams message) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void notifyNotebookCellExecutionProgress(NotebookCellExecutionProgressResultParams params) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @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 00000000..2369c42f --- /dev/null +++ b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookConfigsTest.java @@ -0,0 +1,209 @@ +/* + * 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.io.File; +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 +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 { + + 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 = 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) { + 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 = 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) { + 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 = 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) { + 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); + 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)); + JsonArray addModules = new JsonArray(); + addModules.add(new JsonPrimitive("javafx.controls")); + addModules.add(new JsonPrimitive("javafx.graphics")); + configsObj.add(ADD_MODULES_KEY, addModules); + + } + + 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); + } + } +} 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 00000000..28e18aa4 --- /dev/null +++ b/nbcode/notebooks/test/unit/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManagerTest.java @@ -0,0 +1,234 @@ +/* + * 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, TestableCellState::new); + } + } + + 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/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 00000000..cf1e8bcd --- /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 new file mode 100644 index 00000000..b71783d3 --- /dev/null +++ b/patches/java-notebooks.diff @@ -0,0 +1,1205 @@ +--- /dev/null ++++ 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 ++ * 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.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 ++ */ ++public class NotebookCellExecutionProgressResultParams { ++ ++ public static enum EXECUTION_STATUS { ++ QUEUED, ++ EXECUTING, ++ SUCCESS, ++ FAILURE, ++ INTERRUPTED ++ } ++ /** ++ * URI of the notebook. ++ */ ++ @NonNull ++ private final String notebookUri; ++ ++ /** ++ * URI of the cell. ++ */ ++ @NonNull ++ 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. ++ */ ++ @Pure ++ @NonNull ++ public String getNotebookUri() { ++ return notebookUri; ++ } ++ ++ /** ++ * URI of the cell. ++ */ ++ @Pure ++ @NonNull ++ public String getCellUri() { ++ return cellUri; ++ } ++ ++ /** ++ * @param status new execution stage (must not be null) ++ */ ++ 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() { ++ return Objects.hash( ++ notebookUri, ++ cellUri, ++ status, ++ outputStream, ++ errorStream, ++ diagnostics, ++ errorDiagnostics, ++ metadata ++ ); ++ } ++ ++ @Override ++ public boolean equals(Object obj) { ++ if (this == obj) { ++ return true; ++ } ++ if (!(obj instanceof NotebookCellExecutionProgressResultParams)) { ++ 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 (!(obj instanceof NotebookCellStateParams)) { ++ return false; ++ } ++ NotebookCellStateParams other = (NotebookCellStateParams) obj; ++ return Objects.equals(this.notebookUri, other.notebookUri) ++ && Objects.equals(this.cellUri, other.cellUri); ++ } ++} +--- /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,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.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 +169,9 @@ + @JsonRequest("output/reset") + public CompletableFuture resetOutput(String outputName); + ++ @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,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.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 +247,13 @@ + return remote.resetOutput(outputName); + } + ++ @Override ++ 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 +@@ -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,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.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 +185,14 @@ + public CompletableFuture resetOutput(String outputName) { + return CompletableFuture.completedFuture(null); + } ++ ++ @Override ++ 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,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.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,8 +309,18 @@ + public CompletableFuture resetOutput(String outputName) { + return CompletableFuture.completedFuture(null); + } ++ ++ @Override ++ 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() +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,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.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 +1386,16 @@ public final class Server { + logWarning("Reset output: " + outputName); //NOI18N + return CompletableFuture.completedFuture(null); + } ++ ++ @Override ++ 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); ++ } + }; + + + +--- 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 +@@ -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";// 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 volatile NotebookDocumentServiceHandler notebookDocumentServiceDelegator = null; ++ + /** + * File URIs touched / queried by the client. + */ +@@ -337,7 +340,49 @@ 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 { + +@@ -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) { ++ 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(); +@@ -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); ++ } ++ } ++ ++ 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); + } + } + +@@ -705,6 +768,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); +@@ -729,6 +798,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); +@@ -780,6 +855,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); +@@ -804,6 +885,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); +@@ -828,11 +915,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()); + } + +@@ -988,6 +1087,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) { +@@ -1031,6 +1136,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(() -> { +@@ -1087,6 +1199,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); + +@@ -1271,6 +1390,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + 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()); +@@ -1431,6 +1556,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + + @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()); +@@ -1438,6 +1569,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + + @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) { +@@ -1479,6 +1616,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); +@@ -1538,6 +1682,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()); +@@ -1667,6 +1818,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()); +@@ -1810,6 +1967,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + + @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); +@@ -1882,6 +2045,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + + @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); +@@ -1909,6 +2078,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + + @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(); +@@ -1933,6 +2108,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + + @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); +@@ -1960,6 +2141,13 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + + @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) { +@@ -2553,6 +2741,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + + @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) { +@@ -2629,6 +2823,12 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli + + @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); +@@ -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/patches/l10n/25.0.0-notebooks-l10n.diff b/patches/l10n/25.0.0-notebooks-l10n.diff new file mode 100644 index 00000000..e19d6a84 --- /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 ++ diff --git a/patches/updated-show-input-params.diff b/patches/updated-show-input-params.diff new file mode 100644 index 00000000..f786ce53 --- /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/patches/upgrade-lsp4j.diff b/patches/upgrade-lsp4j.diff new file mode 100644 index 00000000..375f914b --- /dev/null +++ b/patches/upgrade-lsp4j.diff @@ -0,0 +1,549 @@ +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.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 +--- 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/l10n/bundle.l10n.en.json b/vscode/l10n/bundle.l10n.en.json index db88197f..afbe70c9 100644 --- a/vscode/l10n/bundle.l10n.en.json +++ b/vscode/l10n/bundle.l10n.en.json @@ -88,6 +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.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", @@ -98,5 +100,30 @@ "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 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": "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 3f469730..3b813522 100755 --- a/vscode/l10n/bundle.l10n.ja.json +++ b/vscode/l10n/bundle.l10n.ja.json @@ -88,6 +88,8 @@ "jdk.extension.error_msg.doesntSupportNewTeamplate":"クライアント{client}では、「テンプレートからファイル新規作成」はサポートされていません", "jdk.extension.error_msg.doesntSupportNewProject":"クライアント{client}では、新規プロジェクトはサポートされていません", "jdk.extension.error_msg.doesntSupportGoToTest":"クライアント{client}では、「テストへ移動」はサポートされていません", + "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":"キャッシュが正常に削除されました", @@ -98,5 +100,30 @@ "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 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": "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 ac4bdcd2..4e023a83 100755 --- a/vscode/l10n/bundle.l10n.zh-cn.json +++ b/vscode/l10n/bundle.l10n.zh-cn.json @@ -88,6 +88,8 @@ "jdk.extension.error_msg.doesntSupportNewTeamplate":"客户端 {client} 不支持从模板新建文件", "jdk.extension.error_msg.doesntSupportNewProject":"客户端 {client} 不支持新项目", "jdk.extension.error_msg.doesntSupportGoToTest":"客户端 {client} 不支持转至测试", + "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":"已成功删除高速缓存", @@ -98,5 +100,30 @@ "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 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": "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-lock.json b/vscode/package-lock.json index a7edb664..b5d010ea 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": "^8.17.1", "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,39 @@ "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": "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.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.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 +1188,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 +1214,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 +1228,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 +1243,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 +1270,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 +1298,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 +1345,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 +1364,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 +1388,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,53 +1456,115 @@ "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", + "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" }, @@ -1660,9 +1573,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 +1590,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 +1627,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 +1642,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 +1661,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 +1674,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 +1712,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 +1736,26 @@ "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-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", @@ -1851,6 +1776,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 +1793,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 +1802,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 +1817,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 +1844,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 +1855,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 +1895,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 +1918,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 +1935,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 +1961,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 +1989,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 +2039,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 +2067,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 +2097,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 +2125,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 +2253,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 +2277,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 +2289,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 +2300,12 @@ "node": ">=6" } }, + "node_modules/json-schema-traverse": { + "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", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -2526,6 +2346,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 +2376,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 +2458,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 +2483,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,10 +2539,31 @@ "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, + "dependencies": { + "@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": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", "dependencies": { @@ -2727,17 +2576,20 @@ "url": "https://github.com/sponsors/isaacs" } }, - "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==", + "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, - "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/ms": { @@ -2759,19 +2611,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 +2657,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 +2677,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 +2730,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 +2769,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 +2907,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 +2988,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 +3006,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 +3071,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 +3099,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 +3135,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 +3211,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" @@ -3391,21 +3246,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,12 +3274,23 @@ }, "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": { "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", @@ -3460,19 +3323,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 +3361,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 +3433,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 +3463,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 +3514,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 +3548,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 +3582,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 +3610,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 +3639,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 +3659,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 +3672,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 +3722,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 +3796,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 +3843,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 +3863,7 @@ ], "dependencies": { "escalade": "^3.2.0", - "picocolors": "^1.1.0" + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -4049,6 +3945,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 +3966,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 +4008,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 +4080,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 +4133,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 +4147,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 +4223,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 b1fc1fb5..6d80e89f 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", @@ -242,6 +254,52 @@ "default": 10000, "description": "%jdk.debugger.configuration.completion.warning.time.description%" }, + "jdk.notebook.classpath": { + "type": "array", + "items": { + "type": "string" + }, + "default": [], + "description": "%jdk.notebook.classpath.description%" + }, + "jdk.notebook.modulepath": { + "type": "array", + "items": { + "type": "string" + }, + "default": [], + "description": "%jdk.notebook.modulepath.description%" + }, + "jdk.notebook.addmodules": { + "type": "array", + "items": { + "type": "string" + }, + "default": [], + "description": "%jdk.notebook.addmodules.description%" + }, + "jdk.notebook.enablePreview": { + "type": "boolean", + "default": false, + "description": "%jdk.notebook.enablePreview.description%" + }, + "jdk.notebook.implicitImports": { + "type": "array", + "items": { + "type": "string" + }, + "default": [ + "java.util.*", + "java.io.*", + "java.math.*" + ], + "description": "%jdk.notebook.implicitImports.description%" + }, + "jdk.notebook.projects.mapping": { + "type": "object", + "default": {}, + "description": "%jdk.notebook.projects.mapping.description%" + }, "jdk.telemetry.enabled": { "type": "boolean", "description": "%jdk.configuration.telemetry.enabled.description%", @@ -439,6 +497,11 @@ "category": "Java", "icon": "$(new-folder)" }, + { + "command": "jdk.jshell.project", + "title": "%jdk.jshell.project%", + "category": "Java" + }, { "command": "jdk.java.goto.super.implementation", "title": "%jdk.java.goto.super.implementation%", @@ -541,6 +604,18 @@ { "command": "jdk.delete.cache", "title": "%jdk.delete.cache%" + }, + { + "command": "jdk.notebook.new", + "title": "%jdk.notebook.new%", + "category": "Java", + "icon": "$(new-file)" + }, + { + "command": "jdk.notebook.change.project", + "title": "%jdk.notebook.change.project%", + "category": "Java", + "icon": "$(pencil)" } ], "keybindings": [ @@ -583,6 +658,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": [ @@ -591,6 +671,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", @@ -605,6 +690,11 @@ "command": "jdk.project.debug", "when": "nbJdkReady && resourceExtname == .java", "group": "javadebug@2" + }, + { + "command": "jdk.jshell.project", + "when": "nbJdkReady && resourceExtname == .java", + "group": "javadebug@3" } ], "commandPalette": [ @@ -644,6 +734,10 @@ { "command": "jdk.addEventListener", "when": "false" + }, + { + "command": "jdk.notebook.change.project", + "when": "false" } ], "view/title": [ @@ -718,6 +812,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": [ @@ -818,7 +919,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", @@ -838,6 +938,7 @@ "dependencies": { "@vscode/debugadapter": "^1.68.0", "@vscode/l10n": "^0.0.18", + "ajv": "^8.17.1", "jsonc-parser": "3.3.1", "vscode-languageclient": "^9.0.1" }, @@ -858,4 +959,4 @@ "brace-expansion": "^2.0.2" } } -} \ No newline at end of file +} diff --git a/vscode/package.nls.ja.json b/vscode/package.nls.ja.json index 7bc09bbd..f4573666 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,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": "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 628d5b57..c93d910b 100644 --- a/vscode/package.nls.json +++ b/vscode/package.nls.json @@ -6,11 +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", "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", @@ -65,6 +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": "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 64177b65..a41a6d3f 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,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": "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/commands.ts b/vscode/src/commands/commands.ts index cbc8f56a..c696189c 100644 --- a/vscode/src/commands/commands.ts +++ b/vscode/src/commands/commands.ts @@ -50,7 +50,10 @@ 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("notebook.new"), + openJshellInProject: appendPrefixToCommand("jshell.project"), + notebookChangeProjectContext: appendPrefixToCommand("notebook.change.project") } export const builtInCommands = { @@ -83,5 +86,9 @@ 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"), + 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/notebook.ts b/vscode/src/commands/notebook.ts new file mode 100644 index 00000000..b2fe7137 --- /dev/null +++ b/vscode/src/commands/notebook.ts @@ -0,0 +1,202 @@ +import * as os from 'os'; +import * as path from 'path'; +import * as fs from 'fs'; +import { LOGGER } from '../logger'; +import { commands, ConfigurationTarget, 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'; +import { Notebook } from '../notebooks/notebook'; +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 { + 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: l10n.value("jdk.notebook.create.select.workspace.folder"), + 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: l10n.value("jdk.notebook.create.select.workspace.folder.label"), + title: l10n.value("jdk.notebook.create.select.workspace.folder.title") + }); + + if (nbFolderPath) { + notebookDir = nbFolderPath[0]; + } + } else { + notebookDir = getContextUri(ctx) || null; + } + 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"), + value: `Untitled.${extConstants.NOTEBOOK_FILE_EXTENSION}` + }); + + if (!notebookName?.trim()) { + window.showErrorMessage(l10n.value("jdk.notebook.create.error_msg.invalid.notebook.name")); + return; + } + const notebookNameWithExt = notebookName.endsWith(extConstants.NOTEBOOK_FILE_EXTENSION) ? + 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(l10n.value("jdk.notebook.create.error_msg.invalid.notebook.path")); + return; + } + + 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}`); + + 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(l10n.value("jdk.notebook.create.error_msg.failed")); + } +}; + +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.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(`${jshellPath} ${finalArgs.join(' ')}`, true); + terminal.show(); + } else { + throw l10n.value("jdk.extension.error_msg.doesntSupportJShellExecution", { client: client?.name }); + } + } catch (error) { + 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}`); + } +} + +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 }; +} + +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.doesntSupportNotebookCellExecution", { client: client?.name }); + } + } 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, + handler: createNewNotebook + }, + { + command: extCommands.openJshellInProject, + handler: openJshellInContextOfProject + }, + { + command: extCommands.notebookChangeProjectContext, + handler: notebookChangeProjectContextHandler + } +]; \ No newline at end of file diff --git a/vscode/src/commands/register.ts b/vscode/src/commands/register.ts index 7e2e5c3e..ef357782 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/configurations/configuration.ts b/vscode/src/configurations/configuration.ts index fadbe640..53c1621e 100644 --- a/vscode/src/configurations/configuration.ts +++ b/vscode/src/configurations/configuration.ts @@ -31,7 +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', + notebookProjectMapping: "notebook.projects.mapping" }; export const builtInConfigKeys = { @@ -44,6 +50,12 @@ 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), + appendPrefixToCommand(configKeys.notebookProjectMapping), builtInConfigKeys.vscodeTheme, ]; diff --git a/vscode/src/constants.ts b/vscode/src/constants.ts index 0e1789ca..956fce31 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/extension.ts b/vscode/src/extension.ts index 41ff8043..6d02737b 100644 --- a/vscode/src/extension.ts +++ b/vscode/src/extension.ts @@ -33,6 +33,7 @@ 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'; export function activate(context: ExtensionContext): VSNetBeansAPI { @@ -47,6 +48,7 @@ export function activate(context: ExtensionContext): VSNetBeansAPI { registerDebugger(context); subscribeCommands(context); registerFileProviders(context); + registerNotebookProviders(context); launchConfigurations.updateLaunchConfig(); diff --git a/vscode/src/lsp/listeners/notifications/handlers.ts b/vscode/src/lsp/listeners/notifications/handlers.ts index 2ef5cf6b..6161c2d4 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 da24cffa..57c4d5bd 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, ShowQuickPickParams, 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) => { @@ -109,11 +109,22 @@ 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; } +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/nbLanguageClient.ts b/vscode/src/lsp/nbLanguageClient.ts index 05efe6e9..14aae56d 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 32cd87e2..ac04f414 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 { @@ -342,4 +346,45 @@ export namespace CloseOutputRequest { export namespace ResetOutputRequest { export const type = new ProtocolRequestType('output/reset'); -} \ No newline at end of file +} + +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 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 00000000..58577278 --- /dev/null +++ b/vscode/src/notebooks/codeCellExecution.ts @@ -0,0 +1,152 @@ +/* + * 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, window, workspace } 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([]); + private isError: boolean = false; + + 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.debug(`${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) { + this.isError = true; + 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) { + this.isError = true; + 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) => { + const finalExecStatus = status && !this.isError; + if (this.isExecutionStarted) { + status && !this.isError ? + LOGGER.debug(`${this.getCellId()} successfully executed`) + : + LOGGER.error(`${this.getCellId()} failed while executing`); + this.execution!.end(finalExecStatus, 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 () => { + 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; + } + 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 00000000..6c280e6c --- /dev/null +++ b/vscode/src/notebooks/constants.ts @@ -0,0 +1,43 @@ +/* + * 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'; + 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 00000000..773e87e7 --- /dev/null +++ b/vscode/src/notebooks/executionSummary.ts @@ -0,0 +1,51 @@ +/* + * 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 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/kernel.ts b/vscode/src/notebooks/kernel.ts new file mode 100644 index 00000000..aea72b51 --- /dev/null +++ b/vscode/src/notebooks/kernel.ts @@ -0,0 +1,185 @@ +/* + * 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 { 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.doesntSupportNotebookCellExecution", { client: client?.name }); + } + + 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(l10n.value("jdk.notebook.cell.language.not.found", { languageId: cell.document.languageId })))); + 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 00000000..b439d3a7 --- /dev/null +++ b/vscode/src/notebooks/mimeTypeHandler.ts @@ -0,0 +1,72 @@ +/* + * 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 { 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; + +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 vscode.NotebookCellOutputItem.text(l10n.value("jdk.notebook.mime_type.not.found.cell.output", {mimeType: mime, contentLength: data.length})); + }); + } +} 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 00000000..22585f4d --- /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 00000000..6d16a3a4 --- /dev/null +++ b/vscode/src/notebooks/notebook.ts @@ -0,0 +1,96 @@ +/* + * 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 * as vscode from 'vscode'; +import { ICell, INotebook } from './types'; +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; +} + +export class Notebook { + readonly nbformat: number; + readonly nbformat_minor: number; + readonly metadata: { language_info: { name: string } }; + readonly cells: ICell[]; + static ajv = new Ajv({ + allErrors: true, + strict: false + }); + 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) { + LOGGER.error('Error serializing cell: ' + cell + cellError); + throw new Error(l10n.value("jdk.notebook.cell.serializer.error_msg")) + } + }) + 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.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 new file mode 100644 index 00000000..47f39ab0 --- /dev/null +++ b/vscode/src/notebooks/register.ts @@ -0,0 +1,35 @@ +/* + * 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 { ExtensionContext, workspace } from "vscode"; +import { notebookKernel } from "./kernel"; +import { notebookSerializer } from "./serializer"; + +export const registerNotebookProviders = (context: ExtensionContext) => { + context.subscriptions.push( + workspace.registerNotebookSerializer( + 'ijnb-notebook', + 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 00000000..a6d44703 --- /dev/null +++ b/vscode/src/notebooks/serializer.ts @@ -0,0 +1,90 @@ +/* + * 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 * 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(); + + async deserializeNotebook( + content: Uint8Array, + _token: vscode.CancellationToken + ): Promise { + const raw = this.decoder.decode(content).trim(); + if (!raw) { + 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; + try { + parsed = JSON.parse(raw) as INotebook; + Notebook.assertValidNotebookJson(parsed); + } catch (err) { + LOGGER.error('Failed to parse notebook content: ' + err); + vscode.window.showErrorMessage(l10n.value("jdk.notebook.parsing.error_msg.title")); + return errorNotebook( + 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(l10n.value("jdk.notebook.parsing.invalid.structure.error_msg.title"), + l10n.value("jdk.notebook.parsing.invalid.structure.error_msg.desc")); + } + + let cells: vscode.NotebookCellData[]; + try { + cells = parsed.cells.map((cell: ICell) => parseCell(cell)); + } catch (cellError) { + return errorNotebook( + l10n.value("jdk.notebook.cell.parsing.error_msg.title"), + (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) { + 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; + } + } +} + +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 00000000..dad8e151 --- /dev/null +++ b/vscode/src/notebooks/types.ts @@ -0,0 +1,115 @@ +/* + * 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 { Uri } from "vscode"; + +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; +} + +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 new file mode 100644 index 00000000..798747d3 --- /dev/null +++ b/vscode/src/notebooks/utils.ts @@ -0,0 +1,217 @@ +/* + * 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 { 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'; +import { LOGGER } from '../logger'; +import { l10n } from '../localiser'; + + +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(l10n.value("jdk.notebook.cell.type.error_msg", { cellType: cell.cell_type })); + if (cell.source === undefined || cell.source === null) + 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'; + 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) LOGGER.debug(`${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) LOGGER.debug(`${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 { + LOGGER.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 0fe128aa..2478173c 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/test/unit/mocks/vscode/notebookCellOutputItem.ts b/vscode/src/test/unit/mocks/vscode/notebookCellOutputItem.ts new file mode 100644 index 00000000..8cc5fc82 --- /dev/null +++ b/vscode/src/test/unit/mocks/vscode/notebookCellOutputItem.ts @@ -0,0 +1,31 @@ +/* + 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. +*/ + +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 00000000..d08836d5 --- /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 00000000..3680d003 --- /dev/null +++ b/vscode/src/test/unit/notebooks/mimeTypeHandler.unit.test.ts @@ -0,0 +1,191 @@ +/* + 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(3); + + 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)); + + 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', () => { + 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', + }; + 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 44954e1a..848a8689 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 }); }); diff --git a/vscode/tsconfig.json b/vscode/tsconfig.json index 64351bb6..0da96ea3 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. */