~damien/jedit-lsp

a0cfdfce2ec465c0b0659593976588258362e943 — Damien Radtke 4 months ago 44beae3
Add pre-init hook and update docs
5 files changed, 167 insertions(+), 34 deletions(-)

M LSP.props
M docs/index.html
M docs/index.md
M lsp/Server.java
M lsp/ServerDefinition.java
M LSP.props => LSP.props +1 -0
@@ 74,6 74,7 @@ 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.root-files=build.xml pom.xml settings.gradle settings.gradle.kts - build.gradle build.gradle.kts
lsp.server.jdtls.hook.pre-init=initOptions = new HashMap(); params.setInitializationOptions(initOptions);

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

M docs/index.html => docs/index.html +107 -20
@@ 20,6 20,70 @@ margin: 0 0.8em 0.2em -1.6em;
vertical-align: middle;
}
.display.math{display: block; text-align: center; margin: 0.5rem auto;}

pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } 
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } 
code span.at { color: #7d9029; } 
code span.bn { color: #40a070; } 
code span.bu { color: #008000; } 
code span.cf { color: #007020; font-weight: bold; } 
code span.ch { color: #4070a0; } 
code span.cn { color: #880000; } 
code span.co { color: #60a0b0; font-style: italic; } 
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } 
code span.do { color: #ba2121; font-style: italic; } 
code span.dt { color: #902000; } 
code span.dv { color: #40a070; } 
code span.er { color: #ff0000; font-weight: bold; } 
code span.ex { } 
code span.fl { color: #40a070; } 
code span.fu { color: #06287e; } 
code span.im { color: #008000; font-weight: bold; } 
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } 
code span.kw { color: #007020; font-weight: bold; } 
code span.op { color: #666666; } 
code span.ot { color: #007020; } 
code span.pp { color: #bc7a00; } 
code span.sc { color: #4070a0; } 
code span.ss { color: #bb6688; } 
code span.st { color: #4070a0; } 
code span.va { color: #19177c; } 
code span.vs { color: #4070a0; } 
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } 
</style>
  <style type="text/css">
body {


@@ 56,6 120,10 @@ be started and initialized automatically.</p>
<h2 id="configuration">Configuration</h2>
<p>The plugin comes with some servers defined already, which can be
configured in the plugin options.</p>
<p>Server-specific options are saved under the prefix
<code>lsp.server.&lt;name&gt;</code>. For example, the command to start
the <code>jdtls</code> language server is saved as the property
<code>lsp.server.jdtls.command</code>.</p>
<p><br></p>
<h3 id="general">General</h3>
<p>The <strong>General</strong> pane lets you specify which language


@@ 102,26 170,45 @@ symbols.</li>
project’s root folder (more on this below).</li>
</ol>
<h3 id="project-root">Project Root</h3>
<p>LSP servers generally expect to be initialized with some information
about the project the server should be acting on. This information is
provided through the <code>rootUri</code> initialization parameter (or,
more recently, a list of <code>workspaceFolders</code>). Notably, the
Java language server <code>jdtls</code> will not have access to most of
its features if it does not know where the project’s root is.</p>
<p>Most clients use language-specific logic to determine where the
project’s root is, often by looking for a specific file like
<code>go.mod</code> or <code>Cargo.toml</code>. However, in order to be
more flexible, this plugin takes a different approach.</p>
<p>When an LSP server is started, this plugin will take note of the
buffer that caused it to start. It will then look through all projects
defined via the <strong>Project Viewer</strong> plugin, and for each
project that contains that buffer, add its root path to a list. That
list of root paths is used to define <code>workspaceFolders</code>, and
whichever one comes first is used to define <code>rootUri</code>.</p>
<p>This approach might seem like a bit of a hassle since it requires you
to create a project with the expected root path, but it has the big
benefit of working with any language, and gives the user more control
over how the server is initialized.</p>
<p>This plugin allows for two different ways to specify the root of your
project, which is needed in order for LSP servers to function
correctly.</p>
<p>The first, and highest-priority approach, is to create a project
within the Project Viewer plugin. The LSP server’s root will be defined
as the root of the Project Viewer project containing the current buffer
(whichever kicked off the LSP server).</p>
<p>The second approach is the one taken by most editors with LSP
support, which is to define a set of “root files” that are used to
locate the project root. jEdit will start at the current buffer’s
directory and search upwards until it finds the directory containing one
of the specified root files. This is driven by the
<code>root-files</code> property attached to the LSP server, which is a
space-separated list of root file names. (The name <code>-</code> can be
used to determine priority; everything in the first group
[i.e. preceding the first <code>-</code>] will be tried first, until
jEdit hits the root of the filesystem, after which everything in the
next group will be attempted, etc.)</p>
<h2 id="advanced-configuration-hooks">Advanced Configuration: Hooks</h2>
<p>The LSP plugin allows for advanced configuration through
<em>hooks</em>, which allow you to specify BeanShell snippets to be run
at specific points in the LSP server lifecycle.</p>
<p>Hooks are defined using <code>hook.&lt;name&gt;</code> properties
attached to their respective LSP servers.</p>
<p>Currently, there is only one hook available, but there may be more
added in the future.</p>
<h3 id="pre-init"><code>pre-init</code></h3>
<p>The <code>pre-init</code> hook is run after the initialization
parameters have been constructed, but before the request has been sent.
This allows you to modify the parameters as you see fit, potentially
overriding any defaults set by the LSP plugin.</p>
<p>For example, to send a map of initialization options to
<code>jdtls</code>, you can define the property
<code>lsp.server.jdtls.hook.pre-init</code> as</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode java"><code class="sourceCode java"><span id="cb1-1"><a href="#cb1-1" tabindex="-1"></a><span class="co">// The &quot;params&quot; variable is an instance of InitializeParams.</span></span>
<span id="cb1-2"><a href="#cb1-2" tabindex="-1"></a><span class="co">// https://github.com/eclipse-lsp4j/lsp4j/blob/3c98376797b96922689af963b8403431135f0f35/org.eclipse.lsp4j/src/main/java/org/eclipse/lsp4j/Protocol.xtend#L4914</span></span>
<span id="cb1-3"><a href="#cb1-3" tabindex="-1"></a></span>
<span id="cb1-4"><a href="#cb1-4" tabindex="-1"></a>initOptions <span class="op">=</span> <span class="kw">new</span> <span class="bu">HashMap</span><span class="op">();</span></span>
<span id="cb1-5"><a href="#cb1-5" tabindex="-1"></a>params<span class="op">.</span><span class="fu">setInitializationOptions</span><span class="op">(</span>initOptions<span class="op">);</span></span></code></pre></div>
<!-- vim: set tw=100: -->
</body>
</html>

M docs/index.md => docs/index.md +40 -14
@@ 20,6 20,9 @@ mode is running. If it's not, it will be started and initialized automatically.

The plugin comes with some servers defined already, which can be configured in the plugin options.

Server-specific options are saved under the prefix `lsp.server.<name>`. For example, the command to
start the `jdtls` language server is saved as the property `lsp.server.jdtls.command`.

<br>

### General


@@ 69,22 72,45 @@ This plugin integrates with a few other ones to provide several useful features:

### Project Root

LSP servers generally expect to be initialized with some information about the project the server
should be acting on. This information is provided through the `rootUri` initialization parameter
(or, more recently, a list of `workspaceFolders`). Notably, the Java language server `jdtls` will
not have access to most of its features if it does not know where the project's root is.
This plugin allows for two different ways to specify the root of your project, which is needed in
order for LSP servers to function correctly.

The first, and highest-priority approach, is to create a project within the Project Viewer plugin.
The LSP server's root will be defined as the root of the Project Viewer project containing the
current buffer (whichever kicked off the LSP server).

The second approach is the one taken by most editors with LSP support, which is to define a set of
"root files" that are used to locate the project root. jEdit will start at the current buffer's
directory and search upwards until it finds the directory containing one of the specified root
files. This is driven by the `root-files` property attached to the LSP server, which is a
space-separated list of root file names. (The name `-` can be used to determine priority; everything
in the first group [i.e. preceding the first `-`] will be tried first, until jEdit hits the root of
the filesystem, after which everything in the next group will be attempted, etc.)

## Advanced Configuration: Hooks

The LSP plugin allows for advanced configuration through _hooks_, which allow you to specify
BeanShell snippets to be run at specific points in the LSP server lifecycle.

Hooks are defined using `hook.<name>` properties attached to their respective LSP servers.

Currently, there is only one hook available, but there may be more added in the future.

### `pre-init`

The `pre-init` hook is run after the initialization parameters have been constructed, but before the
request has been sent. This allows you to modify the parameters as you see fit, potentially
overriding any defaults set by the LSP plugin.

Most clients use language-specific logic to determine where the project's root is, often by looking
for a specific file like `go.mod` or `Cargo.toml`. However, in order to be more flexible, this
plugin takes a different approach.
For example, to send a map of initialization options to `jdtls`, you can define the property
`lsp.server.jdtls.hook.pre-init` as

When an LSP server is started, this plugin will take note of the buffer that caused it to start. It
will then look through all projects defined via the **Project Viewer** plugin, and for each project
that contains that buffer, add its root path to a list. That list of root paths is used to define
`workspaceFolders`, and whichever one comes first is used to define `rootUri`.
```java
// The "params" variable is an instance of InitializeParams.
// https://github.com/eclipse-lsp4j/lsp4j/blob/3c98376797b96922689af963b8403431135f0f35/org.eclipse.lsp4j/src/main/java/org/eclipse/lsp4j/Protocol.xtend#L4914

This approach might seem like a bit of a hassle since it requires you to create a project with the
expected root path, but it has the big benefit of working with any language, and gives the user more
control over how the server is initialized.
initOptions = new HashMap();
params.setInitializationOptions(initOptions);
```

<!-- vim: set tw=100: -->

M lsp/Server.java => lsp/Server.java +12 -0
@@ 271,6 271,18 @@ public class Server {
		}
		// params.setLocale(...);

		String hookPreInit = this.serverDefinition.getHookPreInit();
		if (hookPreInit != null && !hookPreInit.equals("")) {
			log(Log.NOTICE, "Running pre-initialization hook: " + hookPreInit);
			NameSpace ns = new NameSpace(BeanShell.getNameSpace(), "LSP " + this.getName() + ": pre-init");
			ns.setVariable("params", params);
			try {
				BeanShell._eval(jEdit.getActiveView(), ns, hookPreInit);
			} catch (Exception e) {
				log(Log.ERROR, "Error running pre-initialization hook", e);
			}
		}

		log(Log.DEBUG, "Initializing with parameters: " + params.toString());
		Utils.showTemporaryMessage("Initializing...");
		CompletableFuture<InitializeResult> future = this.handle.getRemoteProxy().initialize(params);

M lsp/ServerDefinition.java => lsp/ServerDefinition.java +7 -0
@@ 13,6 13,8 @@ public class ServerDefinition implements Comparable<ServerDefinition> {
	private String installScript;
	private List<List<String>> rootFiles;

	private String hookPreInit;

	public ServerDefinition(String name) {
		this.name = name;
		this.rawCommand = jEdit.getProperty(getProp("command"));


@@ 24,6 26,7 @@ public class ServerDefinition implements Comparable<ServerDefinition> {
			this.installScript = "";
		}
		this.rootFiles = parseRootFiles(jEdit.getProperty(getProp("root-files")));
		this.hookPreInit = jEdit.getProperty(getProp("hook.pre-init"));
	}

	private static List<List<String>> parseRootFiles(String raw) {


@@ 90,6 93,10 @@ public class ServerDefinition implements Comparable<ServerDefinition> {
		return this.rootFiles;
	}

	public String getHookPreInit() {
		return this.hookPreInit;
	}

	@Override
	public int compareTo(ServerDefinition other) {
		return this.name.compareTo(other.getName());