~damien/jedit-lsp

a2c5092290c5cfe48cc2173fa3a731e8a0583854 — Damien Radtke 29 days ago bcb231b master
Add clojure-lsp
M LSP.props => LSP.props +17 -1
@@ 66,7 66,22 @@ messages.lsp.action-unsupported.title=Action Unsupported
messages.lsp.action-unsupported.message=The language server has indicated that this action is not supported: %s

# Define available LSP servers
lsp.servers=gopls jdtls
lsp.servers=clojure-lsp gopls jdtls
lsp.server.clojure-lsp.command=${PLUGIN_HOME}/clojure-lsp/clojure-lsp
lsp.server.clojure-lsp.install=releaseInfo = lsp.helpers.GitHub.getRelease("clojure-lsp", "clojure-lsp", "latest"); \
							   dest = lsp.Utils.getPluginSubdir("clojure-lsp"); \
                               downloaded = lsp.helpers.GitHub.downloadAsset(releaseInfo, "clojure-lsp-native-linux-amd64.zip", dest); \
                               if (downloaded) { \
							   	   if (new File(dest, "clojure-lsp").setExecutable(true, false)) { \
								       view.getStatus().setMessageAndClear("Downloaded to " + dest); \
								   } else { \
								       Macros.error(view, "Could not set to executable"); \
								   } \
                               } else { \
                                   Macros.error(view, "Could not find the appropriate asset"); \
                               }
lsp.server.clojure-lsp.root-files=project.clj deps.edn

lsp.server.gopls.command=gopls
lsp.server.gopls.install=exec("go install golang.org/x/tools/gopls@latest");
lsp.server.gopls.root-files=go.work go.mod


@@ 79,5 94,6 @@ lsp.server.jdtls.root-files=build.xml pom.xml settings.gradle settings.gradle.kt
lsp.server.jdtls.hook.pre-init=initOptions = new HashMap(); initOptions.put("settings", settings.getAll()); params.setInitializationOptions(initOptions);

# Map a mode to the language server to use
mode.clojure.languageServer=clojure-lsp
mode.go.languageServer=gopls
mode.java.languageServer=jdtls

M gradle.properties => gradle.properties +1 -1
@@ 1,1 1,1 @@
org.gradle.java.home=/usr/lib64/jvm/java-11
org.gradle.java.home=/usr/lib/jvm/java-11-openjdk-amd64

M ivy.xml => ivy.xml +1 -0
@@ 23,5 23,6 @@

      <!-- Used in scripts that download LSP servers -->
      <dependency org="org.apache.commons" name="commons-compress" rev="1.23.0"/>
      <dependency org="org.json" name="json" rev="20240303"/>
    </dependencies>
</ivy-module>

