~damien/jedit-lsp

ee8ebfabd3c58eed1ce34323959488e7661fad87 — Damien Radtke 4 months ago 6b9bf01
Enable "get-root-uri" bsh script as an alternative to ProjectViewer
M LSP.props => LSP.props +1 -1
@@ 71,7 71,7 @@ lsp.server.jdtls.command=${PLUGIN_HOME}/jdtls/bin/jdtls
lsp.server.jdtls.install=dir = lsp.Utils.getPluginSubdir("jdtls"); \
                         eclipseDownload(view, "jdtls", "1.23.0", dir); \
                         new File(dir, "bin/jdtls").setExecutable(true);
lsp.server.jdtls.get-root=lsp.Utils.findRoot(buffer, new String[]{ "build.xml", "pom.xml", "settings.gradle", "settings.gradle.kts" })
lsp.server.jdtls.get-root-uri=lsp.Utils.findRootURI(buffer, new String[]{ "build.xml", "pom.xml", "settings.gradle", "settings.gradle.kts" })

# Map a mode to the language server to use
mode.go.languageServer=gopls

M Makefile => Makefile +8 -1
@@ 21,7 21,14 @@ else
	@make -f Makefile.macos userdocs
endif

clean: get-build-support
ifeq ($(UNAME), Linux)
	@make -f Makefile.linux clean
else
	@make -f Makefile.macos clean
endif

get-build-support:
	@if [ ! -d build-support ]; then svn checkout svn://svn.code.sf.net/p/jedit/svn/build-support/trunk/ build-support; fi

.PHONY: dist build userdocs get-build-support
.PHONY: dist build userdocs clean get-build-support

M Makefile.linux => Makefile.linux +4 -1
@@ 16,4 16,7 @@ build:
userdocs:
	$(ANT) userdocs

.PHONY: dist build userdocs
clean:
	$(ANT) clean

.PHONY: dist build userdocs clean

M lsp/ClientCommands.java => lsp/ClientCommands.java +1 -0
@@ 12,6 12,7 @@ import org.eclipse.lsp4j.WorkspaceEdit;
public class ClientCommands {
	public static Map<String, ClientCommandHandler> commands;

	// TODO: allow client command implementations to be written in Beanshell and registered via properties.
	static {
		commands = new HashMap<>();
		commands.put("java.apply.workspaceEdit", ClientCommands::javaApplyWorkspaceEdit);

M lsp/Server.java => lsp/Server.java +38 -2
@@ 13,6 13,7 @@ import java.util.Arrays;
import java.util.ArrayList;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;


@@ 74,6 75,7 @@ import org.eclipse.lsp4j.jsonrpc.Launcher;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.launch.LSPLauncher;
import org.eclipse.lsp4j.services.LanguageServer;
import org.gjt.sp.jedit.BeanShell;
import org.gjt.sp.jedit.Buffer;
import org.gjt.sp.jedit.EditPane;
import org.gjt.sp.jedit.Macros;


@@ 81,6 83,7 @@ import org.gjt.sp.jedit.Mode;
import org.gjt.sp.jedit.View;
import org.gjt.sp.jedit.jEdit;
import org.gjt.sp.jedit.bsh.UtilEvalError;
import org.gjt.sp.jedit.bsh.NameSpace;
import org.gjt.sp.jedit.textarea.TextArea;
import org.gjt.sp.util.Log;
import org.gjt.sp.util.ThreadUtilities;


@@ 229,19 232,52 @@ public class Server {
		}
	}

	private List<String> findWorkspaceFolders(Buffer buffer) {
		List<String> roots = Utils.getProjectRootURIs(buffer);
		if (!roots.isEmpty()) {
			return roots;
		}
		if (this.serverDefinition.hasGetRootURI()) {
			String script = this.serverDefinition.getGetRootURI();
			log(Log.WARNING, "Executing get-root-uri script: " + script);
			try {
				NameSpace ns = new NameSpace(BeanShell.getNameSpace(), "LSP " + this.getName() + " findWorkspaceFolders()");
				ns.setVariable("buffer", buffer);
				Object result = BeanShell.eval(null, ns, script);
				if (result instanceof String) {
					return Collections.singletonList((String) result);
				} else if ((result instanceof Path) || (result instanceof URI) || (result instanceof URL)) {
					return Collections.singletonList(result.toString());
				} else if (result instanceof List) {
					List<String> convertedResult = new ArrayList<>();
					for (Object item : ((List) result)) {
						convertedResult.add(item.toString());
					}
					return convertedResult;
				} else {
					log(Log.WARNING, "get-root-uri script returned an invalid result: " + result.toString());
				}
			} catch (UtilEvalError e) {
				log(Log.ERROR, "get-root-uri script threw an error", e);
			}
		} else {
			log(Log.WARNING, "no get-root-uri script");
		}
		return Collections.emptyList();
	}

