diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java index 850cb7e4e..0149351b4 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java @@ -13,8 +13,6 @@ package org.springframework.ide.vscode.boot.app; import java.io.File; import java.net.URI; import java.net.URISyntaxException; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -128,7 +126,7 @@ public class SpringSymbolIndex implements InitializingBean { private String watchXMLChangedRegistration; // Futures resolved when project is initialized/indexed - private Map> initializedProjects = new HashMap<>(); + private Map> initializedProjects = new HashMap<>(); private SimpleWorkspaceService getWorkspaceService() { @@ -538,61 +536,52 @@ public class SpringSymbolIndex implements InitializingBean { .map(enhanced -> enhanced.getSymbol()); } - synchronized private CompletableFuture projectInitializedFuture(IJavaProject project) { + synchronized private CompletableFuture projectInitializedFuture(IJavaProject project) { if (project == null) { return CompletableFuture.completedFuture(null); } else { URI uri = project.getLocationUri(); - return initializedProjects.computeIfAbsent(uri, u -> new CompletableFuture()); + return initializedProjects.computeIfAbsent(uri, u -> CompletableFuture.completedFuture(project)); } } public List getSymbols(String docURI) { - try { + try { + TextDocument doc = server.getTextDocumentService().getLatestSnapshot(docURI); URI uri = URI.create(docURI); - - /* - * Workaround for docUri coming from vscode-languageclient on Windows - * - * It comes in as "file:///c%3A/Users/ab/spring-petclinic/src/main/java/org/springframework/samples/petclinic/owner/PetRepository.java" - * - * While symbols index would have this uri instead: - * - "file:///C:/Users/ab/spring-petclinic/src/main/java/org/springframework/samples/petclinic/owner/PetRepository.java" - * - * i.e. lower vs upper case drive letter and escaped drive colon - */ - if ("file".equals(uri.getScheme())) { - String path = URLDecoder.decode(uri.getPath(), StandardCharsets.UTF_8); - int colonIdx = path.indexOf(':'); - if (colonIdx > 0 && colonIdx < 3) { - int driveIdx = colonIdx - 1; - if (Character.isLowerCase(path.charAt(driveIdx))) { - StringBuilder sb = new StringBuilder(path.substring(0, driveIdx)); - sb.append(Character.toUpperCase(path.charAt(driveIdx))); - sb.append(path.substring(driveIdx + 1)); - path = sb.toString(); + CompletableFuture projectInitialized = futureProjectFinder.findFuture(uri).thenCompose(project -> projectInitializedFuture(project)); + IJavaProject project = projectInitialized.get(15, TimeUnit.SECONDS); + ImmutableList.Builder builder = ImmutableList.builder(); + if (project != null && doc != null) { + // Collect symbols from the opened document + for (SpringIndexer indexer : this.indexers) { + if (indexer.isInterestedIn(docURI)) { + try { + for (EnhancedSymbolInformation enhanced : indexer.computeSymbols(project, docURI, + doc.get())) { + builder.add(enhanced.getSymbol()); + } + } catch (Exception e) { + log.error("{}", e); + } } } - uri = UriUtil.toUri(new File(path)); - } - - CompletableFuture projectInitialized = futureProjectFinder.findFuture(uri).thenCompose(project -> projectInitializedFuture(project)); - projectInitialized.get(15, TimeUnit.SECONDS); - List docSymbols = this.symbolsByDoc.get(uri.toString()); - if (docSymbols != null) { - synchronized (docSymbols) { - ImmutableList.Builder builder = ImmutableList.builder(); - for (EnhancedSymbolInformation enhanced : docSymbols) { - builder.add(enhanced.getSymbol()); + } else { + // Take symbols from the index if there is no opened document. + List docSymbols = this.symbolsByDoc.get(uri.toString()); + if (docSymbols != null) { + synchronized (docSymbols) { + for (EnhancedSymbolInformation enhanced : docSymbols) { + builder.add(enhanced.getSymbol()); + } } - return builder.build(); } } + return builder.build(); } catch (Exception e) { log.warn("", e); return Collections.emptyList(); } - return Collections.emptyList(); } public List getAllAdditionalInformation(Predicate filter) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/EnhancedSymbolInformation.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/EnhancedSymbolInformation.java index cb61b3d27..c2523cad2 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/EnhancedSymbolInformation.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/EnhancedSymbolInformation.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018 Pivotal, Inc. + * Copyright (c) 2018, 2022 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -33,4 +33,9 @@ public class EnhancedSymbolInformation { return additionalInformation; } + @Override + public String toString() { + return "EnhancedSymbolInformation [symbol=" + symbol + "]"; + } + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CachedSymbol.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CachedSymbol.java index 45629c772..8421b8466 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CachedSymbol.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CachedSymbol.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019 Pivotal, Inc. + * Copyright (c) 2019, 2022 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -36,4 +36,10 @@ public class CachedSymbol { return lastModified; } + @Override + public String toString() { + return "CachedSymbol [docURI=" + docURI + ", enhancedSymbol=" + enhancedSymbol + "]"; + } + + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java index 849065478..86756408a 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019, 2020 Pivotal, Inc. + * Copyright (c) 2019, 2022 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -10,6 +10,9 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.utils; +import java.util.List; + +import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation; import org.springframework.ide.vscode.commons.java.IJavaProject; /** @@ -19,6 +22,8 @@ public interface SpringIndexer { String[] getFileWatchPatterns(); boolean isInterestedIn(String docURI); + + List computeSymbols(IJavaProject project, String docURI, String content) throws Exception; void initializeProject(IJavaProject project) throws Exception; void removeProject(IJavaProject project) throws Exception; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java index 652fd6ad5..f8ea5ed1d 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2020 Pivotal, Inc. + * Copyright (c) 2017, 2022 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -19,6 +19,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -230,6 +231,35 @@ public class SpringIndexerJava implements SpringIndexer { scanAffectedFiles(project, context.getScannedTypes(), scannedFiles); } } + + public List computeSymbols(IJavaProject project, String docURI, String content) throws Exception { + ASTParser parser = createParser(project, false); + + if (content != null) { + String unitName = docURI.substring(docURI.lastIndexOf("/")); + parser.setUnitName(unitName); + log.debug("Scan file: {}", unitName); + parser.setSource(content.toCharArray()); + + CompilationUnit cu = (CompilationUnit) parser.createAST(null); + + if (cu != null) { + List generatedSymbols = new ArrayList(); + AtomicReference docRef = new AtomicReference<>(); + String file = UriUtil.toFileString(docURI); + SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, file, + 0, docRef, content, generatedSymbols, SCAN_PASS.ONE, new ArrayList<>()); + + scanAST(context); + + return generatedSymbols.stream().map(s -> s.getEnhancedSymbol()).collect(Collectors.toList()); + } + + } + + return Collections.emptyList(); + + } private Set scanFilesInternally(IJavaProject project, DocumentDescriptor[] docs) throws Exception { ASTParser parser = createParser(project, false); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerXML.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerXML.java index fbaf2976e..1ec15e599 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerXML.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerXML.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019, 2020 Pivotal, Inc. + * Copyright (c) 2019, 2022 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -17,6 +17,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; @@ -30,6 +31,7 @@ import org.eclipse.lemminx.dom.DOMNode; import org.eclipse.lemminx.dom.DOMParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation; import org.springframework.ide.vscode.commons.java.IClasspath; import org.springframework.ide.vscode.commons.java.IClasspathUtil; import org.springframework.ide.vscode.commons.java.IJavaProject; @@ -308,5 +310,16 @@ public class SpringIndexerXML implements SpringIndexer { } } } + + @Override + public List computeSymbols(IJavaProject project, String docURI, String content) + throws Exception { + if (content != null) { + List generatedSymbols = new ArrayList<>(); + scanFile(project, content, docURI, 0, generatedSymbols); + return generatedSymbols.stream().map(s -> s.getEnhancedSymbol()).collect(Collectors.toList()); + } + return Collections.emptyList(); + } } diff --git a/vscode-extensions/commons-vscode/src/code-lens-service.ts b/vscode-extensions/commons-vscode/src/code-lens-service.ts index 25f28f549..e46b26d1a 100644 --- a/vscode-extensions/commons-vscode/src/code-lens-service.ts +++ b/vscode-extensions/commons-vscode/src/code-lens-service.ts @@ -6,7 +6,8 @@ import { Event, EventEmitter, ProviderResult, - TextDocument + TextDocument, + Uri } from "vscode"; import {HighlightParams, toVSRange} from './highlight-service'; import * as Lsp from 'vscode-languageclient'; @@ -22,7 +23,7 @@ export class HighlightCodeLensProvider implements CodeLensProvider { handle(highlghtParams: HighlightParams) { if (!deepEqual(this.highlights.get(highlghtParams.doc.uri), highlghtParams)) { - this.highlights.set(highlghtParams.doc.uri, highlghtParams); + this.highlights.set(Uri.parse(highlghtParams.doc.uri).toString(), highlghtParams); this._onDidChangeCodeLenses.fire(); } } diff --git a/vscode-extensions/commons-vscode/src/highlight-service.ts b/vscode-extensions/commons-vscode/src/highlight-service.ts index 797902b18..8392afc38 100644 --- a/vscode-extensions/commons-vscode/src/highlight-service.ts +++ b/vscode-extensions/commons-vscode/src/highlight-service.ts @@ -43,7 +43,7 @@ export class HighlightService { } handle(params : HighlightParams) : void { - this.highlights.set(params.doc.uri, params); + this.highlights.set(VSCode.Uri.parse(params.doc.uri).toString(), params); this.refresh(params.doc); } @@ -52,7 +52,7 @@ export class HighlightService { for (let editor of editors) { const activeUri = editor.document.uri.toString(); const activeVersion = editor.document.version; - if (docId.uri === activeUri && docId.version === activeVersion) { + if (VSCode.Uri.parse(docId.uri).toString() === activeUri && docId.version === activeVersion) { //We only update highlights in the active editor for now this.updateHighlightsForEditor(editor); } diff --git a/vscode-extensions/vscode-spring-boot/lib/Main.ts b/vscode-extensions/vscode-spring-boot/lib/Main.ts index ffa5f7661..d1c0b7cd3 100644 --- a/vscode-extensions/vscode-spring-boot/lib/Main.ts +++ b/vscode-extensions/vscode-spring-boot/lib/Main.ts @@ -1,5 +1,6 @@ 'use strict'; +import * as OS from "os"; import * as VSCode from 'vscode'; import { workspace } from 'vscode'; @@ -44,6 +45,30 @@ export function activate(context: VSCode.ExtensionContext): Thenable { + /* + * Workaround for docUri coming from vscode-languageclient on Windows + * + * It comes in as "file:///c%3A/Users/ab/spring-petclinic/src/main/java/org/springframework/samples/petclinic/owner/PetRepository.java" + * + * While symbols index would have this uri instead: + * - "file:///C:/Users/ab/spring-petclinic/src/main/java/org/springframework/samples/petclinic/owner/PetRepository.java" + * + * i.e. lower vs upper case drive letter and escaped drive colon + */ + if (OS.platform() === "win32" && uri.scheme === 'file') { + let uriStr = uri.toString(true); + const idx = uriStr.indexOf(':', 5); + if (idx > 5 && idx < 10) { + uriStr = `${uriStr.substring(0, idx - 1)}${uriStr.charAt(idx - 1).toUpperCase()}${uriStr.substring(idx)}` + } + return uriStr; + } + return uri.toString(); + }, + protocol2Code: uri => VSCode.Uri.file(uri) + }, // See PT-158992999 as to why a scheme is added to the document selector // documentSelector: [ PROPERTIES_LANGUAGE_ID, YAML_LANGUAGE_ID, JAVA_LANGUAGE_ID ], documentSelector: [