You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by en...@apache.org on 2021/07/14 22:15:56 UTC

[netbeans] branch master updated: Visually differentiate lines with DWARF information during native image debugging.

This is an automated email from the ASF dual-hosted git repository.

entl pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git


The following commit(s) were added to refs/heads/master by this push:
     new 9cd0dee  Visually differentiate lines with DWARF information during native image debugging.
9cd0dee is described below

commit 9cd0dee3499cd77c86db6ec6c864d8e7eec0a107
Author: Martin Entlicher <ma...@oracle.com>
AuthorDate: Wed Jun 30 09:49:01 2021 +0200

    Visually differentiate lines with DWARF information during native image debugging.
---
 cpplite/cpplite.debugger/nbproject/project.xml     |   2 +-
 .../modules/cpplite/debugger/CPPLiteDebugger.java  | 139 +++++++
 .../debugger/ni/NIDebuggerProviderImpl.java        |  20 +
 ide/nativeimage.api/manifest.mf                    |   2 +-
 .../netbeans/modules/nativeimage/api/Location.java | 140 +++++++
 .../modules/nativeimage/api/SourceInfo.java        | 138 +++++++
 .../netbeans/modules/nativeimage/api/Symbol.java   | 131 +++++++
 .../modules/nativeimage/api/debug/NIDebugger.java  |  48 +++
 .../nativeimage/spi/debug/NIDebuggerProvider.java  |  26 +-
 java/java.lsp.server/nbproject/project.xml         |   2 +-
 .../modules/java/lsp/server/LspServerState.java    |   6 +
 .../server/debugging/launch/NbLaunchDelegate.java  |   9 +
 .../server/debugging/ni/NILocationVisualizer.java  | 411 +++++++++++++++++++++
 .../java/lsp/server/files/OpenedDocuments.java     |  98 +++++
 .../server/protocol/DecorationRenderOptions.java   | 313 ++++++++++++++++
 .../lsp/server/protocol/NbCodeClientWrapper.java   |  15 +
 .../lsp/server/protocol/NbCodeLanguageClient.java  |  25 ++
 .../java/lsp/server/protocol/NbLspServer.java      |   5 +
 .../lsp/server/protocol/OverviewRulerLane.java     |  53 +++
 .../modules/java/lsp/server/protocol/Server.java   |  26 ++
 .../protocol/SetTextEditorDecorationParams.java    | 139 +++++++
 .../server/protocol/TextDocumentServiceImpl.java   |  30 +-
 .../ThemableDecorationAttachmentRenderOptions.java | 229 ++++++++++++
 .../protocol/ThemableDecorationRenderOptions.java  | 383 +++++++++++++++++++
 .../java/lsp/server/protocol/ThemeColor.java       |  81 ++++
 .../server/progress/TestProgressHandlerTest.java   |  18 +
 .../lsp/server/protocol/OverviewRulerLaneTest.java |  38 ++
 .../java/lsp/server/protocol/ServerTest.java       | 120 ++++++
 java/java.lsp.server/vscode/src/extension.ts       |  31 +-
 java/java.lsp.server/vscode/src/protocol.ts        |  25 +-
 .../nbproject/project.xml                          |   2 +-
 .../debugger/displayer/JavaFrameDisplayer.java     |   3 +
 32 files changed, 2683 insertions(+), 25 deletions(-)

diff --git a/cpplite/cpplite.debugger/nbproject/project.xml b/cpplite/cpplite.debugger/nbproject/project.xml
index 48ff741..380bbb0 100644
--- a/cpplite/cpplite.debugger/nbproject/project.xml
+++ b/cpplite/cpplite.debugger/nbproject/project.xml
@@ -66,7 +66,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>0</release-version>
-                        <specification-version>0.1</specification-version>
+                        <specification-version>0.2</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebugger.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebugger.java
index 0c41c83..07c78f1 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebugger.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebugger.java
@@ -29,7 +29,9 @@ import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.EventListener;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -59,6 +61,9 @@ import org.netbeans.modules.cpplite.debugger.breakpoints.CPPLiteBreakpoint;
 import org.netbeans.modules.nativeexecution.api.ExecutionEnvironmentFactory;
 import org.netbeans.modules.nativeexecution.api.pty.Pty;
 import org.netbeans.modules.nativeexecution.api.pty.PtySupport;
+import org.netbeans.modules.nativeimage.api.Location;
+import org.netbeans.modules.nativeimage.api.SourceInfo;
+import org.netbeans.modules.nativeimage.api.Symbol;
 import org.netbeans.spi.debugger.ContextProvider;
 import org.netbeans.spi.debugger.DebuggerEngineProvider;
 import org.netbeans.spi.debugger.SessionProvider;
@@ -421,6 +426,140 @@ public final class CPPLiteDebugger {
         return versionRecord.command().getConsoleStream();
     }
 
+    public List<Location> listLocations(String filePath) {
+        MIRecord lines;
+        try {
+            lines = sendAndGet("-symbol-list-lines " + filePath);
+        } catch (InterruptedException ex) {
+            return null;
+        }
+        MIValue linesValue = lines.results().valueOf("lines");
+        if (linesValue instanceof MITList) {
+            MITList lineList = (MITList) linesValue;
+            int size = lineList.size();
+            List<Location> locations = new ArrayList<>(size);
+            Location.Builder locationBuilder = Location.newBuilder();
+            for (MITListItem item : lineList) {
+                if (item instanceof MITList) {
+                    MITList il = (MITList) item;
+                    String pcs = il.getConstValue("pc", null);
+                    if (pcs != null) {
+                        long pc;
+                        if (pcs.startsWith("0x")) {
+                            pcs = pcs.substring(2);
+                            pc = Long.parseUnsignedLong(pcs, 16);
+                        } else {
+                            pc = Long.parseUnsignedLong(pcs);
+                        }
+                        locationBuilder.pc(pc);
+                    } else {
+                        locationBuilder.pc(0);
+                    }
+                    String lineStr = il.getConstValue("line", null);
+                    if (lineStr != null) {
+                        locationBuilder.line(Integer.parseInt(lineStr));
+                    } else {
+                        locationBuilder.line(0);
+                    }
+                    locations.add(locationBuilder.build());
+                }
+            }
+            return locations;
+        } else {
+            return null;
+        }
+    }
+
+    public Map<SourceInfo, List<Symbol>> listFunctions(String name, boolean includeNondebug, int maxResults) {
+        StringBuilder command = new StringBuilder("-symbol-info-functions");
+        if (name != null) {
+            command.append(" --name ");
+            command.append(name);
+        }
+        if (includeNondebug) {
+            command.append(" --include-nondebug");
+        }
+        if (maxResults > 0) {
+            command.append(" --max-results ");
+            command.append(maxResults);
+        }
+        return listSymbols(command.toString());
+    }
+
+    public Map<SourceInfo, List<Symbol>> listVariables(String name, boolean includeNondebug, int maxResults) {
+        StringBuilder command = new StringBuilder("-symbol-info-variables");
+        if (name != null) {
+            command.append(" --name ");
+            command.append(name);
+        }
+        if (includeNondebug) {
+            command.append(" --include-nondebug");
+        }
+        if (maxResults > 0) {
+            command.append(" --max-results ");
+            command.append(maxResults);
+        }
+        return listSymbols(command.toString());
+    }
+
+    private Map<SourceInfo, List<Symbol>> listSymbols(String command) {
+        MIRecord result;
+        try {
+            result = sendAndGet(command);
+        } catch (InterruptedException ex) {
+            return null;
+        }
+        MIValue allSymbolsValue = result.results().valueOf("symbols");
+        if (allSymbolsValue instanceof MITList) {
+            MITList allSymbolsList = (MITList) allSymbolsValue;
+            if (allSymbolsList.size() == 0) {
+                return Collections.emptyMap();
+            }
+            MIValue debugValue = allSymbolsList.valueOf("debug");
+            if (debugValue instanceof MITList) {
+                MITList debugList = (MITList) debugValue;
+                int size = debugList.size();
+                Map<SourceInfo, List<Symbol>> sourceSymbols = new LinkedHashMap<>(size);
+                for (MITListItem debugItem : debugList) {
+                    if (debugItem instanceof MITList) {
+                        MITList sourceWithSymbols = (MITList) debugItem;
+                        SourceInfo.Builder sourceBuilder = SourceInfo.newBuilder();
+                        String filename = sourceWithSymbols.getConstValue("filename", null);
+                        String fullname = sourceWithSymbols.getConstValue("fullname", null);
+                        sourceBuilder.fileName(filename);
+                        sourceBuilder.fullName(fullname);
+                        SourceInfo source = sourceBuilder.build();
+                        MIValue symbolsValue = sourceWithSymbols.valueOf("symbols");
+                        if (symbolsValue instanceof MITList) {
+                            MITList symbolsList = (MITList) symbolsValue;
+                            int symbolsSize = symbolsList.size();
+                            List<Symbol> symbols = new ArrayList<>(symbolsSize);
+                            for (MITListItem symbolItem : symbolsList) {
+                                if (symbolItem instanceof MITList) {
+                                    MITList symbolList = (MITList) symbolItem;
+                                    String name = symbolList.getConstValue("name");
+                                    String type = symbolList.getConstValue("type", null);
+                                    String description = symbolList.getConstValue("description", null);
+                                    Symbol.Builder symbolBuilder = Symbol.newBuilder();
+                                    symbolBuilder.name(name);
+                                    symbolBuilder.type(type);
+                                    symbolBuilder.description(description);
+                                    symbols.add(symbolBuilder.build());
+                                }
+                            }
+                            sourceSymbols.put(source, symbols);
+                        }
+                    }
+                }
+                return Collections.unmodifiableMap(sourceSymbols);
+            } else {
+                return null;
+            }
+        } else {
+            return null;
+        }
+    }
+
     ContextProvider getContextProvider() {
         return contextProvider;
     }
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ni/NIDebuggerProviderImpl.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ni/NIDebuggerProviderImpl.java
index b136c33..4c75cb8 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ni/NIDebuggerProviderImpl.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ni/NIDebuggerProviderImpl.java
@@ -20,6 +20,7 @@ package org.netbeans.modules.cpplite.debugger.ni;
 
 import java.io.File;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Semaphore;
 import java.util.function.Consumer;
@@ -32,6 +33,9 @@ import org.netbeans.modules.cpplite.debugger.CPPFrame;
 import org.netbeans.modules.cpplite.debugger.CPPLiteDebugger;
 import org.netbeans.modules.cpplite.debugger.CPPLiteDebuggerConfig;
 import org.netbeans.modules.cpplite.debugger.CPPThread;
+import org.netbeans.modules.nativeimage.api.Location;
+import org.netbeans.modules.nativeimage.api.SourceInfo;
+import org.netbeans.modules.nativeimage.api.Symbol;
 import org.netbeans.modules.nativeimage.api.debug.EvaluateException;
 import org.netbeans.modules.nativeimage.api.debug.NIFrame;
 import org.netbeans.modules.nativeimage.api.debug.NILineBreakpointDescriptor;
@@ -180,4 +184,20 @@ public class NIDebuggerProviderImpl implements NIDebuggerProvider {
     public String getVersion() {
         return debugger.getVersion();
     }
+
+    @Override
+    public List<Location> listLocations(String filePath) {
+        return debugger.listLocations(filePath);
+    }
+
+    @Override
+    public Map<SourceInfo, List<Symbol>> listFunctions(String name, boolean includeNondebug, int maxResults) {
+        return debugger.listFunctions(name, includeNondebug, maxResults);
+    }
+
+    @Override
+    public Map<SourceInfo, List<Symbol>> listVariables(String name, boolean includeNondebug, int maxResults) {
+        return debugger.listVariables(name, includeNondebug, maxResults);
+    }
+
 }