	/*
	 * Initializes the language server once it is running.
	 * See: https://github.com/eclipse/lsp4j/blob/0a3e64aa029ad7f760427a0dfd74f2a2d3e4ae97/org.eclipse.lsp4j/src/main/java/org/eclipse/lsp4j/Protocol.xtend#L4913
	 */
	private void initialize(Buffer buffer) throws UtilEvalError {
		// TODO: execute the get-root script, and pass that in to init
		InitializeParams params = new InitializeParams();
		params.setProcessId((int) ProcessHandle.current().pid());
		//params.setInitializationOptions(initOptions);
		params.setCapabilities(new ClientCapabilities(Capabilities.getWorkspace(), Capabilities.getDocument(), null));
		params.setClientInfo(new ClientInfo("jEdit", jEdit.getVersion()));

		List<String> roots = Utils.getRootURIsWith(buffer);
		List<String> roots = this.findWorkspaceFolders(buffer);
		if (!roots.isEmpty()) {
			this.workspaceFolders = roots.stream().map(root -> new WorkspaceFolder(root, "project root")).collect(Collectors.toList());
			params.setWorkspaceFolders(this.workspaceFolders);

M lsp/ServerDefinition.java => lsp/ServerDefinition.java +12 -14
@@ 7,7 7,7 @@ public class ServerDefinition implements Comparable<ServerDefinition> {
	private String name;
	private String rawCommand;
	private String installScript;
	//private String getRoot;
	private String getRootURI;

	public ServerDefinition(String name) {
		this.name = name;


@@ 19,17 19,17 @@ public class ServerDefinition implements Comparable<ServerDefinition> {
		if (this.installScript == null) {
			this.installScript = "";
		}
		//this.getRoot = jEdit.getProperty(getProp("get-root"));
		//if (this.getRoot == null) {
		//	this.getRoot = "";
		//}
		this.getRootURI = jEdit.getProperty(getProp("get-root-uri"));
		if (this.getRootURI == null) {
			this.getRootURI = "";
		}
	}

	public void save() {
		Log.log(Log.DEBUG, this, "Saving command as: " + this.rawCommand);
		jEdit.setProperty(getProp("command"), this.rawCommand);
		jEdit.setProperty(getProp("install"), this.installScript);
		//jEdit.setProperty(getProp("get-root"), this.getRoot);
		jEdit.setProperty(getProp("get-root-uri"), this.getRootURI);
	}

	public String getName() {


@@ 62,19 62,17 @@ public class ServerDefinition implements Comparable<ServerDefinition> {
		this.installScript = installScript;
	}

	/*
	public String getGetRoot() {
		return this.getRoot;
	public String getGetRootURI() {
		return this.getRootURI;
	}

	public void setGetRoot(String getRoot) {
		this.getRoot = getRoot;
	public void setGetRootURI(String getRootURI) {
		this.getRootURI = getRootURI;
	}

	public boolean hasGetRoot() {
		return this.getRoot != null;
	public boolean hasGetRootURI() {
		return this.getRootURI != null && !this.getRootURI.isEmpty();
	}
	*/

	@Override
	public int compareTo(ServerDefinition other) {

M lsp/Utils.java => lsp/Utils.java +57 -33
@@ 20,6 20,8 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Predicate;

import org.gjt.sp.jedit.Buffer;


@@ 27,6 29,8 @@ import org.gjt.sp.jedit.MiscUtilities;
import org.gjt.sp.jedit.Mode;
import org.gjt.sp.jedit.View;
import org.gjt.sp.jedit.jEdit;
import org.gjt.sp.jedit.io.VFS;
import org.gjt.sp.jedit.io.VFSFile;
//import org.gjt.sp.jedit.manager.BufferManagerImpl;
import org.gjt.sp.jedit.textarea.Selection;
import org.gjt.sp.jedit.textarea.TextArea;


@@ 42,10 46,6 @@ import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveEntry;

import projectviewer.ProjectManager;
import projectviewer.vpt.VPTNode;
import projectviewer.vpt.VPTProject;

public class Utils {
	private Utils() {}



@@ 316,23 316,14 @@ public class Utils {
		return MiscUtilities.constructPath(LanguageServerPlugin.get().getPluginHome().getAbsolutePath(), name);
	}

	public static List<VPTProject> getProjectsWith(Buffer buffer) {
		List<VPTProject> result = new ArrayList<>();
		for (VPTProject project : ProjectManager.getInstance().getProjects()) {
			project = ProjectManager.getInstance().getProject(project.getName()); // ensure it's loaded
			VPTNode node = project.getChildNode(buffer.getPath());
			if (node != null) {
				result.add(project);
			}
	public static List<String> getProjectRootURIs(Buffer buffer) {
		if (jEdit.getPlugin("projectviewer.ProjectPlugin") == null) {
			return Collections.emptyList();
		}
		return result;
	}

	public static List<String> getRootURIsWith(Buffer buffer) {
		List<String> result = new ArrayList<>();
		Path bufferPath = Paths.get(buffer.getPath());
		for (VPTProject project : ProjectManager.getInstance().getProjects()) {
			project = ProjectManager.getInstance().getProject(project.getName()); // ensure it's loaded
		for (projectviewer.vpt.VPTProject project : projectviewer.ProjectManager.getInstance().getProjects()) {
			project = projectviewer.ProjectManager.getInstance().getProject(project.getName()); // ensure it's loaded
			// XXX: we don't care about projects containing buffer but with buffer outside of the root path, do we?
			if (bufferPath.startsWith(project.getRootPath())) {
				// TODO: support VFS?


@@ 342,32 333,65 @@ public class Utils {
		return result;
	}

	// Methods for finding the project root, currently unused in favor of ProjectViewer integration.
	// Methods for finding the project root, when one is not found via ProjectViewer.

	@FunctionalInterface
	public interface VfsPredicate {
		boolean test(VFS vfs, Object session, View view, String dir);
	}

	public static Path findRoot(Path dir, Predicate<Path> pred) {
		if (dir == null) {
	private static String findRootURI(VFS vfs, Object vfsSession, View view, String dir, VfsPredicate pred) {
		if (dir == null || dir.isEmpty()) {
			return null;
		}
		if (pred.test(vfs, vfsSession, view, dir)) {
			return vfs.getName() + "://" + dir;
		}
		String parent = vfs.getParentOfPath(dir);
		if (parent != null && parent.equals(dir)) {
			// we've hit the root and can't go further
			return null;
		}
		if (pred.test(dir)) {
			return dir;
		return findRootURI(vfs, vfsSession, view, vfs.getParentOfPath(dir), pred);
	}

	public static String findRootURI(VFS vfs, String dir, VfsPredicate pred) {
		View view = jEdit.getActiveView();
		Object vfsSession = vfs.createVFSSessionSafe(dir, view);
		try {
			try {
				return findRootURI(vfs, vfsSession, view, dir, pred);
			} finally {
				vfs._endVFSSession(vfsSession, view);
			}
		} catch (IOException e) {
			Log.log(Log.ERROR, Utils.class, "Error closing VFS session", e);
			return null;
		}
		return findRoot(dir.getParent(), pred);
	}

	public static Path findRoot(Buffer buffer, Predicate<Path> pred) {
		return findRoot(Paths.get(buffer.getDirectory()), pred);
	public static String findRootURI(Buffer buffer, VfsPredicate pred) {
		return findRootURI(buffer.getVFS(), buffer.getDirectory(), pred);
	}

	public static Path findRoot(Buffer buffer, String file) {
		return findRoot(buffer, new String[]{file});
	public static String findRootURI(Buffer buffer, String file) {
		return findRootURI(buffer, new String[]{file});
	}

	public static Path findRoot(Buffer buffer, String[] files) {
		return findRoot(Paths.get(buffer.getDirectory()), (dir) -> {
			for (String file : files) {
				if (Files.exists(dir.resolve(file))) {
					return true;
	public static String findRootURI(Buffer buffer, String[] files) {
		return findRootURI(buffer.getVFS(), buffer.getDirectory(), (vfs, session, view, dir) -> {
			try {
				Set<String> listedFileNames = new TreeSet<>();
				for (VFSFile file : vfs._listFiles(session, dir, view)) {
					listedFileNames.add(file.getName());
				}
				for (String file : files) {
					if (listedFileNames.contains(file)) {
						return true;
					}
				}
			} catch (IOException e) {
				Log.log(Log.ERROR, Utils.class, "Error listing files while getting root URI", e);
			}
			return false;
		});

M lsp/jEditLanguageClient.java => lsp/jEditLanguageClient.java +15 -11
@@ 69,11 69,6 @@ import com.google.gson.JsonObject;

import errorlist.DefaultErrorSource.DefaultError;

import projectviewer.ProjectManager;
import projectviewer.vpt.VPTProject;
import projectviewer.vpt.VPTDirectory;
import projectviewer.vpt.VPTFile;

// See https://github.com/eclipse/lsp4j/blob/main/org.eclipse.lsp4j/src/main/java/org/eclipse/lsp4j/services/LanguageClient.java

public class jEditLanguageClient implements LanguageClient {


@@ 748,17 743,23 @@ public class jEditLanguageClient implements LanguageClient {
	// NOTE: This doesn't seem to refresh the ProjectViewer interface.
	// Running AutoReimporter.runNow() does, but that seems heavy-handed.
	private void addToProjects(boolean isDir, String uri) {
		if (jEdit.getPlugin("projectviewer.ProjectPlugin") == null) {
			return;
		}
		Log.log(Log.DEBUG, this, "Adding to projects: " + uri);
		try {
			for (VPTProject project : ProjectManager.getInstance().getProjects()) {
				project = ProjectManager.getInstance().getProject(project.getName()); // ensure it's loaded
			for (projectviewer.vpt.VPTProject project : projectviewer.ProjectManager.getInstance().getProjects()) {
				project = projectviewer.ProjectManager.getInstance().getProject(project.getName()); // ensure it's loaded
				if (Paths.get(new URI(uri).getPath()).startsWith(Paths.get(project.getRootPath()))) {
					if (isDir) {
						Log.log(Log.DEBUG, this, "Registering new directory in " + project + ": " + uri);
						project.registerNodePath(new VPTDirectory(uri));
						// NOTE: For some reason, the call to project.registerNodePath() causes the plugin to
						// fail loading due to a NoClassDefFound error if we try to make ProjectViewer an
						// optional dependency.
						project.registerNodePath(new projectviewer.vpt.VPTDirectory(uri));
					} else {
						Log.log(Log.DEBUG, this, "Registering new file in " + project + ": " + uri);
						project.registerNodePath(new VPTFile(uri));
						project.registerNodePath(new projectviewer.vpt.VPTFile(uri));
					}
				} else {
					Log.log(Log.WARNING, this, "URI " + uri + " does not start with " + project.getRootPath());


@@ 774,9 775,12 @@ public class jEditLanguageClient implements LanguageClient {
	}

	private void removeFromProjects(String uri) {
		if (jEdit.getPlugin("projectviewer.ProjectPlugin") == null) {
			return;
		}
		Log.log(Log.DEBUG, this, "Removing from projects: " + uri);
		for (VPTProject project : ProjectManager.getInstance().getProjects()) {
			project = ProjectManager.getInstance().getProject(project.getName()); // ensure it's loaded
		for (projectviewer.vpt.VPTProject project : projectviewer.ProjectManager.getInstance().getProjects()) {
			project = projectviewer.ProjectManager.getInstance().getProject(project.getName()); // ensure it's loaded
			Log.log(Log.DEBUG, this, "Removing path from " + project + ": " + uri);
			project.unregisterNodePath(uri);
		}