diff --git a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java index b1e2072c9..1ebcb73b0 100644 --- a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java +++ b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java @@ -148,12 +148,25 @@ public class SpringBootApp { } private boolean isSpringBootAppClasspath() throws IOException { - Properties props = this.vm.getSystemProperties(); - String classpath = (String) props.get("java.class.path"); - String[] cpElements = getClasspath(classpath); - return contains(cpElements, "spring-boot"); + return contains(getClasspath(), "spring-boot"); } + public String[] getClasspath() throws IOException { + Properties props = this.vm.getSystemProperties(); + String classpath = (String) props.get("java.class.path"); + String[] cpElements = splitClasspath(classpath); + return cpElements; + } + + public String getJavaCommand() throws IOException { + Properties props = this.vm.getSystemProperties(); + if (props.contains("sun.java.command")) { + return props.getProperty("sun.java.command"); + } + else { + return null; + } + } public boolean containsSystemProperty(Object key) throws IOException { Properties props = this.vm.getSystemProperties(); @@ -340,7 +353,7 @@ public class SpringBootApp { return false; } - protected String[] getClasspath(String classpath) { + protected String[] splitClasspath(String classpath) { List classpathElements = new ArrayList<>(); if (classpath != null) { StringTokenizer tokenizer = new StringTokenizer(classpath, File.pathSeparator); diff --git a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/livebean/LiveBeansModel.java b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/livebean/LiveBeansModel.java index 0f70a916d..a1be6f16b 100644 --- a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/livebean/LiveBeansModel.java +++ b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/livebean/LiveBeansModel.java @@ -11,14 +11,19 @@ package org.springframework.ide.vscode.commons.boot.app.cli.livebean; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.Set; import org.json.JSONArray; import org.json.JSONObject; import org.springframework.ide.vscode.commons.util.Log; import org.springframework.ide.vscode.commons.util.StringUtil; +import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableMultiset; +import com.google.common.collect.ImmutableSet; /** * @author Martin Lippert @@ -210,4 +215,8 @@ public class LiveBeansModel { return beansViaName.isEmpty(); //Assumes every bean has a name. } + public Set getBeanNames() { + return beansViaName.keySet(); + } + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaConfig.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaConfig.java index 6383624a3..8169de884 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaConfig.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaConfig.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 2018 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 @@ -28,6 +28,11 @@ public class BootJavaConfig { return enabled == null || enabled.booleanValue(); } + public boolean isChangeDetectionEnabled() { + Boolean enabled = settings.getBoolean("boot-java", "change-detection", "on"); + return enabled == null || enabled.booleanValue(); + } + public void handleConfigurationChange(Settings newConfig) { Log.info("Settings received: "+newConfig); this.settings = newConfig; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java index 007c9f52e..fa4dbc74c 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java @@ -56,6 +56,7 @@ import org.springframework.ide.vscode.boot.java.snippets.JavaSnippetContext; import org.springframework.ide.vscode.boot.java.snippets.JavaSnippetManager; import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; +import org.springframework.ide.vscode.boot.java.utils.SpringLiveChangeDetectionWatchdog; import org.springframework.ide.vscode.boot.java.utils.SpringLiveHoverWatchdog; import org.springframework.ide.vscode.boot.java.value.ValueCompletionProcessor; import org.springframework.ide.vscode.boot.java.value.ValueHoverProvider; @@ -95,6 +96,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent private final SpringIndexer indexer; private final SpringPropertyIndexProvider propertyIndexProvider; private final SpringLiveHoverWatchdog liveHoverWatchdog; + private final SpringLiveChangeDetectionWatchdog liveChangeDetectionWatchdog; private final ProjectObserver projectObserver; private final BootJavaConfig config; private final CompilationUnitCache cuCache; @@ -169,6 +171,8 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent // } }); + liveChangeDetectionWatchdog = new SpringLiveChangeDetectionWatchdog(this, server, serverParams.projectObserver, serverParams.runningAppProvider, projectFinder, serverParams.watchDogInterval); + codeLensHandler = createCodeLensEngine(); documents.onCodeLens(codeLensHandler); @@ -177,11 +181,21 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent workspaceService.onDidChangeConfiguraton(settings -> { config.handleConfigurationChange(settings); + + // live hover watchdog if (config.isBootHintsEnabled()) { liveHoverWatchdog.enableHighlights(); } else { liveHoverWatchdog.disableHighlights(); } + + // live change detection watchdog + if (config.isChangeDetectionEnabled()) { + liveChangeDetectionWatchdog.enableHighlights(); + } + else { + liveChangeDetectionWatchdog.disableHighlights(); + } }); server.onInitialize(this::initialize); @@ -213,6 +227,8 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent private void initialized() { this.indexer.serverInitialized(); + this.liveChangeDetectionWatchdog.start(); + // TODO: due to a missing message from lsp4e this "initialized" is not called in // the LSP4E case // if this gets fixed, the code should move here (from "initialize" above) @@ -223,6 +239,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent private void shutdown() { this.liveHoverWatchdog.shutdown(); + this.liveChangeDetectionWatchdog.shutdown(); this.indexer.shutdown(); this.cuCache.dispose(); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/Change.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/Change.java new file mode 100644 index 000000000..449a05982 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/Change.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2018 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.utils; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean; + +/** + * @author Martin Lippert + */ +public class Change { + + private List newBeans; + private List deletedBeans; + + public Change() { + } + + public List getNewBeans() { + return newBeans; + } + + public List getDeletedBeans() { + return deletedBeans; + } + + public void addDeletedBean(LiveBean bean) { + if (deletedBeans == null) { + deletedBeans = new ArrayList<>(); + } + + deletedBeans.add(bean); + } + + public void addNewBean(LiveBean bean) { + if (newBeans == null) { + newBeans = new ArrayList<>(); + } + + newBeans.add(bean); + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ChangeDetectionHistory.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ChangeDetectionHistory.java new file mode 100644 index 000000000..6f79ddcc0 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ChangeDetectionHistory.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2018 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.utils; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; + +/** + * @author Martin Lippert + */ +public class ChangeDetectionHistory { + + private Map changeHistory; + + public ChangeDetectionHistory() { + this.changeHistory = new HashMap<>(); + } + + public Change checkForChanges(SpringBootApp app) { + String virtualID = getVirtualAppID(app); + + if (!changeHistory.containsKey(virtualID)) { + ChangeHistory appHistory = new ChangeHistory(virtualID); + changeHistory.put(virtualID, appHistory); + } + + ChangeHistory appHistory = changeHistory.get(virtualID); + appHistory.updateProcess(app); + + Change result = appHistory.checkForUpdates(); + return result; + } + + private String getVirtualAppID(SpringBootApp app) { + // TODO: this needs a lot more work to make this a real VIRTUAL ID which detects the same app + // running across different app restarts, so the process ID is not the best way to do this (just + // a temporary solution) + return app.getProcessID(); + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ChangeHistory.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ChangeHistory.java new file mode 100644 index 000000000..2348a62c7 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ChangeHistory.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * Copyright (c) 2018 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.utils; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; +import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean; +import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; + +/** + * @author Martin Lippert + */ +public class ChangeHistory { + + private static final List EMPTY_BEANS_LIST = new ArrayList<>(0); + + private final String virtualAppID; + + private SpringBootApp runningProcess; + private LiveBeansModel lastBeans; + + public ChangeHistory(String virtualAppID) { + this.virtualAppID = virtualAppID; + } + + public void updateProcess(SpringBootApp app) { + this.runningProcess = app; + } + + public Change checkForUpdates() { + Change result = null; + + LiveBeansModel currentBeans = this.runningProcess.getBeans(); + + if (lastBeans == null) { + lastBeans = currentBeans; + } + else if (currentBeans != null) { + result = calculateBeansDiff(lastBeans, currentBeans, result); + lastBeans = currentBeans; + } + + return result; + } + + private Change calculateBeansDiff(LiveBeansModel previous, LiveBeansModel current, Change result) { + if (previous == current) { + return result; + } + + Set currentNames = current.getBeanNames(); + Set previousNames = previous.getBeanNames(); + + Set allNames = new HashSet<>(currentNames); + allNames.addAll(previousNames); + + for (String name : allNames) { + List currentBeans = current.getBeansOfName(name); + List previousBeans = previous.getBeansOfName(name); + + result = calculateBeansDiff(previousBeans, currentBeans, result); + } + + return result; + } + + private Change calculateBeansDiff(List previousBeans, List currentBeans, Change result) { + if (currentBeans == null) currentBeans = EMPTY_BEANS_LIST; + if (previousBeans == null) previousBeans = EMPTY_BEANS_LIST; + + for (LiveBean bean : previousBeans) { + if (!contains(currentBeans, bean)) { + + if (result == null) { + result = new Change(); + } + + result.addDeletedBean(bean); + } + } + + for (LiveBean bean : currentBeans) { + if (!contains(previousBeans, bean)) { + + if (result == null) { + result = new Change(); + } + + result.addNewBean(bean); + } + } + + return result; + } + + private boolean contains(List beans, LiveBean bean) { + for (LiveBean beansFromList : beans) { + if (StringUtils.equals(beansFromList.getId(), bean.getId()) + && StringUtils.equals(beansFromList.getType(), bean.getType()) + && StringUtils.equals(beansFromList.getResource(), bean.getResource())) { + return true; + } + } + + return false; + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringLiveChangeDetectionWatchdog.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringLiveChangeDetectionWatchdog.java new file mode 100644 index 000000000..8ecb1a327 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringLiveChangeDetectionWatchdog.java @@ -0,0 +1,297 @@ +/******************************************************************************* + * Copyright (c) 2018 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.utils; + +import java.nio.file.Paths; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.DiagnosticSeverity; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.PublishDiagnosticsParams; +import org.eclipse.lsp4j.Range; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents; +import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider; +import org.springframework.ide.vscode.boot.java.links.SourceLinks; +import org.springframework.ide.vscode.boot.java.links.VSCodeSourceLinks; +import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; +import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean; +import org.springframework.ide.vscode.commons.java.IClasspath; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; +import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver.Listener; +import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath; +import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath.CPE; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; + +/** + * @author Martin Lippert + */ +public class SpringLiveChangeDetectionWatchdog { + + public static final Duration DEFAULT_INTERVAL = Duration.ofMillis(5000); + + Logger logger = LoggerFactory.getLogger(SpringLiveChangeDetectionWatchdog.class); + + private final long POLLING_INTERVAL_MILLISECONDS; + + private final SimpleLanguageServer server; + private final RunningAppProvider runningAppProvider; + private final SourceLinks sourceLinks; + + private final ChangeDetectionHistory changeHistory; + private final Set observedProjects; + + private boolean changeDetectionEnabled = true; + private Timer timer; + + public SpringLiveChangeDetectionWatchdog( + BootJavaLanguageServerComponents bootJavaLanguageServerComponents, + SimpleLanguageServer server, + ProjectObserver projectObserver, + RunningAppProvider runningAppProvider, + JavaProjectFinder projectFinder, + Duration pollingInterval + ) { + this.observedProjects = new HashSet<>(); + + this.server = server; + this.runningAppProvider = runningAppProvider; + + this.POLLING_INTERVAL_MILLISECONDS = pollingInterval == null ? DEFAULT_INTERVAL.toMillis() : pollingInterval.toMillis(); + + this.changeHistory = new ChangeDetectionHistory(); + this.sourceLinks = new VSCodeSourceLinks(bootJavaLanguageServerComponents); + + if (projectObserver != null) { + projectObserver.addListener(new Listener() { + + @Override + public void deleted(IJavaProject project) { + observedProjects.remove(project); + } + + @Override + public void created(IJavaProject project) { + observedProjects.add(project); + } + + @Override + public void changed(IJavaProject project) { + // do nothing + } + }); + } + } + + public synchronized void start() { + if (changeDetectionEnabled && timer == null) { + logger.debug("Starting SpringLiveChangeDetectionWatchdog"); + this.timer = new Timer(); + + TimerTask task = new TimerTask() { + @Override + public void run() { + update(); + } + }; + + timer.schedule(task, 0, POLLING_INTERVAL_MILLISECONDS); + } + } + + public synchronized void shutdown() { + if (timer != null) { + logger.info("Shutting down SpringLiveChangeDetectionWatchdog"); + timer.cancel(); + timer = null; + } + } + + public void update() { + if (changeDetectionEnabled) { + try { + SpringBootApp[] runningBootApps = runningAppProvider.getAllRunningSpringApps().toArray(new SpringBootApp[0]); + for (SpringBootApp app : runningBootApps) { + updateApp(app); + } + } catch (Exception e) { + logger.error("", e); + } + } + } + + private void updateApp(SpringBootApp app) { + Change change = changeHistory.checkForChanges(app); + if (change != null) { + publishDetectedChange(change, app); + } + } + + private void publishDetectedChange(Change change, SpringBootApp app) { + Map> diagnostics = new HashMap<>(); + + IJavaProject[] projects = findProjectsFor(app); + + List deletedBeans = change.getDeletedBeans(); + if (deletedBeans != null) { + for (LiveBean liveBean : deletedBeans) { + Diagnostic diag = new Diagnostic(); + diag.setSeverity(DiagnosticSeverity.Information); + diag.setRange(new Range(new Position(0, 0), new Position(0, 0))); + diag.setSource("Spring Boot Change Detection Mechanism"); + + diag.setMessage("bean removed from app: " + liveBean.getId()); + + String docURI = getDocURI(liveBean, projects); + if (docURI != null) { + List diags = diagnostics.computeIfAbsent(docURI, (s) -> new ArrayList<>()); + diags.add(diag); + } + else { + logger.info("deleted bean could not be associated with a doc URI: " + liveBean.getId()); + } + } + } + + List newBeans = change.getNewBeans(); + if (newBeans != null) { + for (LiveBean liveBean : newBeans) { + Diagnostic diag = new Diagnostic(); + diag.setSeverity(DiagnosticSeverity.Information); + diag.setRange(new Range(new Position(0, 0), new Position(0, 0))); + diag.setSource("Spring Boot Change Detection Mechanism"); + + diag.setMessage("new bean detected: " + liveBean.getId()); + + String docURI = getDocURI(liveBean, projects); + if (docURI != null) { + List diags = diagnostics.computeIfAbsent(docURI, (s) -> new ArrayList<>()); + diags.add(diag); + } + else { + logger.info("new bean could not be associated with a doc URI: " + liveBean.getId()); + } + } + } + + for (String docURI : diagnostics.keySet()) { + PublishDiagnosticsParams params = new PublishDiagnosticsParams(docURI, diagnostics.get(docURI)); + server.getClient().publishDiagnostics(params); + } + + } + + private IJavaProject[] findProjectsFor(SpringBootApp app) { + List result = new ArrayList<>(); + + try { + Set runningClasspath = new HashSet<>(); + Collections.addAll(runningClasspath, app.getClasspath()); + + for (IJavaProject project : this.observedProjects) { + IClasspath classpath = project.getClasspath(); + Collection entries = classpath.getClasspathEntries(); + for (CPE cpe : entries) { + if (Classpath.ENTRY_KIND_SOURCE.equals(cpe.getKind())) { + String path = cpe.getOutputFolder(); + if (runningClasspath.contains(path)) { + result.add(project); + break; + } + } + } + } + } + catch (Exception e) { + logger.error("find projects failed with: ", e); + } + + return (IJavaProject[]) result.toArray(new IJavaProject[result.size()]); + } + + private String getDocURI(LiveBean liveBean, IJavaProject[] projects) { + String result = null; + + String resource = liveBean.getResource(); + Pattern BRACKETS = Pattern.compile("\\[[^\\]]*\\]"); + + Matcher matcher = BRACKETS.matcher(resource); + if (matcher.find()) { + String type = resource.substring(0, matcher.start()).trim(); + String path = resource.substring(matcher.start()+1, matcher.end()-1); + + for (IJavaProject project : projects) { + if (SpringResource.FILE.equals(type)) { + String relativePath = SpringResource.projectRelativePath(project, path); + + if (relativePath != path && path.endsWith(SourceLinks.CLASS)) { + result = sourceLinks.sourceLinkUrlForClasspathResource(project, relativePath).get(); + break; + } else { + result = sourceLinks.sourceLinkForResourcePath(Paths.get(path)).get(); + break; + } + } + else if (SpringResource.CLASS_PATH_RESOURCE.equals(type)) { + result = sourceLinks.sourceLinkUrlForClasspathResource(project, path).get(); + break; + } + } + } + + if (result != null) { + int position = result.lastIndexOf('#'); + if (position > 0) { + result = result.substring(0, position); + } + } + return result; + } + + public synchronized void enableHighlights() { + if (!changeDetectionEnabled) { + changeDetectionEnabled = true; + refreshEnablement(); + } + } + + public synchronized void disableHighlights() { + if (changeDetectionEnabled) { + changeDetectionEnabled = false; + refreshEnablement(); + } + } + + private void refreshEnablement() { + if (changeDetectionEnabled) { + start(); + } else { + shutdown(); + } + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringLiveHoverWatchdog.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringLiveHoverWatchdog.java index d460d6ecb..b85c0d78c 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringLiveHoverWatchdog.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringLiveHoverWatchdog.java @@ -37,7 +37,6 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument; */ public class SpringLiveHoverWatchdog { - public static final Duration DEFAULT_INTERVAL = Duration.ofMillis(5000); Logger logger = LoggerFactory.getLogger(SpringLiveHoverWatchdog.class); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringResource.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringResource.java index e20b406d6..0f82f153d 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringResource.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringResource.java @@ -29,8 +29,8 @@ import org.springframework.ide.vscode.commons.util.Renderables; */ public class SpringResource { - private static final String FILE = "file"; - private static final String CLASS_PATH_RESOURCE = "class path resource"; + public static final String FILE = "file"; + public static final String CLASS_PATH_RESOURCE = "class path resource"; private SourceLinks sourceLinks; private String type; @@ -65,7 +65,7 @@ public class SpringResource { Optional linkUrl; switch (type) { case FILE: - String relativePath = projectRelativePath(path); + String relativePath = projectRelativePath(project, path); if (relativePath != path && path.endsWith(SourceLinks.CLASS)) { linkUrl = sourceLinks.sourceLinkUrlForClasspathResource(project, relativePath); } else { @@ -73,7 +73,7 @@ public class SpringResource { } // not a project relative path return linkUrl.isPresent() ? Renderables.link(relativePath, linkUrl.get()).toMarkdown() - : "`" + projectRelativePath(path) + "`"; + : "`" + projectRelativePath(project, path) + "`"; case CLASS_PATH_RESOURCE: linkUrl = sourceLinks.sourceLinkUrlForClasspathResource(project, path); return linkUrl.isPresent() ? Renderables.link(path, linkUrl.get()).toMarkdown() : "`"+path+"`"; @@ -82,7 +82,7 @@ public class SpringResource { } } - private String projectRelativePath(String pathStr) { + public static String projectRelativePath(IJavaProject project, String pathStr) { Path path = Paths.get(pathStr); IClasspath classpath = project.getClasspath(); Iterable ofs = () -> IClasspathUtil.getOutputFolders(classpath).iterator();