diff --git a/ide/nativeimage.api/manifest.mf b/ide/nativeimage.api/manifest.mf
index fdfa01c..bad1b2a 100644
--- a/ide/nativeimage.api/manifest.mf
+++ b/ide/nativeimage.api/manifest.mf
@@ -2,5 +2,5 @@ Manifest-Version: 1.0
 AutoUpdate-Show-In-Client: false
 OpenIDE-Module: org.netbeans.modules.nativeimage.api/0
 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/nativeimage/api/Bundle.properties
-OpenIDE-Module-Specification-Version: 0.1
+OpenIDE-Module-Specification-Version: 0.2
 
diff --git a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/Location.java b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/Location.java
new file mode 100644
index 0000000..4771117
--- /dev/null
+++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/Location.java
@@ -0,0 +1,140 @@
+/*
+ * 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.nativeimage.api;
+
+/**
+ * A location in the debuggee program.
+ *
+ * @since 0.2
+ */
+public final class Location {
+
+    private final long pc;
+    private final int line;
+    private final int column;
+
+    private Location(long pc, int line, int column) {
+        this.pc = pc;
+        this.line = line;
+        this.column = column;
+    }
+
+    /**
+     * Get the program counter position.
+     *
+     * @since 0.2
+     */
+    public long getPC() {
+        return pc;
+    }
+
+    /**
+     * Get 1-based line number. Returns 0 when the line is not defined.
+     *
+     * @since 0.2
+     */
+    public int getLine() {
+        return line;
+    }
+
+    /**
+     * Get 1-based column number. Returns 0 when the column is not defined.
+     *
+     * @since 0.2
+     */
+    public int getColumn() {
+        return column;
+    }
+
+    /**
+     * Creates a builder to build a new {@link Location}.
+     *
+     * @since 0.2
+     */
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    @Override
+    public String toString() {
+        return "Location{" + "pc=" + pc + ", line=" + line + ", column=" + column + '}';
+    }
+
+    /**
+     * Location's builder.
+     *
+     * @since 0.2
+     */
+    public static final class Builder {
+
+        private long pc;
+        private int line;
+        private int column;
+
+        Builder() {}
+
+        /**
+         * Set a program counter location in the native binary.
+         *
+         * @since 0.2
+         */
+        public void pc(long pc) {
+            this.pc = pc;
+        }
+
+        /**
+         * Set an 1-based line. The line is treated as unknown when 0.
+         *
+         * @since 0.2
+         */
+        public void line(int line) {
+            if (line < 0) {
+                throw new IllegalArgumentException("Line must not be negative");
+            }
+            this.line = line;
+        }
+
+        /**
+         * Set an 1-based column. The column is treated as unknown when 0.
+         *
+         * @since 0.2
+         */
+        public void column(int column) {
+            if (column < 0) {
+                throw new IllegalArgumentException("Column must not be negative");
+            }
+            this.column = column;
+        }
+
+        /**
+         * Build the {@link Location} object.
+         *
+         * @since 0.2
+         */
+        public Location build() {
+            if (column > 0 && line == 0) {
+                throw new IllegalStateException("Column can not be defined without a line.");
+            }
+            if (line == 0 && pc == 0) {
+                throw new IllegalStateException("No location information is defined.");
+            }
+            return new Location(pc, line, column);
+        }
+    }
+}
diff --git a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/SourceInfo.java b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/SourceInfo.java
new file mode 100644
index 0000000..5855e77
--- /dev/null
+++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/SourceInfo.java
@@ -0,0 +1,138 @@
+/*
+ * 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.nativeimage.api;
+
+import java.util.Objects;
+
+/**
+ * A source file information in the debuggee program.
+ *
+ * @since 0.2
+ */
+public final class SourceInfo {
+
+    private final String fileName;
+    private final String fullName;
+
+    private SourceInfo(String fileName, String fullName) {
+        this.fileName = fileName;
+        this.fullName = fullName;
+    }
+
+    /**
+     * Get the file name, or <code>null</code> when unknown. May return a relative path.
+     *
+     * @since 0.2
+     */
+    public String getFileName() {
+        return fileName;
+    }
+
+    /**
+     * Get the full file name, or <code>null</code> when unknown. Returns an absolute path, if any.
+     *
+     * @since 0.2
+     */
+    public String getFullName() {
+        return fullName;
+    }
+
+    /**
+     * Creates a builder to build a new {@link SourceInfo}.
+     *
+     * @since 0.2
+     */
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 17 * hash + Objects.hashCode(this.fileName);
+        hash = 17 * hash + Objects.hashCode(this.fullName);
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final SourceInfo other = (SourceInfo) obj;
+        if (!Objects.equals(this.fileName, other.fileName)) {
+            return false;
+        }
+        if (!Objects.equals(this.fullName, other.fullName)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "SourceInfo{" + "fileName=" + fileName + ", fullName=" + fullName + '}';
+    }
+
+    /**
+     * Symbol's builder.
+     *
+     * @since 0.2
+     */
+    public static final class Builder {
+
+        private String fileName;
+        private String fullName;
+
+        Builder() {}
+
+        /**
+         * Set a file name. It may be a relative path.
+         *
+         * @since 0.2
+         */
+        public void fileName(String fileName) {
+            this.fileName = fileName;
+        }
+
+        /**
+         * Set a full file name. It needs to be an absolute path.
+         *
+         * @since 0.2
+         */
+        public void fullName(String fullName) {
+            this.fullName = fullName;
+        }
+
+        /**
+         * Build the {@link Symbol} object.
+         *
+         * @since 0.2
+         */
+        public SourceInfo build() {
+            return new SourceInfo(fileName, fullName);
+        }
+    }
+}
diff --git a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/Symbol.java b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/Symbol.java
new file mode 100644
index 0000000..cf18960
--- /dev/null
+++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/Symbol.java
@@ -0,0 +1,131 @@
+/*
+ * 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.nativeimage.api;
+
+/**
+ * A symbol in the debuggee program.
+ *
+ * @since 0.2
+ */
+public final class Symbol {
+
+    private final String name;
+    private final String type;
+    private final String description;
+
+    private Symbol(String name, String type, String description) {
+        this.name = name;
+        this.type = type;
+        this.description = description;
+    }
+
+    /**
+     * Get the symbol name. Never <code>null<code>.
+     *
+     * @since 0.2
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Get the symbol type.
+     *
+     * @since 0.2
+     */
+    public String getType() {
+        return type;
+    }
+
+    /**
+     * Get the symbol description.
+     *
+     * @since 0.2
+     */
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * Creates a builder to build a new {@link Symbol}.
+     *
+     * @since 0.2
+     */
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    @Override
+    public String toString() {
+        return "Symbol{" + "name=" + name + ", type=" + type + ", description=" + description + '}';
+    }
+
+    /**
+     * Symbol's builder.
+     *
+     * @since 0.2
+     */
+    public static final class Builder {
+
+        private String name;
+        private String type;
+        private String description;
+
+        Builder() {}
+
+        /**
+         * Set the symbol name. The name must be defined.
+         *
+         * @since 0.2
+         */
+        public void name(String name) {
+            this.name = name;
+        }
+
+        /**
+         * Set the symbol type.
+         *
+         * @since 0.2
+         */
+        public void type(String type) {
+            this.type = type;
+        }
+
+        /**
+         * Set the symbol description.
+         *
+         * @since 0.2
+         */
+        public void description(String description) {
+            this.description = description;
+        }
+
+        /**
+         * Build the {@link Symbol} object.
+         *
+         * @since 0.2
+         */
+        public Symbol build() {
+            if (name == null) {
+                throw new IllegalArgumentException("Name must be defined.");
+            }
+            return new Symbol(name, type, description);
+        }
+    }
+}
diff --git a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIDebugger.java b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIDebugger.java
index f980655..b5042c0 100644
--- a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIDebugger.java
+++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIDebugger.java
@@ -20,13 +20,18 @@ package org.netbeans.modules.nativeimage.api.debug;
 
 import java.io.File;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 
+import org.netbeans.api.annotations.common.CheckForNull;
 import org.netbeans.api.debugger.Breakpoint;
 import org.netbeans.api.debugger.DebuggerEngine;
 import org.netbeans.api.extexecution.ExecutionDescriptor;
+import org.netbeans.modules.nativeimage.api.Location;
+import org.netbeans.modules.nativeimage.api.SourceInfo;
+import org.netbeans.modules.nativeimage.api.Symbol;
 import org.netbeans.modules.nativeimage.spi.debug.NIDebuggerProvider;
 import org.netbeans.modules.nativeimage.spi.debug.NIDebuggerServiceProvider;
 import org.netbeans.modules.nativeimage.spi.debug.filters.FrameDisplayer;
@@ -166,6 +171,49 @@ public final class NIDebugger {
     }
 
     /**
+     * Get a list of locations for a given file path.
+     *
+     * @param filePath a file path
+     * @return list of locations, or <code>null</code> when there's no location
+     *         information about such file.
+     * @since 0.2
+     */
+    @CheckForNull
+    public List<Location> listLocations(String filePath) {
+        return provider.listLocations(filePath);
+    }
+
+    /**
+     * Get functions of a given name.
+     *
+     * @param name a name pattern
+     * @param includeNondebug include also symbols from the symbol table
+     * @param maxResults maximum number of results
+     * @return map of source information and their symbols, or <code>null</code>
+     *         when there are no matching symbols.
+     * @since 0.2
+     */
+    @CheckForNull
+    public Map<SourceInfo, List<Symbol>> listFunctions(String name, boolean includeNondebug, int maxResults) {
+        return provider.listFunctions(name, includeNondebug, maxResults);
+    }
+
+    /**
+     * Get variables of a given name.
+     *
+     * @param name a name pattern
+     * @param includeNondebug include also symbols from the symbol table
+     * @param maxResults maximum number of results
+     * @return map of source information and their symbols, or <code>null</code>
+     *         when there are no matching symbols.
+     * @since 0.2
+     */
+    @CheckForNull
+    public Map<SourceInfo, List<Symbol>> listVariables(String name, boolean includeNondebug, int maxResults) {
+        return provider.listVariables(name, includeNondebug, maxResults);
+    }
+
+    /**
      * A builder that creates a Native Image debugger with optional displayers.
      *
      * @since 1.0
diff --git a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/spi/debug/NIDebuggerProvider.java b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/spi/debug/NIDebuggerProvider.java
index c2ff425..8868123 100644
--- a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/spi/debug/NIDebuggerProvider.java
+++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/spi/debug/NIDebuggerProvider.java
@@ -20,12 +20,16 @@ package org.netbeans.modules.nativeimage.spi.debug;
 
 import java.io.File;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.function.Consumer;
+
 import org.netbeans.api.debugger.Breakpoint;
 import org.netbeans.api.debugger.DebuggerEngine;
-
 import org.netbeans.api.extexecution.ExecutionDescriptor;
+import org.netbeans.modules.nativeimage.api.Location;
+import org.netbeans.modules.nativeimage.api.SourceInfo;
+import org.netbeans.modules.nativeimage.api.Symbol;
 import org.netbeans.modules.nativeimage.api.debug.NIFrame;
 import org.netbeans.modules.nativeimage.api.debug.NILineBreakpointDescriptor;
 import org.netbeans.modules.nativeimage.api.debug.NIVariable;
@@ -120,4 +124,24 @@ public interface NIDebuggerProvider {
      * @since 1.0
      */
     String getVersion();