M lsp/bsh/eclipseDownload.bsh => lsp/bsh/eclipseDownload.bsh +2 -1
@@ 1,4 1,5 @@
// Example: eclipseDownload("jdtls", "1.23.0")
// TODO: support the string "latest" to automatically find the latest version
eclipseDownload(view, name, version, destination) {
	downloadBase = "https://download.eclipse.org/" + name + "/milestones/" + version;
	


@@ 30,7 31,7 @@ eclipseDownload(view, name, version, destination) {
	// extract the archive
	dest = new File(destination);
	org.gjt.sp.jedit.io.FileVFS.recursiveDelete(dest);
	lsp.Utils.extractTar(new java.util.zip.GZIPInputStream(new ByteArrayInputStream(archive)), dest);
	lsp.helpers.Archive.extractTar(new java.util.zip.GZIPInputStream(new ByteArrayInputStream(archive)), dest);
	
	Macros.message(view, name + " downloaded to " + dest.getAbsolutePath());
}

A lsp/bsh/githubDownload.bsh => lsp/bsh/githubDownload.bsh +21 -0
@@ 0,0 1,21 @@
// Example: githubDownload("clojure-lsp", "clojure-lsp", "latest")
githubDownload(org, repo, tag, destination) {
	releaseInfo = lsp.helpers.GitHub.getRelease(org, repo, tag);
	for (asset : releaseInfo.getJSONArray("assets")) {
		// TODO: make the OS here configurable
		String assetName = asset.getString("name");
		if ("clojure-lsp-native-linux-amd64.zip".equals(assetName)) {
			archive = lsp.Utils.readRemoteBytes(asset.getString("browser_download_url"));

			// extract the archive
			dest = new File(destination);
			org.gjt.sp.jedit.io.FileVFS.recursiveDelete(dest);
			lsp.helpers.Archive.extractZip(new ByteArrayInputStream(archive), dest);

			Macros.message(view, assetName + " downloaded to " + dest.getAbsolutePath());
			return;
		}
	}

	Macros.message(view, "Valid asset not found!");
}

M src/main/java/lsp/InstallDialog.java => src/main/java/lsp/InstallDialog.java +7 -1
@@ 58,10 58,16 @@ public class InstallDialog extends EnhancedDialog {

	@Override
	public void ok() {
		view.getStatus().setMessage("Installing LSP server: " + this.selected + "...");
		String statusMessage = "Installing LSP server: " + this.selected + "...";
		view.getStatus().setMessage(statusMessage);
		Log.debug(this, "Installing LSP server: " + this.selected);
		ThreadUtilities.runInBackground(new Task("Installing LSP server " + this.selected, () -> {
			BeanShell.eval(view, BeanShell.getNameSpace(), this.selected.getInstallScript());
			ThreadUtilities.runInDispatchThread(() -> {
				if (statusMessage.equals(view.getStatus().getMessage())) {
					view.getStatus().setMessage(null);
				}
			});
		}));
		super.dispose();
	}

M src/main/java/lsp/Utils.java => src/main/java/lsp/Utils.java +0 -37
@@ 41,9 41,6 @@ import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentPositionParams;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveEntry;

public class Utils {
	private Utils() {}


@@ 246,40 243,6 @@ public class Utils {
		mode.setProperty("languageServer", languageServer);
	}

	public static void extractTar(InputStream in, File targetDir) throws IOException {
		try (ArchiveInputStream i = new TarArchiveInputStream(in)) {
			extract(i, targetDir);
		}
	}

	// From https://commons.apache.org/proper/commons-compress/examples.html#Common_Extraction_Logic
	private static void extract(ArchiveInputStream i, File targetDir) throws IOException {
		if (!targetDir.isDirectory() && !targetDir.mkdirs()) {
			throw new IOException("failed to create target directory: " + targetDir.getAbsolutePath());
		}
		ArchiveEntry entry = null;
		while ((entry = i.getNextEntry()) != null) {
			if (!i.canReadEntryData(entry)) {
				// log something?
				continue;
			}
			File f = new File(targetDir, entry.getName());
			if (entry.isDirectory()) {
				if (!f.isDirectory() && !f.mkdirs()) {
					throw new IOException("failed to create directory " + f);
				}
			} else {
				File parent = f.getParentFile();
				if (!parent.isDirectory() && !parent.mkdirs()) {
					throw new IOException("failed to create directory " + parent);
				}
				try (OutputStream o = Files.newOutputStream(f.toPath())) {
					IOUtilities.copyStream(null, i, o, true);
				}
			}
		}
	}

	public static String readRemoteLine(String url) throws IOException {
		BufferedReader reader = new BufferedReader(new InputStreamReader(new URL(url).openStream()));
		return reader.readLine();

A src/main/java/lsp/helpers/Archive.java => src/main/java/lsp/helpers/Archive.java +56 -0
@@ 0,0 1,56 @@
package lsp.helpers;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;

import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveEntry;

import org.gjt.sp.util.IOUtilities;

public class Archive {
	public static void extractTar(InputStream in, File targetDir) throws IOException {
		try (ArchiveInputStream i = new TarArchiveInputStream(in)) {
			extract(i, targetDir);
		}
	}

	public static void extractZip(InputStream in, File targetDir) throws IOException {
		try (ArchiveInputStream i = new ZipArchiveInputStream(in)) {
			extract(i, targetDir);
		}
	}

	// From https://commons.apache.org/proper/commons-compress/examples.html#Common_Extraction_Logic
	private static void extract(ArchiveInputStream i, File targetDir) throws IOException {
		if (!targetDir.isDirectory() && !targetDir.mkdirs()) {
			throw new IOException("failed to create target directory: " + targetDir.getAbsolutePath());
		}
		ArchiveEntry entry = null;
		while ((entry = i.getNextEntry()) != null) {
			if (!i.canReadEntryData(entry)) {
				// log something?
				continue;
			}
			File f = new File(targetDir, entry.getName());
			if (entry.isDirectory()) {
				if (!f.isDirectory() && !f.mkdirs()) {
					throw new IOException("failed to create directory " + f);
				}
			} else {
				File parent = f.getParentFile();
				if (!parent.isDirectory() && !parent.mkdirs()) {
					throw new IOException("failed to create directory " + parent);
				}
				try (OutputStream o = Files.newOutputStream(f.toPath())) {
					IOUtilities.copyStream(null, i, o, true);
				}
			}
		}
	}
}

A src/main/java/lsp/helpers/GitHub.java => src/main/java/lsp/helpers/GitHub.java +51 -0
@@ 0,0 1,51 @@
package lsp.helpers;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

import org.json.JSONArray;
import org.json.JSONObject;

public class GitHub {
	private static HttpClient client = HttpClient.newBuilder()
			.followRedirects(HttpClient.Redirect.NORMAL)
			.build();

	private static HttpRequest.Builder baseRequest = HttpRequest.newBuilder()
		.header("Accept", "application/vnd.github+json")
		.header("X-GitHub-Api-Version", "2022-11-28");

	// Use "latest" for the tag to get the most recent release.
	// See https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28
	public static JSONObject getRelease(String org, String repo, String tag) throws IOException, InterruptedException {
		HttpRequest request = baseRequest.copy()
			.uri(URI.create("https://api.github.com/repos/" + org + "/" + repo + "/releases/" + tag))
			.build();
		HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
		if (response.statusCode() == 200) {
			return new JSONObject(response.body());
		}
		return null;
	}

	public static boolean downloadAsset(JSONObject releaseInfo, String assetName, String destination) throws IOException {
		JSONArray assets = releaseInfo.getJSONArray("assets");
		for (int i = 0; i < assets.length(); i++) {
			JSONObject asset = assets.getJSONObject(i);
			if (assetName.equals(asset.getString("name"))) {
				byte[] archive = lsp.Utils.readRemoteBytes(asset.getString("browser_download_url"));
				File dest = new File(destination);
				org.gjt.sp.jedit.io.FileVFS.recursiveDelete(dest);
				// TODO: make this configurable based on file extension
				Archive.extractZip(new ByteArrayInputStream(archive), dest);
				return true;
			}
		}
		return false;
	}
}