+
+    /**
+     * Provide a list of locations for a given file path.
+     *
+     * @param filePath a file path
+     * @return list of locations, or <code>null</code> when there's no location
+     *         information about such file.
+     * @since 0.2
+     */
+    default List<Location> listLocations(String filePath) {
+        return null;
+    }
+
+    default Map<SourceInfo, List<Symbol>> listFunctions(String name, boolean includeNondebug, int maxResults) {
+        return null;
+    }
+
+    default Map<SourceInfo, List<Symbol>> listVariables(String name, boolean includeNondebug, int maxResults) {
+        return null;
+    }
 }
diff --git a/java/java.lsp.server/nbproject/project.xml b/java/java.lsp.server/nbproject/project.xml
index 4bb0e49..a34b540 100644
--- a/java/java.lsp.server/nbproject/project.xml
+++ b/java/java.lsp.server/nbproject/project.xml
@@ -344,7 +344,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>0</release-version>
-                        <specification-version>0.1</specification-version>
+                        <specification-version>0.2</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/LspServerState.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/LspServerState.java
index bb66851..4ad2d08 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/LspServerState.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/LspServerState.java
@@ -23,6 +23,7 @@ import java.util.concurrent.CancellationException;
 import java.util.concurrent.CompletableFuture;
 import org.eclipse.lsp4j.services.TextDocumentService;
 import org.netbeans.api.project.Project;
+import org.netbeans.modules.java.lsp.server.files.OpenedDocuments;
 import org.openide.filesystems.FileObject;
 
 /**
@@ -73,4 +74,9 @@ public interface LspServerState {
      * @return TextDocumentService
      */
     public TextDocumentService getTextDocumentService();
+
+    /**
+     * Get documents opened by the LSP client.
+     */
+    public OpenedDocuments getOpenedDocuments();
 }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java
index c6304c0..9803105 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java
@@ -62,6 +62,7 @@ import org.netbeans.api.project.ProjectUtils;
 import org.netbeans.modules.java.lsp.server.Utils;
 import org.netbeans.modules.java.lsp.server.debugging.DebugAdapterContext;
 import org.netbeans.modules.java.lsp.server.debugging.NbSourceProvider;
+import org.netbeans.modules.java.lsp.server.debugging.ni.NILocationVisualizer;
 import org.netbeans.modules.java.lsp.server.debugging.utils.ErrorUtilities;
 import org.netbeans.modules.java.lsp.server.progress.OperationContext;
 import org.netbeans.modules.java.lsp.server.progress.ProgressOperationEvent;
@@ -364,6 +365,7 @@ public abstract class NbLaunchDelegate {
 
     private static void startNativeDebug(File nativeImageFile, List<String> args, String miDebugger, DebugAdapterContext context, ExecutionDescriptor executionDescriptor, CompletableFuture<Void> launchFuture, ActionProgress debugProgress) {
         AtomicReference<NbDebugSession> debugSessionRef = new AtomicReference<>();
+        CompletableFuture<Void> finished = new CompletableFuture<>();
         NIDebugger niDebugger;
         try {
             niDebugger = NIDebugRunner.start(nativeImageFile, args, miDebugger, null, null, executionDescriptor, engine -> {
@@ -373,6 +375,12 @@ public abstract class NbLaunchDelegate {
                 context.setDebugSession(debugSession);
                 launchFuture.complete(null);
                 context.getConfigurationSemaphore().waitForConfigurationDone();
+                session.addPropertyChangeListener(Session.PROP_CURRENT_LANGUAGE, evt -> {
+                    if (evt.getNewValue() == null) {
+                        // No current language => finished
+                        finished.complete(null);
+                    }
+                });
             });
         } catch (IllegalStateException ex) {
             ErrorUtilities.completeExceptionally(launchFuture,
@@ -383,6 +391,7 @@ public abstract class NbLaunchDelegate {
         }
         NbDebugSession debugSession = debugSessionRef.get();
         debugSession.setNIDebugger(niDebugger);
+        NILocationVisualizer.handle(nativeImageFile, niDebugger, finished, context.getLspSession().getLspServer().getOpenedDocuments());
     }
 
     @NonNull
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/ni/NILocationVisualizer.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/ni/NILocationVisualizer.java
new file mode 100644
index 0000000..13cb437
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/ni/NILocationVisualizer.java
@@ -0,0 +1,411 @@
+/*
+ * 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.debugging.ni;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.LineMap;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.SourcePositions;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.tools.Diagnostic;
+
+import org.eclipse.lsp4j.Position;
+import org.eclipse.lsp4j.Range;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.source.CompilationController;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.Task;
+import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.modules.java.lsp.server.files.OpenedDocuments;
+import org.netbeans.modules.java.lsp.server.progress.OperationContext;
+import org.netbeans.modules.java.lsp.server.protocol.DecorationRenderOptions;
+import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient;
+import org.netbeans.modules.java.lsp.server.protocol.SetTextEditorDecorationParams;
+import org.netbeans.modules.nativeimage.api.Location;
+import org.netbeans.modules.nativeimage.api.SourceInfo;
+import org.netbeans.modules.nativeimage.api.Symbol;
+import org.netbeans.modules.nativeimage.api.debug.NIDebugger;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Lookup;
+
+/**
+ *
+ * @author Martin Entlicher
+ */
+public final class NILocationVisualizer implements Consumer<String> {
+
+    private final File niFileSources;
+    private final NIDebugger niDebugger;
+    private final NbCodeLanguageClient client;
+    private final OpenedDocuments openedDocuments;
+    private Set<String> decorationKeys = new HashSet<>(); 
+
+    private NILocationVisualizer(File nativeImageFile, NIDebugger niDebugger, CompletableFuture<Void> finished, OpenedDocuments openedDocuments) {
+        this.niFileSources = getNativeSources(nativeImageFile);
+        this.niDebugger = niDebugger;
+        OperationContext ctx = OperationContext.find(Lookup.getDefault());
+        this.client = ctx.getClient();
+        this.openedDocuments = openedDocuments;
+        finished.thenRun(() -> {
+            openedDocuments.removeOpenedConsumer(this);
+            Set<String> keys;
+            synchronized (this) {
+                keys = decorationKeys;
+                decorationKeys = null;
+            }
+            for (String key : keys) {
+                client.disposeTextEditorDecoration(key);
+            }
+        });
+    }
+
+    private static File getNativeSources(File niFile) {
+        File sources = new File(niFile.getParentFile(), "sources");
+        if (sources.isDirectory()) {
+            return sources;
+        } else {
+            return null;
+        }
+    }
+
+    public static void handle(File nativeImageFile, NIDebugger niDebugger, CompletableFuture<Void> finished, OpenedDocuments openedDocuments) {
+        openedDocuments.addOpenedConsumer(new NILocationVisualizer(nativeImageFile, niDebugger, finished, openedDocuments));
+    }
+
+    @Override
+    public void accept(final String uri) {
+        List<Location> locations = getLocations(uri);
+        if (locations != null) {
+            DecorationRenderOptions decorationOptions = new DecorationRenderOptions();
+            decorationOptions.setColor(Either.forLeft("gray"));
+            CompletableFuture<String> decorationFuture = client.createTextEditorDecoration(decorationOptions);
+            decorationFuture.thenAccept(key -> {
+                Intervals intervals = getCodeIntervals(uri);
+                Range[] ranges = locationsToRanges(locations, intervals);
+                client.setTextEditorDecoration(new SetTextEditorDecorationParams(key, uri, ranges));
+                boolean disposed;
+                synchronized (this) {
+                    disposed = decorationKeys == null; // Disposed in the mean time
+                    if (!disposed) {
+                        decorationKeys.add(key);
+                    }
+                }
+                if (disposed) {
+                    client.disposeTextEditorDecoration(key);
+                }
+            });
+        }
+    }
+
+    private final List<Location> getLocations(String uri) {
+        List<Location> locations = niDebugger.listLocations(uri);
+        if (locations == null && niFileSources != null) {
+            String relPath = getRelativePath(uri);
+            if (relPath != null) {
+                File sourcesFile = new File(niFileSources, relPath);
+                String filePath = sourcesFile.getAbsolutePath();
+                locations = niDebugger.listLocations(filePath);
+            }
+        }
+        return locations;
+    }
+
+    /**
+     * The ranges are sub-intervals of <code>intervals</code> that do not contain <code>locations</code>.
+     */
+    private Range[] locationsToRanges(List<Location> locations, Intervals intervals) {
+        locations.sort((l1, l2) -> l1.getLine() - l2.getLine());
+        List<Range> ranges = new ArrayList<>();
+        int lastLine = intervals.getFirst();
+        int maxLine = intervals.getLast();
+        for (Location l : locations) {
+            int line = l.getLine();
+            if (line == 0) {  // Unknown line location
+                continue;
+            }
+            if (lastLine < line) {
+                int start = lastLine;
+                int end = line - 1;
+                do {
+                    while (!intervals.contains(start) && start < maxLine) {
+                        start++;
+                    }
+                    if (start > end) {
+                        break;
+                    }
+                    int rangeEnd = start;
+                    while (rangeEnd < end && intervals.contains(rangeEnd)) {
+                        rangeEnd++;
+                    }
+                    int startCol = intervals.getFirstColumn(start);
+                    int endCol = intervals.getLastColumn(rangeEnd);
+                    int endLine = rangeEnd;
+                    if (endCol == -1) { // end is the end of line
+                        endLine++;
+                        endCol = 1;
+                    }
+                    ranges.add(new Range(new Position(start-1, startCol-1), new Position(endLine-1, endCol-1))); // Position is 0-based
+                    start = rangeEnd + 1;
+                } while (start <= end);
+            }
+            lastLine = line + 1;
+        }
+        if (lastLine < maxLine) {
+            ranges.add(new Range(new Position(lastLine, 0), new Position(maxLine, 0)));
+        }
+        for (String variable : intervals.variables.keySet()) {
+            Map<SourceInfo, List<Symbol>> listVariables = niDebugger.listVariables(variable, true, -1);
+            if (listVariables != null && listVariables.isEmpty()) {
+                Interval interval = intervals.variables.get(variable);
+                ranges.add(new Range(new Position(interval.l1-1, interval.c1-1), new Position(interval.l2-1, interval.c2-1))); // Position is 0-based
+            }
+        }
+        return ranges.toArray(new Range[ranges.size()]);
+    }
+
+    private static String r2s(List<Range> ranges) {
+        StringBuilder sb = new StringBuilder("[");
+        for (Range r : ranges) {
+            sb.append(r.getStart().getLine() + " - " + r.getEnd().getLine());
+            sb.append(", ");
+        }
+        if (sb.length() > 3) {
+            sb.delete(sb.length() - 2, sb.length());
+        }
+        sb.append(']');
+        return sb.toString();
+    }
+
+    private static String getRelativePath(String url) {
+        FileObject fo;
+        try {
+            fo = URLMapper.findFileObject(new URL(url));
+        } catch (MalformedURLException e) {
+            return null;
+        }
+        if (fo == null) {
+            return null;
+        }
+        ClassPath cp = ClassPath.getClassPath (fo, ClassPath.SOURCE);
+        if (cp == null) {
+            cp = ClassPath.getClassPath (fo, ClassPath.COMPILE);
+        }
+        if (cp == null) {
+            return null;
+        }
+        return cp.getResourceName (fo, '/', true);
+    }
+
+    private static Intervals getCodeIntervals(String url) {
+        FileObject fo;
+        try {
+            fo = URLMapper.findFileObject(new URL(url));
+        } catch (MalformedURLException e) {
+            return null;
+        }
+        JavaSource source = JavaSource.forFileObject(fo);
+        Intervals intervals = new Intervals();
+        try {
+            source.runWhenScanFinished(new Task<CompilationController>() {
+                @Override
+                public void run(CompilationController cc) throws Exception {
+                    List<? extends TypeElement> topLevelElements = cc.getTopLevelElements();
+                    TreeUtilities treeUtilities = cc.getTreeUtilities();
+                    SourcePositions sourcePositions = cc.getTrees().getSourcePositions();
+                    LineMap lineMap = cc.getCompilationUnit().getLineMap();
+                    for (Element element : topLevelElements) {
+                        Tree tree = cc.getTrees().getTree(element);
+                        if (tree.getKind() ==  Tree.Kind.CLASS) {
+                            List<? extends Tree> members = ((ClassTree) tree).getMembers();
+                            for (Tree member : members) {
+                                Tree t = null;
+                                if (member.getKind() == Tree.Kind.METHOD) {
+                                    t = ((MethodTree) member).getBody();
+                                } else if (member.getKind() == Tree.Kind.BLOCK) {
+                                    t = member;
+                                }
+                                if (t != null) {
+                                    Interval interval = createInterval(cc.getCompilationUnit(), sourcePositions, lineMap, t);
+                                    if (interval != null) {
+                                        intervals.add(interval);
+                                    }
+                                } else if (member.getKind() == Tree.Kind.VARIABLE) {
+                                    VariableTree variable = (VariableTree) member;
+                                    boolean isStatic = variable.getModifiers().getFlags().contains(Modifier.STATIC);
+                                    if (isStatic) {
+                                        String name = variable.getName().toString();
+                                        name = cc.getElementUtilities().getElementName(element, true) + "::" + name;
+                                        Interval interval = createInterval(cc.getCompilationUnit(), sourcePositions, lineMap, member);
+                                        if (interval != null) {
+                                            intervals.addVariable(name, interval);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }, true);
+        } catch (IOException ex) {
+        }
+        return intervals;
+    }
+
+    private static Interval createInterval(CompilationUnitTree cut, SourcePositions sourcePositions, LineMap lineMap, Tree tree) {
+        long start = sourcePositions.getStartPosition(cut, tree);
+        long end = sourcePositions.getEndPosition(cut, tree);
+        if (start != Diagnostic.NOPOS && end != Diagnostic.NOPOS) {
+            int line1 = (int) lineMap.getLineNumber(start);
+            int col1 = (int) lineMap.getColumnNumber(start);
+            int line2 = (int) lineMap.getLineNumber(end);
+            int col2 = (int) lineMap.getColumnNumber(end);
+            return new Interval(line1, col1, line2, col2);
+        } else {
+            return null;
+        }
+    }
+
+    private static final class Intervals {
+
+        private final List<Interval> intervals = new ArrayList<>();
+        private final Map<String, Interval> variables = new HashMap<>();
+
+        void add(Interval i) {
+            int index = intervals.size();
+            for (int idx = 0; idx < intervals.size(); idx++) {
+                Interval ii = intervals.get(idx);
+                if (i.l1 < ii.l1) {
+                    index = idx;
+                    break;
+                }
+            }
+            intervals.add(index, i);
+        }
+
+        void addVariable(String name, Interval i) {
+            variables.put(name, i);
+        }
+
+        boolean contains(int n) {
+            for (Interval i : intervals) {
+                if (i.contains(n)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        int getFirst() {
+            if (intervals.size() > 0) {
+                return intervals.get(0).l1;
+            } else {
+                return 0;
+            }
+        }
+
+        int getLast() {
+            int s = intervals.size();
+            if (s > 0) {
+                return intervals.get(s - 1).l2;
+            } else {
+                return -1;
+            }
+        }
+
+        private int getFirstColumn(int line) {
+            for (Interval i : intervals) {
+                if (i.contains(line)) {
+                    return i.firstColumnOn(line);
+                }
+            }
+            return 1;
+        }
+
+        private int getLastColumn(int line) {
+            for (Interval i : intervals) {
+                if (i.contains(line)) {
+                    return i.lastColumnOn(line);
+                }
+            }
+            return -1;
+        }
+    }
+
+    private static final class Interval {
+
+        private final int l1;
+        private final int c1;
+        private final int l2;
+        private final int c2;
+
+        Interval(int l1, int c1, int l2, int c2) {
+            assert l1 <= l2;
+            this.l1 = l1;
+            this.c1 = c1;
+            this.l2 = l2;
+            this.c2 = c2;
+        }
+
+        private boolean contains(int l) {
+            return l1 <= l && l <= l2;
+        }
+
+        private int firstColumnOn(int l) {
+            if (l == l1) {
+                return c1;
+            } else {
+                return 1;
+            }
+        }
+
+        private int lastColumnOn(int l) {
+            if (l == l2) {
+                return c2;
+            } else {
+                return -1;
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "Interval<" + l1 + ", " + l2 + '>';
+        }
+
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/files/OpenedDocuments.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/files/OpenedDocuments.java
new file mode 100644
index 0000000..b8b1826
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/files/OpenedDocuments.java
@@ -0,0 +1,98 @@
+/*
+ * 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.files;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+import javax.swing.text.Document;
+
+/**
+ * Gathers documents opened by the LSP client.
+ *
+ * @author Martin Entlicher
+ */
+public final class OpenedDocuments {
+
+    private final Map<String, Document> openedDocuments = new ConcurrentHashMap<>();
+    private final List<Consumer<String>> openedConsumers = new ArrayList<>();
+
+    /**
+     * Get URIs of opened documents.
+     * @return a collection of URIs of opened documents.
+     */
+    public Collection<String> getUris() {
+        return Collections.unmodifiableSet(openedDocuments.keySet());
+    }
+
+    /**
+     * Get an opened document from an URI.
+     * @param uri an URI
+     * @return an opened document from the provided URI, or <code>null</code>
+     * when no document was opened from the URI.
+     */
+    public Document getDocument(String uri) {
+        return openedDocuments.get(uri);
+    }
+
+    /**
+     * Notify that a document was opened from an URI.
+     */
+    public void notifyOpened(String uri, Document doc) {
+        openedDocuments.put(uri, doc);
+        synchronized (openedConsumers) {
+            for (Consumer<String> c : openedConsumers) {
+                c.accept(uri);
+            }
+        }
+    }
+
+    /**
+     * Notify that a document was closed.
+     */
+    public void notifyClosed(String uri) {
+        openedDocuments.remove(uri);
+    }
+
+    /**
+     * Add a consumer to be notified with URIs of opened documents.
+     * The added consumer is notified with the already opened documents immediately.
+     */
+    public void addOpenedConsumer(Consumer<String> openedConsumer) {
+        synchronized (openedConsumers) {
+            openedConsumers.add(openedConsumer);
+        }
+        for (String uri : getUris()) {
+            openedConsumer.accept(uri);
+        }
+    }
+
+    /**
+     * Remove a consumer that was previously added.
+     */
+    public void removeOpenedConsumer(Consumer<String> openedConsumer) {
+        synchronized (openedConsumers) {
+            openedConsumers.remove(openedConsumer);
+        }
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/DecorationRenderOptions.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/DecorationRenderOptions.java
new file mode 100644
index 0000000..6105ac0
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/DecorationRenderOptions.java
@@ -0,0 +1,313 @@
+/*
+ * 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.protocol;
+
+import java.net.URI;
+
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
+import org.eclipse.xtext.xbase.lib.Pure;
+import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
+
+/**
+ *
+ * @author Martin Entlicher
+ */
+public final class DecorationRenderOptions {
+
+    private ThemableDecorationAttachmentRenderOptions after;
+    private Either<String, ThemeColor> backgroundColor;
+    private ThemableDecorationAttachmentRenderOptions before;
+    private String border;
+    private Either<String, ThemeColor> borderColor;
+    private String borderRadius;
+    private String borderSpacing;
+    private String borderStyle;
+    private String borderWidth;
+    private Either<String, ThemeColor> color;
+    private String cursor;
+    private ThemableDecorationRenderOptions dark;
+    private String fontStyle;
+    private String fontWeight;
+    private Either<String, URI> gutterIconPath;
+    private String gutterIconSize;
+    private boolean isWholeLine;
+    private String letterSpacing;
+    private ThemableDecorationRenderOptions light;
+    private String opacity;
+    private String outline;
+    private Either<String, ThemeColor> outlineColor;
+    private String outlineStyle;
+    private String outlineWidth;
+    private Either<String, ThemeColor> overviewRulerColor;
+    private OverviewRulerLane overviewRulerLane;
+    //private DecorationRangeBehavior rangeBehavior;
+    private String textDecoration;
+
+    @Pure
+    @Override
+    public String toString() {
+        ToStringBuilder b = new ToStringBuilder(this);
+        b.add("after", after);
+        b.add("backgroundColor", backgroundColor);
+        b.add("before", before);
+        b.add("border", border);
+        b.add("borderColor", borderColor);
+        b.add("borderRadius", borderRadius);
+        b.add("borderSpacing", borderSpacing);
+        b.add("borderStyle", borderStyle);
+        b.add("borderWidth", borderWidth);
+        b.add("color", color);
+        b.add("cursor", cursor);
+        b.add("dark", dark);
+        b.add("fontStyle", fontStyle);
+        b.add("fontWeight", fontWeight);
+        b.add("gutterIconPath", gutterIconPath);
+        b.add("gutterIconSize", gutterIconSize);
+        b.add("isWholeLine", isWholeLine);
+        b.add("letterSpacing", letterSpacing);
+        b.add("light", light);
+        b.add("opacity", opacity);
+        b.add("outline", outline);
+        b.add("outlineColor", outlineColor);
+        b.add("outlineStyle", outlineStyle);
+        b.add("outlineWidth", outlineWidth);
+        b.add("overviewRulerColor", overviewRulerColor);
+        b.add("overviewRulerLane", overviewRulerLane);
+        b.add("textDecoration", textDecoration);
+        return b.toString();
+    }
+
+    public ThemableDecorationAttachmentRenderOptions getAfter() {
+        return after;
+    }
+
+    public void setAfter(ThemableDecorationAttachmentRenderOptions after) {
+        this.after = after;
+    }
+
+    public Either<String, ThemeColor> getBackgroundColor() {
+        return backgroundColor;
+    }
+
+    public void setBackgroundColor(Either<String, ThemeColor> backgroundColor) {
+        this.backgroundColor = backgroundColor;
+    }
+
+    public ThemableDecorationAttachmentRenderOptions getBefore() {
+        return before;
+    }
+
+    public void setBefore(ThemableDecorationAttachmentRenderOptions before) {
+        this.before = before;
+    }
+
+    public String getBorder() {
+        return border;
+    }
+
+    public void setBorder(String border) {
+        this.border = border;
+    }
+
+    public Either<String, ThemeColor> getBorderColor() {
+        return borderColor;
+    }
+
+    public void setBorderColor(Either<String, ThemeColor> borderColor) {
+        this.borderColor = borderColor;
+    }
+
+    public String getBorderRadius() {
+        return borderRadius;
+    }
+
+    public void setBorderRadius(String borderRadius) {
+        this.borderRadius = borderRadius;
+    }
+
+    public String getBorderSpacing() {
+        return borderSpacing;
+    }
+
+    public void setBorderSpacing(String borderSpacing) {
+        this.borderSpacing = borderSpacing;
+    }
+
+    public String getBorderStyle() {
+        return borderStyle;
+    }
+
+    public void setBorderStyle(String borderStyle) {
+        this.borderStyle = borderStyle;
+    }
+
+    public String getBorderWidth() {
+        return borderWidth;
+    }
+
+    public void setBorderWidth(String borderWidth) {
+        this.borderWidth = borderWidth;
+    }
+
+    public Either<String, ThemeColor> getColor() {
+        return color;
+    }
+
+    public void setColor(Either<String, ThemeColor> color) {
+        this.color = color;
+    }
+
+    public String getCursor() {
+        return cursor;
+    }
+
+    public void setCursor(String cursor) {
+        this.cursor = cursor;
+    }
+
+    public ThemableDecorationRenderOptions getDark() {
+        return dark;
+    }
+
+    public void setDark(ThemableDecorationRenderOptions dark) {
+        this.dark = dark;
+    }
+
+    public String getFontStyle() {
+        return fontStyle;
+    }
+
+    public void setFontStyle(String fontStyle) {
+        this.fontStyle = fontStyle;
+    }
+
+    public String getFontWeight() {
+        return fontWeight;
+    }
+
+    public void setFontWeight(String fontWeight) {
+        this.fontWeight = fontWeight;
+    }
+
+    public Either<String, URI> getGutterIconPath() {
+        return gutterIconPath;
+    }
+
+    public void setGutterIconPath(Either<String, URI> gutterIconPath) {
+        this.gutterIconPath = gutterIconPath;
+    }
+
+    public String getGutterIconSize() {
+        return gutterIconSize;
+    }
+
+    public void setGutterIconSize(String gutterIconSize) {
+        this.gutterIconSize = gutterIconSize;
+    }
+
+    public boolean isIsWholeLine() {
+        return isWholeLine;
+    }
+
+    public void setIsWholeLine(boolean isWholeLine) {
+        this.isWholeLine = isWholeLine;
+    }
+
+    public String getLetterSpacing() {
+        return letterSpacing;
+    }
+
+    public void setLetterSpacing(String letterSpacing) {
+        this.letterSpacing = letterSpacing;
+    }
+
+    public ThemableDecorationRenderOptions getLight() {
+        return light;
+    }
+
+    public void setLight(ThemableDecorationRenderOptions light) {
+        this.light = light;
+    }
+
+    public String getOpacity() {
+        return opacity;
+    }
+
+    public void setOpacity(String opacity) {
+        this.opacity = opacity;
+    }
+
+    public String getOutline() {
+        return outline;
+    }
+
+    public void setOutline(String outline) {
+        this.outline = outline;
+    }
+
+    public Either<String, ThemeColor> getOutlineColor() {
+        return outlineColor;
+    }
+
+    public void setOutlineColor(Either<String, ThemeColor> outlineColor) {
+        this.outlineColor = outlineColor;
+    }
+
+    public String getOutlineStyle() {
+        return outlineStyle;
+    }
+
+    public void setOutlineStyle(String outlineStyle) {
+        this.outlineStyle = outlineStyle;
+    }
+
+    public String getOutlineWidth() {
+        return outlineWidth;
+    }
+
+    public void setOutlineWidth(String outlineWidth) {
+        this.outlineWidth = outlineWidth;
+    }
+
+    public Either<String, ThemeColor> getOverviewRulerColor() {
+        return overviewRulerColor;
+    }
+
+    public void setOverviewRulerColor(Either<String, ThemeColor> overviewRulerColor) {
+        this.overviewRulerColor = overviewRulerColor;
+    }
+
+    public OverviewRulerLane getOverviewRulerLane() {
+        return overviewRulerLane;
+    }
+
+    public void setOverviewRulerLane(OverviewRulerLane overviewRulerLane) {
+        this.overviewRulerLane = overviewRulerLane;
+    }
+
+    public String getTextDecoration() {
+        return textDecoration;
+    }
+
+    public void setTextDecoration(String textDecoration) {
+        this.textDecoration = textDecoration;
+    }
+
+    
+}
diff --git 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
index 0d4f513..05520e0 100644
--- 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
@@ -144,4 +144,19 @@ class NbCodeClientWrapper implements NbCodeLanguageClient {
     public void notifyProgress(ProgressParams params) {
         remote.notifyProgress(params);
     }
+
+    @Override
+    public CompletableFuture<String> createTextEditorDecoration(DecorationRenderOptions params) {
+        return remote.createTextEditorDecoration(params);
+    }
+
+    @Override
+    public void setTextEditorDecoration(SetTextEditorDecorationParams params) {
+        remote.setTextEditorDecoration(params);
+    }
+
+    @Override
+    public void disposeTextEditorDecoration(String params) {
+        remote.disposeTextEditorDecoration(params);
+    }
 }
diff --git 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
index 6954b40..c1b3923 100644
--- 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
@@ -72,6 +72,31 @@ public interface NbCodeLanguageClient extends LanguageClient {
     public void notifyTestProgress(@NonNull TestProgressParams params);
 
     /**
+     * Create a text editor decoration.
+     *
+     * @param params the decoration render options
+     * @return a key of the created decoration
+     */
+    @JsonRequest("window/createTextEditorDecoration")
+    public CompletableFuture<String> createTextEditorDecoration(@NonNull DecorationRenderOptions params);
+
+    /**
+     * Set text editor decoration to an array of code ranges.
+     *
+     * @param params 
+     */
+    @JsonNotification("window/setTextEditorDecoration")
+    public void setTextEditorDecoration(@NonNull SetTextEditorDecorationParams params);
+
+    /**
+     * Notifies client about disposal of the text editor decoration.
+     *
+     * @param params the decoration key
+     */
+    @JsonNotification("window/disposeTextEditorDecoration")
+    public void disposeTextEditorDecoration(@NonNull String params);
+
+    /**
      * Returns extended code capabilities.
      * @return code capabilities.
      */
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbLspServer.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbLspServer.java
index 0453434..f50d437 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbLspServer.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbLspServer.java
@@ -23,6 +23,7 @@ import java.util.concurrent.Future;
 import org.eclipse.lsp4j.services.TextDocumentService;
 import org.eclipse.lsp4j.services.WorkspaceService;
 import org.netbeans.modules.java.lsp.server.LspSession;
+import org.netbeans.modules.java.lsp.server.files.OpenedDocuments;
 import org.openide.util.Lookup;
 
 /**
@@ -57,4 +58,8 @@ public final class NbLspServer implements LspSession.ScheduledServer {
     public Future<Void> getRunningFuture() {
         return runningFuture;
     }
+
+    public OpenedDocuments getOpenedDocuments() {
+        return impl.getOpenedDocuments();
+    }
 }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/OverviewRulerLane.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/OverviewRulerLane.java
new file mode 100644
index 0000000..e8eb9a5
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/OverviewRulerLane.java
@@ -0,0 +1,53 @@
+/*
+ * 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.protocol;
+
+/**
+ *
+ * @author Martin Entlicher
+ */
+public enum OverviewRulerLane {
+
+    Center(2),
+    Full(7),
+    Left(1),
+    Right(4);
+    
+    private final int intValue;
+
+    OverviewRulerLane(int intValue) {
+        this.intValue = intValue;
+    }
+
+    public int getIntValue() {
+        return intValue;
+    }
+
+    public static OverviewRulerLane get(int intValue) {
+        switch (intValue) {
+            case 2: return Center;
+            case 7: return Full;
+            case 1: return Left;
+            case 4: return Right;
+            default:
+                return null;
+        }
+    }
+
+}
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 4774ecc..9944083 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
@@ -88,6 +88,7 @@ import org.netbeans.api.project.ui.OpenProjects;
 import org.netbeans.modules.java.lsp.server.LspServerState;
 import org.netbeans.modules.java.lsp.server.LspSession;
 import org.netbeans.modules.java.lsp.server.Utils;
+import org.netbeans.modules.java.lsp.server.files.OpenedDocuments;
 import org.netbeans.modules.java.lsp.server.progress.OperationContext;
 import org.netbeans.modules.progress.spi.InternalHandle;
 import org.netbeans.spi.project.ActionProgress;
@@ -316,6 +317,8 @@ public final class Server {
          * the set of opened projects change, collections are never modified.
          */
         private volatile Collection<Project> openedProjects = Collections.emptyList();
+
+        private final OpenedDocuments openedDocuments = new OpenedDocuments();
         
         Lookup getSessionLookup() {
             return sessionLookup;
@@ -574,6 +577,11 @@ public final class Server {
             return workspaceProjects;
         }
         
+        @Override
+        public OpenedDocuments getOpenedDocuments() {
+            return openedDocuments;
+        }
+
         private JavaSource showIndexingCompleted(Project[] opened) {
             try {
                 final JavaSource source = checkJavaSupport();
@@ -827,6 +835,24 @@ public final class Server {
         }
 
         @Override
+        public CompletableFuture<String> createTextEditorDecoration(DecorationRenderOptions params) {
+            logWarning(params);
+            CompletableFuture<String> x = new CompletableFuture<>();
+            x.complete(null);
+            return x;
+        }
+
+        @Override
+        public void setTextEditorDecoration(SetTextEditorDecorationParams params) {
+            logWarning(params);
+        }
+
+        @Override
+        public void disposeTextEditorDecoration(String params) {
+            logWarning(params);
+        }
+
+        @Override
         public void logMessage(MessageParams message) {
             logWarning(message);
         }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/SetTextEditorDecorationParams.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/SetTextEditorDecorationParams.java
new file mode 100644
index 0000000..e6b8c70
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/SetTextEditorDecorationParams.java
@@ -0,0 +1,139 @@
+/*
+ * 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.protocol;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+import org.eclipse.lsp4j.Range;
+import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
+import org.eclipse.xtext.xbase.lib.Pure;
+import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
+
+/**
+ * VSCode's DecorationRenderOptions.
+ *
+ * @author martin
+ */
+public final class SetTextEditorDecorationParams {
+
+    /**
+     * The text editor decoration key.
+     */
+    @NonNull
+    private String key;
+
+    /**
+     * The text editor uri.
+     */
+    @NonNull
+    private String uri;
+
+    /**
+     * The decoration ranges.
+     */
+    @NonNull
+    private Range[] ranges;
+
+    public SetTextEditorDecorationParams() {
+        this("", "", new Range[]{});
+    }
+
+    public SetTextEditorDecorationParams(@NonNull String key, @NonNull String uri, @NonNull Range[] ranges) {
+        this.key = key;
+        this.uri = uri;
+        this.ranges = ranges;
+    }
+
+    @Pure
+    @NonNull
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(@NonNull String key) {
+        this.key = key;
+    }
+
+    @Pure
+    @NonNull
+    public String getUri() {
+        return uri;
+    }
+
+    public void setUri(@NonNull String uri) {
+        this.uri = uri;
+    }
+
+    @Pure
+    @NonNull
+    public Range[] getRanges() {
+        return ranges;
+    }
+
+    public void setRanges(@NonNull Range[] ranges) {
+        this.ranges = ranges;
+    }
+
+    @Pure
+    @Override
+    public String toString() {
+        ToStringBuilder b = new ToStringBuilder(this);
+        b.add("key", key);
+        b.add("uri", uri);
+        b.add("ranges", Arrays.toString(ranges));
+        return b.toString();
+    }
+
+    @Pure
+    @Override
+    public int hashCode() {
+        int hash = 5;
+        hash = 23 * hash + Objects.hashCode(this.key);
+        hash = 23 * hash + Objects.hashCode(this.uri);
+        hash = 23 * hash + Arrays.deepHashCode(this.ranges);
+        return hash;
+    }
+
+    @Pure
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final SetTextEditorDecorationParams other = (SetTextEditorDecorationParams) obj;
+        if (!Objects.equals(this.key, other.key)) {
+            return false;
+        }
+        if (!Objects.equals(this.uri, other.uri)) {
+            return false;
+        }
+        if (!Arrays.deepEquals(this.ranges, other.ranges)) {
+            return false;
+        }
+        return true;
+    }
+
+}
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 0685f07..a1af889 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
@@ -175,6 +175,7 @@ import org.netbeans.modules.java.lsp.server.Utils;
 import org.netbeans.modules.java.lsp.server.debugging.utils.ErrorUtilities;
 import org.netbeans.modules.parsing.api.ParserManager;
 import org.netbeans.modules.parsing.api.ResultIterator;
+import org.netbeans.modules.java.lsp.server.files.OpenedDocuments;
 import org.netbeans.modules.parsing.api.Source;
 import org.netbeans.modules.parsing.api.UserTask;
 import org.netbeans.modules.parsing.impl.indexing.implspi.ActiveDocumentProvider.IndexingAware;
@@ -235,7 +236,6 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
     /**
      * Documents actually opened by the client.
      */
-    private final Map<String, Document> openedDocuments = new ConcurrentHashMap<>();
     private final Map<String, RequestProcessor.Task> diagnosticTasks = new HashMap<>();
     private final LspServerState server;
     private NbCodeLanguageClient client;
@@ -246,9 +246,7 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
     }
 
     private void reRunDiagnostics() {
-        Set<String> documents = new HashSet<>(openedDocuments.keySet());
-
-        for (String doc : documents) {
+        for (String doc : server.getOpenedDocuments().getUris()) {
             runDiagnosticTasks(doc);
         }
     }
@@ -447,7 +445,7 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
         }
         String uri = params.getTextDocument().getUri();
         FileObject file = fromURI(uri);
-        Document doc = openedDocuments.get(uri);
+        Document doc = server.getOpenedDocuments().getDocument(uri);
         if (file == null || doc == null) {
             return CompletableFuture.completedFuture(null);
         }
@@ -471,7 +469,7 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
     public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> definition(DefinitionParams params) {
         try {
             String uri = params.getTextDocument().getUri();
-            Document doc = openedDocuments.get(uri);
+            Document doc = server.getOpenedDocuments().getDocument(uri);
             if (doc != null) {
                 FileObject file = Utils.fromUri(uri);
                 if (file != null) {
@@ -494,7 +492,7 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
     public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> typeDefinition(TypeDefinitionParams params) {
         try {
             String uri = params.getTextDocument().getUri();
-            Document doc = openedDocuments.get(uri);
+            Document doc = server.getOpenedDocuments().getDocument(uri);
             if (doc != null) {
                 FileObject file = Utils.fromUri(uri);
                 if (file != null) {
@@ -770,7 +768,7 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
         if (server.openedProjects().getNow(null) == null) {
             return CompletableFuture.completedFuture(Collections.emptyList());
         }
-        Document doc = openedDocuments.get(params.getTextDocument().getUri());
+        Document doc = server.getOpenedDocuments().getDocument(params.getTextDocument().getUri());
         if (doc == null) {
             return CompletableFuture.completedFuture(Collections.emptyList());
         }
@@ -1316,7 +1314,7 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
                 //TODO: include stack trace:
                 client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
             }
-            openedDocuments.put(params.getTextDocument().getUri(), doc);
+            server.getOpenedDocuments().notifyOpened(params.getTextDocument().getUri(), doc);
             
             // attempt to open the directly owning project, delay diagnostics after project open:
             server.asyncOpenFileOwner(file).thenRun(() ->
@@ -1333,7 +1331,7 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
     public void didChange(DidChangeTextDocumentParams params) {
         String uri = params.getTextDocument().getUri();
         upToDateTests.put(uri, Boolean.FALSE);
-        Document doc = openedDocuments.get(uri);
+        Document doc = server.getOpenedDocuments().getDocument(uri);
         if (doc != null) {
             NbDocument.runAtomic((StyledDocument) doc, () -> {
                 for (TextDocumentContentChangeEvent change : params.getContentChanges()) {
@@ -1359,7 +1357,7 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
             upToDateTests.remove(uri);
             // the order here is important ! As the file may cease to exist, it's
             // important that the doucment is already gone form the client.
-            openedDocuments.remove(uri);
+            server.getOpenedDocuments().notifyClosed(uri);
             FileObject file = fromURI(uri, true);
             if (file == null) {
                 return;
@@ -1481,14 +1479,14 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
         }
         diagnosticTasks.computeIfAbsent(uri, u -> {
             return BACKGROUND_TASKS.create(() -> {
-                Document originalDoc = openedDocuments.get(uri);
+                Document originalDoc = server.getOpenedDocuments().getDocument(uri);
                 long originalVersion = documentVersion(originalDoc);
                 List<Diagnostic> errorDiags = computeDiags(u, -1, ErrorProvider.Kind.ERRORS, originalVersion);
                 if (documentVersion(originalDoc) == originalVersion) {
                     publishDiagnostics(uri, errorDiags);
                     BACKGROUND_TASKS.create(() -> {
                         List<Diagnostic> hintDiags = computeDiags(u, -1, ErrorProvider.Kind.HINTS, originalVersion);
-                        Document doc = openedDocuments.get(uri);
+                        Document doc = server.getOpenedDocuments().getDocument(uri);
                         if (documentVersion(doc) == originalVersion) {
                             publishDiagnostics(uri, hintDiags);
                 }
@@ -1646,7 +1644,7 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
      * @param uri file URI
      */
     private void missingFileDiscovered(String uri) {
-        if (openedDocuments.get(uri) != null) {
+        if (server.getOpenedDocuments().getDocument(uri) != null) {
             // do not report anything, the document is still opened in the editor
             return;
         }
@@ -1677,7 +1675,7 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
 
     @CheckForNull
     public JavaSource getJavaSource(String fileUri) {
-        Document doc = openedDocuments.get(fileUri);
+        Document doc = server.getOpenedDocuments().getDocument(fileUri);
         if (doc == null) {
             FileObject file = fromURI(fileUri);
             if (file == null) {
@@ -1691,7 +1689,7 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
 
     @CheckForNull
     public Source getSource(String fileUri) {
-        Document doc = openedDocuments.get(fileUri);
+        Document doc = server.getOpenedDocuments().getDocument(fileUri);
         if (doc == null) {
             FileObject file = fromURI(fileUri);
             if (file == null) {
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ThemableDecorationAttachmentRenderOptions.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ThemableDecorationAttachmentRenderOptions.java
new file mode 100644
index 0000000..6e78867
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ThemableDecorationAttachmentRenderOptions.java
@@ -0,0 +1,229 @@
+/*
+ * 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.protocol;
+
+import java.net.URI;
+import java.util.Objects;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
+import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
+
+/**
+ *
+ * @author Martin Entlicher
+ */
+public final class ThemableDecorationAttachmentRenderOptions {
+
+    private Either<String, ThemeColor> backgroundColor;
+    private String border;
+    private Either<String, ThemeColor> borderColor;
+    private Either<String, ThemeColor> color;
+    private Either<String, URI> contentIconPath;
+    private String contentText;
+    private String fontStyle;
+    private String fontWeight;
+    private String height;
+    private String margin;
+    private String textDecoration;
+    private String width;
+
+    public Either<String, ThemeColor> getBackgroundColor() {
+        return backgroundColor;
+    }
+
+    public void setBackgroundColor(Either<String, ThemeColor> backgroundColor) {
+        this.backgroundColor = backgroundColor;
+    }
+
+    public String getBorder() {
+        return border;
+    }
+
+    public void setBorder(String border) {
+        this.border = border;
+    }
+
+    public Either<String, ThemeColor> getBorderColor() {
+        return borderColor;
+    }
+
+    public void setBorderColor(Either<String, ThemeColor> borderColor) {
+        this.borderColor = borderColor;
+    }
+
+    public Either<String, ThemeColor> getColor() {
+        return color;
+    }
+
+    public void setColor(Either<String, ThemeColor> color) {
+        this.color = color;
+    }
+
+    public Either<String, URI> getContentIconPath() {
+        return contentIconPath;
+    }
+
+    public void setContentIconPath(Either<String, URI> contentIconPath) {
+        this.contentIconPath = contentIconPath;
+    }
+
+    public String getContentText() {
+        return contentText;
+    }
+
+    public void setContentText(String contentText) {
+        this.contentText = contentText;
+    }
+
+    public String getFontStyle() {
+        return fontStyle;
+    }
+
+    public void setFontStyle(String fontStyle) {
+        this.fontStyle = fontStyle;
+    }
+
+    public String getFontWeight() {
+        return fontWeight;
+    }
+
+    public void setFontWeight(String fontWeight) {
+        this.fontWeight = fontWeight;
+    }
+
+    public String getHeight() {
+        return height;
+    }
+
+    public void setHeight(String height) {
+        this.height = height;
+    }
+
+    public String getMargin() {
+        return margin;
+    }
+
+    public void setMargin(String margin) {
+        this.margin = margin;
+    }
+
+    public String getTextDecoration() {
+        return textDecoration;
+    }
+
+    public void setTextDecoration(String textDecoration) {
+        this.textDecoration = textDecoration;
+    }
+
+    public String getWidth() {
+        return width;
+    }
+
+    public void setWidth(String width) {
+        this.width = width;
+    }
+
+    @Override
+    public String toString() {
+        ToStringBuilder b = new ToStringBuilder(this);
+        b.add("backgroundColor", backgroundColor);
+        b.add("border", border);
+        b.add("borderColor", borderColor);
+        b.add("color", color);
+        b.add("contentIconPath", contentIconPath);
+        b.add("contentText", contentText);
+        b.add("fontStyle", fontStyle);
+        b.add("fontWeight", fontWeight);
+        b.add("height", height);
+        b.add("margin", margin);
+        b.add("textDecoration", textDecoration);
+        b.add("width", width);
+        return b.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 3;
+        hash = 79 * hash + Objects.hashCode(this.backgroundColor);
+        hash = 79 * hash + Objects.hashCode(this.border);
+        hash = 79 * hash + Objects.hashCode(this.borderColor);
+        hash = 79 * hash + Objects.hashCode(this.color);
+        hash = 79 * hash + Objects.hashCode(this.contentIconPath);
+        hash = 79 * hash + Objects.hashCode(this.contentText);
+        hash = 79 * hash + Objects.hashCode(this.fontStyle);
+        hash = 79 * hash + Objects.hashCode(this.fontWeight);
+        hash = 79 * hash + Objects.hashCode(this.height);
+        hash = 79 * hash + Objects.hashCode(this.margin);
+        hash = 79 * hash + Objects.hashCode(this.textDecoration);
+        hash = 79 * hash + Objects.hashCode(this.width);
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final ThemableDecorationAttachmentRenderOptions other = (ThemableDecorationAttachmentRenderOptions) obj;
+        if (!Objects.equals(this.border, other.border)) {
+            return false;
+        }
+        if (!Objects.equals(this.contentText, other.contentText)) {
+            return false;
+        }
+        if (!Objects.equals(this.fontStyle, other.fontStyle)) {
+            return false;
+        }
+        if (!Objects.equals(this.fontWeight, other.fontWeight)) {
+            return false;
+        }
+        if (!Objects.equals(this.height, other.height)) {
+            return false;
+        }
+        if (!Objects.equals(this.margin, other.margin)) {
+            return false;
+        }
+        if (!Objects.equals(this.textDecoration, other.textDecoration)) {
+            return false;
+        }
+        if (!Objects.equals(this.width, other.width)) {
+            return false;
+        }
+        if (!Objects.equals(this.backgroundColor, other.backgroundColor)) {
+            return false;
+        }
+        if (!Objects.equals(this.borderColor, other.borderColor)) {
+            return false;
+        }
+        if (!Objects.equals(this.color, other.color)) {
+            return false;
+        }
+        if (!Objects.equals(this.contentIconPath, other.contentIconPath)) {
+            return false;
+        }
+        return true;
+    }
+
+    
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ThemableDecorationRenderOptions.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ThemableDecorationRenderOptions.java
new file mode 100644
index 0000000..5ef78a3
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ThemableDecorationRenderOptions.java
@@ -0,0 +1,383 @@
+/*
+ * 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.protocol;
+
+import java.net.URI;
+import java.util.Objects;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
+import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
+
+/**
+ *
+ * @author Martin Entlicher
+ */
+public final class ThemableDecorationRenderOptions {
+
+    private ThemableDecorationAttachmentRenderOptions after;
+    private Either<String, ThemeColor> backgroundColor;
+    private ThemableDecorationAttachmentRenderOptions before;
+    private String border;
+    private Either<String, ThemeColor> borderColor;
+    private String borderRadius;
+    private String borderSpacing;
+    private String borderStyle;
+    private String borderWidth;
+    private Either<String, ThemeColor> color;
+    private String cursor;
+    private String fontStyle;
+    private String fontWeight;
+    private Either<String, URI> gutterIconPath;
+    private String gutterIconSize;
+    private String letterSpacing;
+    private String opacity;
+    private String outline;
+    private Either<String, ThemeColor> outlineColor;
+    private String outlineStyle;
+    private String outlineWidth;
+    private Either<String, ThemeColor> overviewRulerColor;
+    private String textDecoration;
+
+    public ThemableDecorationAttachmentRenderOptions getAfter() {
+        return after;
+    }
+
+    public void setAfter(ThemableDecorationAttachmentRenderOptions after) {
+        this.after = after;
+    }
+
+    public Either<String, ThemeColor> getBackgroundColor() {
+        return backgroundColor;
+    }
+
+    public void setBackgroundColor(Either<String, ThemeColor> backgroundColor) {
+        this.backgroundColor = backgroundColor;
+    }
+
+    public ThemableDecorationAttachmentRenderOptions getBefore() {
+        return before;
+    }
+
+    public void setBefore(ThemableDecorationAttachmentRenderOptions before) {
+        this.before = before;
+    }
+
+    public String getBorder() {
+        return border;
+    }
+
+    public void setBorder(String border) {
+        this.border = border;
+    }
+
+    public Either<String, ThemeColor> getBorderColor() {
+        return borderColor;
+    }
+
+    public void setBorderColor(Either<String, ThemeColor> borderColor) {
+        this.borderColor = borderColor;
+    }
+
+    public String getBorderRadius() {
+        return borderRadius;
+    }
+
+    public void setBorderRadius(String borderRadius) {
+        this.borderRadius = borderRadius;
+    }
+
+    public String getBorderSpacing() {
+        return borderSpacing;
+    }
+
+    public void setBorderSpacing(String borderSpacing) {
+        this.borderSpacing = borderSpacing;
+    }
+
+    public String getBorderStyle() {
+        return borderStyle;
+    }
+
+    public void setBorderStyle(String borderStyle) {
+        this.borderStyle = borderStyle;
+    }
+
+    public String getBorderWidth() {
+        return borderWidth;
+    }
+
+    public void setBorderWidth(String borderWidth) {
+        this.borderWidth = borderWidth;
+    }
+
+    public Either<String, ThemeColor> getColor() {
+        return color;
+    }
+
+    public void setColor(Either<String, ThemeColor> color) {
+        this.color = color;
+    }
+
+    public String getCursor() {
+        return cursor;
+    }
+
+    public void setCursor(String cursor) {
+        this.cursor = cursor;
+    }
+
+    public String getFontStyle() {
+        return fontStyle;
+    }
+
+    public void setFontStyle(String fontStyle) {
+        this.fontStyle = fontStyle;
+    }
+
+    public String getFontWeight() {
+        return fontWeight;
+    }
+
+    public void setFontWeight(String fontWeight) {
+        this.fontWeight = fontWeight;
+    }
+
+    public Either<String, URI> getGutterIconPath() {
+        return gutterIconPath;
+    }
+
+    public void setGutterIconPath(Either<String, URI> gutterIconPath) {
+        this.gutterIconPath = gutterIconPath;
+    }
+
+    public String getGutterIconSize() {
+        return gutterIconSize;
+    }
+
+    public void setGutterIconSize(String gutterIconSize) {
+        this.gutterIconSize = gutterIconSize;
+    }
+
+    public String getLetterSpacing() {
+        return letterSpacing;
+    }
+
+    public void setLetterSpacing(String letterSpacing) {
+        this.letterSpacing = letterSpacing;
+    }
+
+    public String getOpacity() {
+        return opacity;
+    }
+
+    public void setOpacity(String opacity) {
+        this.opacity = opacity;
+    }
+
+    public String getOutline() {
+        return outline;
+    }
+
+    public void setOutline(String outline) {
+        this.outline = outline;
+    }
+
+    public Either<String, ThemeColor> getOutlineColor() {
+        return outlineColor;
+    }
+
+    public void setOutlineColor(Either<String, ThemeColor> outlineColor) {
+        this.outlineColor = outlineColor;
+    }
+
+    public String getOutlineStyle() {
+        return outlineStyle;
+    }
+
+    public void setOutlineStyle(String outlineStyle) {
+        this.outlineStyle = outlineStyle;
+    }
+
+    public String getOutlineWidth() {
+        return outlineWidth;
+    }
+
+    public void setOutlineWidth(String outlineWidth) {
+        this.outlineWidth = outlineWidth;
+    }
+
+    public Either<String, ThemeColor> getOverviewRulerColor() {
+        return overviewRulerColor;
+    }
+
+    public void setOverviewRulerColor(Either<String, ThemeColor> overviewRulerColor) {
+        this.overviewRulerColor = overviewRulerColor;
+    }
+
+    public String getTextDecoration() {
+        return textDecoration;
+    }
+
+    public void setTextDecoration(String textDecoration) {
+        this.textDecoration = textDecoration;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 37 * hash + Objects.hashCode(this.after);
+        hash = 37 * hash + Objects.hashCode(this.backgroundColor);
+        hash = 37 * hash + Objects.hashCode(this.before);
+        hash = 37 * hash + Objects.hashCode(this.border);
+        hash = 37 * hash + Objects.hashCode(this.borderColor);
+        hash = 37 * hash + Objects.hashCode(this.borderRadius);
+        hash = 37 * hash + Objects.hashCode(this.borderSpacing);
+        hash = 37 * hash + Objects.hashCode(this.borderStyle);
+        hash = 37 * hash + Objects.hashCode(this.borderWidth);
+        hash = 37 * hash + Objects.hashCode(this.color);
+        hash = 37 * hash + Objects.hashCode(this.cursor);
+        hash = 37 * hash + Objects.hashCode(this.fontStyle);
+        hash = 37 * hash + Objects.hashCode(this.fontWeight);
+        hash = 37 * hash + Objects.hashCode(this.gutterIconPath);
+        hash = 37 * hash + Objects.hashCode(this.gutterIconSize);
+        hash = 37 * hash + Objects.hashCode(this.letterSpacing);
+        hash = 37 * hash + Objects.hashCode(this.opacity);
+        hash = 37 * hash + Objects.hashCode(this.outline);
+        hash = 37 * hash + Objects.hashCode(this.outlineColor);
+        hash = 37 * hash + Objects.hashCode(this.outlineStyle);
+        hash = 37 * hash + Objects.hashCode(this.outlineWidth);
+        hash = 37 * hash + Objects.hashCode(this.overviewRulerColor);
+        hash = 37 * hash + Objects.hashCode(this.textDecoration);
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final ThemableDecorationRenderOptions other = (ThemableDecorationRenderOptions) obj;
+        if (!Objects.equals(this.border, other.border)) {
+            return false;
+        }
+        if (!Objects.equals(this.borderRadius, other.borderRadius)) {
+            return false;
+        }
+        if (!Objects.equals(this.borderSpacing, other.borderSpacing)) {
+            return false;
+        }
+        if (!Objects.equals(this.borderStyle, other.borderStyle)) {
+            return false;
+        }
+        if (!Objects.equals(this.borderWidth, other.borderWidth)) {
+            return false;
+        }
+        if (!Objects.equals(this.cursor, other.cursor)) {
+            return false;
+        }
+        if (!Objects.equals(this.fontStyle, other.fontStyle)) {
+            return false;
+        }
+        if (!Objects.equals(this.fontWeight, other.fontWeight)) {
+            return false;
+        }
+        if (!Objects.equals(this.gutterIconSize, other.gutterIconSize)) {
+            return false;
+        }
+        if (!Objects.equals(this.letterSpacing, other.letterSpacing)) {
+            return false;
+        }
+        if (!Objects.equals(this.opacity, other.opacity)) {
+            return false;
+        }
+        if (!Objects.equals(this.outline, other.outline)) {
+            return false;
+        }
+        if (!Objects.equals(this.outlineStyle, other.outlineStyle)) {
+            return false;
+        }
+        if (!Objects.equals(this.outlineWidth, other.outlineWidth)) {
+            return false;
+        }
+        if (!Objects.equals(this.textDecoration, other.textDecoration)) {
+            return false;
+        }
+        if (!Objects.equals(this.after, other.after)) {
+            return false;
+        }
+        if (!Objects.equals(this.backgroundColor, other.backgroundColor)) {
+            return false;
+        }
+        if (!Objects.equals(this.before, other.before)) {
+            return false;
+        }
+        if (!Objects.equals(this.borderColor, other.borderColor)) {
+            return false;
+        }
+        if (!Objects.equals(this.color, other.color)) {
+            return false;
+        }
+        if (!Objects.equals(this.gutterIconPath, other.gutterIconPath)) {
+            return false;
+        }
+        if (!Objects.equals(this.outlineColor, other.outlineColor)) {
+            return false;
+        }
+        if (!Objects.equals(this.overviewRulerColor, other.overviewRulerColor)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        ToStringBuilder b = new ToStringBuilder(this);
+        b.add("after", after);
+        b.add("backgroundColor", backgroundColor);
+        b.add("before", before);
+        b.add("border", border);
+        b.add("borderColor", borderColor);
+        b.add("borderRadius", borderRadius);
+        b.add("borderSpacing", borderSpacing);
+        b.add("borderStyle", borderStyle);
+        b.add("borderWidth", borderWidth);
+        b.add("color", color);
+        b.add("cursor", cursor);
+        b.add("fontStyle", fontStyle);
+        b.add("fontWeight", fontWeight);
+        b.add("gutterIconPath", gutterIconPath);
+        b.add("gutterIconSize", gutterIconSize);
+        b.add("letterSpacing", letterSpacing);
+        b.add("opacity", opacity);
+        b.add("outline", outline);
+        b.add("outlineColor", outlineColor);
+        b.add("outlineStyle", outlineStyle);
+        b.add("outlineWidth", outlineWidth);
+        b.add("overviewRulerColor", overviewRulerColor);
+        b.add("textDecoration", textDecoration);
+        return b.toString();
+    }
+
+    
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ThemeColor.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ThemeColor.java
new file mode 100644
index 0000000..405942e
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ThemeColor.java
@@ -0,0 +1,81 @@
+/*
+ * 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.protocol;
+
+import java.util.Objects;
+
+import org.eclipse.xtext.xbase.lib.Pure;
+import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
+import org.netbeans.api.annotations.common.NonNull;
+
+/**
+ *
+ * @author Martin Entlicher
+ */
+public final class ThemeColor {
+
+    @NonNull
+    private final String id;
+
+    public ThemeColor(@NonNull String id) {
+        this.id = id;
+    }
+
+    @Pure
+    @NonNull
+    public String getId() {
+        return id;
+    }
+
+    @Pure
+    @Override
+    public String toString() {
+        ToStringBuilder b = new ToStringBuilder(this);
+        b.add("id", id);
+        return b.toString();
+    }
+
+    @Pure
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 29 * hash + Objects.hashCode(this.id);
+        return hash;
+    }
+
+    @Pure
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final ThemeColor other = (ThemeColor) obj;
+        if (!Objects.equals(this.id, other.id)) {
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/progress/TestProgressHandlerTest.java b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/progress/TestProgressHandlerTest.java
index 5a54f0b..1265b97 100644
--- a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/progress/TestProgressHandlerTest.java
+++ b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/progress/TestProgressHandlerTest.java
@@ -38,9 +38,11 @@ import org.netbeans.modules.gsf.testrunner.api.Status;
 import org.netbeans.modules.gsf.testrunner.api.TestSession;
 import org.netbeans.modules.gsf.testrunner.api.Testcase;
 import org.netbeans.modules.gsf.testrunner.api.Trouble;
+import org.netbeans.modules.java.lsp.server.protocol.DecorationRenderOptions;
 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.QuickPickItem;
+import org.netbeans.modules.java.lsp.server.protocol.SetTextEditorDecorationParams;
 import org.netbeans.modules.java.lsp.server.protocol.ShowInputBoxParams;
 import org.netbeans.modules.java.lsp.server.protocol.ShowQuickPickParams;
 import org.netbeans.modules.java.lsp.server.protocol.ShowStatusMessageParams;
@@ -198,5 +200,21 @@ public class TestProgressHandlerTest extends NbTestCase {
             fail();
             return null;
         }
+
+        @Override
+        public CompletableFuture<String> createTextEditorDecoration(DecorationRenderOptions params) {
+            fail();
+            return null;
+        }
+
+        @Override
+        public void setTextEditorDecoration(SetTextEditorDecorationParams params) {
+            fail();
+        }
+
+        @Override
+        public void disposeTextEditorDecoration(String params) {
+            fail();
+        }
     }
 }
diff --git a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/OverviewRulerLaneTest.java b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/OverviewRulerLaneTest.java
new file mode 100644
index 0000000..d45133f
--- /dev/null
+++ b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/OverviewRulerLaneTest.java
@@ -0,0 +1,38 @@
+/*
+ * 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.protocol;
+
+import org.netbeans.junit.NbTestCase;
+
+/**
+ *
+ * @author martin
+ */
+public class OverviewRulerLaneTest extends NbTestCase {
+
+    public OverviewRulerLaneTest(String name) {
+        super(name);
+    }
+
+    public void testAllIntValuesCovered() {
+        for (OverviewRulerLane value : OverviewRulerLane.values()) {
+            assertSame(value, OverviewRulerLane.get(value.getIntValue()));
+        }
+    }
+}
diff --git a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
index 15f0ffc..9f4fb43 100644
--- a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
+++ b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
@@ -1604,6 +1604,21 @@ public class ServerTest extends NbTestCase {
             public void logMessage(MessageParams arg0) {
                 throw new UnsupportedOperationException("Not supported yet.");
             }
+
+            @Override
+            public CompletableFuture<String> createTextEditorDecoration(DecorationRenderOptions params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void setTextEditorDecoration(SetTextEditorDecorationParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void disposeTextEditorDecoration(String params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
         }, client.getInputStream(), client.getOutputStream());
         serverLauncher.startListening();
         LanguageServer server = serverLauncher.getRemoteProxy();
@@ -2942,6 +2957,21 @@ public class ServerTest extends NbTestCase {
                 throw new UnsupportedOperationException("Not supported yet.");
             }
 
+            @Override
+            public CompletableFuture<String> createTextEditorDecoration(DecorationRenderOptions params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void setTextEditorDecoration(SetTextEditorDecorationParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void disposeTextEditorDecoration(String params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
         }, client.getInputStream(), client.getOutputStream());
         serverLauncher.startListening();
         LanguageServer server = serverLauncher.getRemoteProxy();
@@ -3141,6 +3171,21 @@ public class ServerTest extends NbTestCase {
                 throw new UnsupportedOperationException("Not supported yet.");
             }
 
+            @Override
+            public CompletableFuture<String> createTextEditorDecoration(DecorationRenderOptions params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void setTextEditorDecoration(SetTextEditorDecorationParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void disposeTextEditorDecoration(String params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
         }, client.getInputStream(), client.getOutputStream());
         serverLauncher.startListening();
         LanguageServer server = serverLauncher.getRemoteProxy();
@@ -3257,6 +3302,21 @@ public class ServerTest extends NbTestCase {
                 throw new UnsupportedOperationException("Not supported yet.");
             }
 
+            @Override
+            public CompletableFuture<String> createTextEditorDecoration(DecorationRenderOptions params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void setTextEditorDecoration(SetTextEditorDecorationParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void disposeTextEditorDecoration(String params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
         }, client.getInputStream(), client.getOutputStream());
         serverLauncher.startListening();
         LanguageServer server = serverLauncher.getRemoteProxy();
@@ -3374,6 +3434,21 @@ public class ServerTest extends NbTestCase {
                 throw new UnsupportedOperationException("Not supported yet.");
             }
 
+            @Override
+            public CompletableFuture<String> createTextEditorDecoration(DecorationRenderOptions params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void setTextEditorDecoration(SetTextEditorDecorationParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void disposeTextEditorDecoration(String params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
         }, client.getInputStream(), client.getOutputStream());
         serverLauncher.startListening();
         LanguageServer server = serverLauncher.getRemoteProxy();
@@ -3478,6 +3553,21 @@ public class ServerTest extends NbTestCase {
                 throw new UnsupportedOperationException("Not supported yet.");
             }
 
+            @Override
+            public CompletableFuture<String> createTextEditorDecoration(DecorationRenderOptions params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void setTextEditorDecoration(SetTextEditorDecorationParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void disposeTextEditorDecoration(String params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
         }, client.getInputStream(), client.getOutputStream());
         serverLauncher.startListening();
         LanguageServer server = serverLauncher.getRemoteProxy();
@@ -3587,6 +3677,21 @@ public class ServerTest extends NbTestCase {
                 throw new UnsupportedOperationException("Not supported yet.");
             }
 
+            @Override
+            public CompletableFuture<String> createTextEditorDecoration(DecorationRenderOptions params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void setTextEditorDecoration(SetTextEditorDecorationParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void disposeTextEditorDecoration(String params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
         }, client.getInputStream(), client.getOutputStream());
         serverLauncher.startListening();
         LanguageServer server = serverLauncher.getRemoteProxy();
@@ -3693,6 +3798,21 @@ public class ServerTest extends NbTestCase {
                 throw new UnsupportedOperationException("Not supported yet.");
             }
 
+            @Override
+            public CompletableFuture<String> createTextEditorDecoration(DecorationRenderOptions params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void setTextEditorDecoration(SetTextEditorDecorationParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void disposeTextEditorDecoration(String params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
         }, client.getInputStream(), client.getOutputStream());
         serverLauncher.startListening();
         LanguageServer server = serverLauncher.getRemoteProxy();
diff --git a/java/java.lsp.server/vscode/src/extension.ts b/java/java.lsp.server/vscode/src/extension.ts
index 22f7e5d..9eefdb8 100644
--- a/java/java.lsp.server/vscode/src/extension.ts
+++ b/java/java.lsp.server/vscode/src/extension.ts
@@ -18,7 +18,7 @@
  */
 'use strict';
 
-import { commands, window, workspace, ExtensionContext, ProgressLocation } from 'vscode';
+import { commands, window, workspace, ExtensionContext, ProgressLocation, TextEditorDecorationType } from 'vscode';
 
 import {
     LanguageClient,
@@ -43,7 +43,9 @@ import { testExplorerExtensionId, TestHub } from 'vscode-test-adapter-api';
 import { TestAdapterRegistrar } from 'vscode-test-adapter-util';
 import * as launcher from './nbcode';
 import {NbTestAdapter} from './testAdapter';
-import { StatusMessageRequest, ShowStatusMessageParams, QuickPickRequest, InputBoxRequest, TestProgressNotification, DebugConnector } from './protocol';
+import { StatusMessageRequest, ShowStatusMessageParams, QuickPickRequest, InputBoxRequest, TestProgressNotification, DebugConnector,
+         TextEditorDecorationCreateRequest, TextEditorDecorationSetNotification, TextEditorDecorationDisposeNotification,
+} from './protocol';
 import * as launchConfigurations from './launchConfigurations';
 
 const API_VERSION : string = "1.0";
@@ -596,7 +598,30 @@ function doActivateWithJDK(specifiedJDK: string | null, context: ExtensionContex
                         }
                     }
                 }
-            })
+            });
+            let decorations = new Map<string, TextEditorDecorationType>();
+            c.onRequest(TextEditorDecorationCreateRequest.type, param => {
+                let decorationType = vscode.window.createTextEditorDecorationType(param);
+                decorations.set(decorationType.key, decorationType);
+                return decorationType.key;
+            });
+            c.onNotification(TextEditorDecorationSetNotification.type, param => {
+                let decorationType = decorations.get(param.key);
+                if (decorationType) {
+                    let editorsWithUri = vscode.window.visibleTextEditors.filter(
+                        editor => editor.document.uri.toString() == param.uri
+                    );
+                    if (editorsWithUri.length > 0) {
+                        editorsWithUri[0].setDecorations(decorationType, param.ranges);
+                    }
+                }
+            });
+            c.onNotification(TextEditorDecorationDisposeNotification.type, param => {
+                let decorationType = decorations.get(param);
+                if (decorationType) {
+                    decorationType.dispose();
+                }
+            });
             handleLog(log, 'Language Client: Ready');
             setClient[0](c);
             commands.executeCommand('setContext', 'nbJavaLSReady', true);
diff --git a/java/java.lsp.server/vscode/src/protocol.ts b/java/java.lsp.server/vscode/src/protocol.ts
index 75c1d2e..3c3a254 100644
--- a/java/java.lsp.server/vscode/src/protocol.ts
+++ b/java/java.lsp.server/vscode/src/protocol.ts
@@ -18,7 +18,12 @@
  */
 'use strict';
 
-import {QuickPickItem} from 'vscode';
+import {
+    DecorationRenderOptions,
+    QuickPickItem,
+    Range,
+    TextEditorDecorationType,
+} from 'vscode';
 import {
     NotificationType,
     RequestType,
@@ -105,3 +110,21 @@ export interface DebugConnector {
     defaultValues: string[];
     descriptions: string[];
 }
+
+export interface SetTextEditorDecorationParams {
+    key: string;
+    uri: string;
+    ranges: Range[];
+};
+
+export namespace TextEditorDecorationCreateRequest {
+    export const type = new RequestType<DecorationRenderOptions, string, void, void>('window/createTextEditorDecoration');
+};
+
+export namespace TextEditorDecorationSetNotification {
+    export const type = new NotificationType<SetTextEditorDecorationParams, void>('window/setTextEditorDecoration');
+};
+
+export namespace TextEditorDecorationDisposeNotification {
+    export const type = new NotificationType<string, void>('window/disposeTextEditorDecoration');
+};
diff --git a/java/java.nativeimage.debugger/nbproject/project.xml b/java/java.nativeimage.debugger/nbproject/project.xml
index 21f442c..5251556 100644
--- a/java/java.nativeimage.debugger/nbproject/project.xml
+++ b/java/java.nativeimage.debugger/nbproject/project.xml
@@ -85,7 +85,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>0</release-version>
-                        <specification-version>0.1</specification-version>
+                        <specification-version>0.2</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/JavaFrameDisplayer.java b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/JavaFrameDisplayer.java
index 2123c98..6c40c8b 100644
--- a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/JavaFrameDisplayer.java
+++ b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/JavaFrameDisplayer.java
@@ -115,6 +115,9 @@ public final class JavaFrameDisplayer implements FrameDisplayer {
     private URI getSourceURI(NIFrame frame) {
         String functionName = frame.getFunctionName();
         int methodEnd = functionName.indexOf('(');
+        if (methodEnd < 0) {
+            methodEnd = functionName.length();
+        }
         if (methodEnd > 0) {
             int methodStart = functionName.lastIndexOf('.', methodEnd);
             if (methodStart > 0) {

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@netbeans.apache.org
For additional commands, e-mail: commits-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists