Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gradle/productsReleases.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
IU-2025.3.1
IU-2025.1.2
IU-2024.3.6
IU-2024.2.6
Expand Down
29 changes: 24 additions & 5 deletions src/main/java/org/nixos/idea/lsp/NixLspServerDescriptor.java
Original file line number Diff line number Diff line change
@@ -1,32 +1,51 @@
package org.nixos.idea.lsp;

import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.platform.lsp.api.ProjectWideLspServerDescriptor;
import com.intellij.util.execution.ParametersListUtil;
import org.eclipse.lsp4j.ClientCapabilities;
import org.eclipse.lsp4j.ConfigurationItem;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.nixos.idea.file.NixFileType;

import java.util.List;
import java.util.Optional;

@SuppressWarnings("UnstableApiUsage")
final class NixLspServerDescriptor extends ProjectWideLspServerDescriptor {

private final String myCommand;

NixLspServerDescriptor(@NotNull Project project, NixLspSettings settings) {
NixLspServerDescriptor(@NotNull Project project) {
super(project, "Nix");
myCommand = settings.getCommand();
}

@Override
public @NotNull ClientCapabilities getClientCapabilities() {
ClientCapabilities clientCapabilities = super.getClientCapabilities();
clientCapabilities.getWorkspace().setConfiguration(true);
return clientCapabilities;
}

@Override
public @NotNull GeneralCommandLine createCommandLine() throws ExecutionException {
List<String> argv = ParametersListUtil.parse(myCommand, false, true);
List<String> argv = ParametersListUtil.parse(NixLspSettings.getInstance(getProject()).getCommand(), false, true);
return new GeneralCommandLine(argv);
}

@Override
public @Nullable Object getWorkspaceConfiguration(@NotNull ConfigurationItem item) {
return Optional.ofNullable(NixLspSettings.getInstance(getProject()).getConfiguration())
.map(JsonParser::parseString)
.map(JsonElement::getAsJsonObject)
.map(jsonObject -> jsonObject.get(item.getSection()))
.orElse(null);
}

@Override
public boolean isSupportedFile(@NotNull VirtualFile virtualFile) {
return virtualFile.getFileType() == NixFileType.INSTANCE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ public final class NixLspServerSupportProvider implements LspServerSupportProvid
@Override
public void fileOpened(@NotNull Project project, @NotNull VirtualFile virtualFile, @NotNull LspServerStarter lspServerStarter) {
if (virtualFile.getFileType() == NixFileType.INSTANCE) {
NixLspSettings settings = NixLspSettings.getInstance();
NixLspSettings settings = NixLspSettings.getInstance(project);
if (settings.isEnabled()) {
lspServerStarter.ensureServerStarted(new NixLspServerDescriptor(project, settings));
lspServerStarter.ensureServerStarted(new NixLspServerDescriptor(project));
}
}
}
Expand Down
12 changes: 9 additions & 3 deletions src/main/java/org/nixos/idea/lsp/NixLspSettings.kt
Original file line number Diff line number Diff line change
@@ -1,35 +1,41 @@
package org.nixos.idea.lsp

import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.BaseState
import com.intellij.openapi.components.RoamingType
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.SimplePersistentStateComponent
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.openapi.project.Project
import org.nixos.idea.settings.NixStoragePaths
import org.nixos.idea.settings.SimplePersistentStateComponentHelper.delegate
import java.util.ArrayDeque
import java.util.Collections
import java.util.Deque

@Service(Service.Level.PROJECT)
Comment thread
JojOatXGME marked this conversation as resolved.
@State(name = "NixLspSettings", storages = [Storage(value = NixStoragePaths.TOOLS, roamingType = RoamingType.LOCAL)])
class NixLspSettings : SimplePersistentStateComponent<NixLspSettings.State>(State()) {

class State : BaseState() {
var enabled by property(false)
var command by string()
var history: Deque<String> by property(ArrayDeque(), { it.isEmpty() })
var configuration by string(DEFAULT_CONFIGURATION)
}

var isEnabled: Boolean by delegate(State::enabled)
var command: String by delegate(State::command, State::history)
val commandHistory: Collection<String>
get() = Collections.unmodifiableCollection(state.history)
var configuration: String? by delegate(State::configuration)

companion object {
private const val DEFAULT_CONFIGURATION: String = "{}"

@JvmStatic
fun getInstance(): NixLspSettings {
return ApplicationManager.getApplication().getService(NixLspSettings::class.java)
fun getInstance(project: Project): NixLspSettings {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: I am a bit worried about moving the settings to the project level. This change would mean that users have to enable the language server for every project separately. I guess it also means the language server will be disabled for all users after the update. Is there any rational you want to share about your decision?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it also means the language server will be disabled for all users after the update

That is something we could handle with an migration, by keeping the old application wide settings service and initializing the project wide settings service with it. However I thought the effort is not wort it.

I am a bit worried about moving the settings to the project level. This change would mean that users have to enable the language server for every project separately.

Yes I also thought about this. However when considering jetbrains own plugins, for example vue.js, those are also project wide configured.
However they come with the default "on" and can only be project wide be turned off. In our case I am not comfortable with defining a default, as this would also include choosing a default language server command.
Lastly we might also mix project wide and application wide, I have done this for another intellij plugin as well. That works by having two services, an application wide service that store in our case the command and if its enabled and an project wide service that would store the configuration.

What do you think ?

image

@JojOatXGME JojOatXGME Jan 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not comfortable with defining a default

Same for me.

an application wide service that store in our case the command and if its enabled and an project wide service that would store the configuration.

I think I would prefer an option where the user would choose which level to use. Similar to how the code style settings allow you to choose profiles either on a project or application level. But without the profiles. Just a checkbox or something.

You haven't explained why you want to have project-specific configurations, but I assume you have multiple projects using different setups or styles. In the same way, I could imagine that some project also uses a different language server.

What do you think? I could also try to make this work if you prefer to not spend more time on it. Otherwise, I think I would also be fine with just moving the option to the project-level, but we would definitely have to document it in the changelog.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You haven't explained why you want to have project-specific configurations, but I assume you have multiple projects using different setups or styles. In the same way, I could imagine that some project also uses a different language server

Yes. I believe you would customize the workspace configuration per project. I.e. in my case I define the nixd options, so that the current nixos autocompletion is using my flake's options and not those of some channel. Similarly I have an option for home-manager that would also only apply to this project. Even though I would say its likely that someone uses one language server for one project and the other for the other, we might as well apply this to everything.

What do you think? I could also try to make this work if you prefer to not spend more time on it. Otherwise, I think I would also be fine with just moving the option to the project-level, but we would definitely have to document it in the changelog.

I can imagine your proposal to be convenient, I will give it a try and see how it feels. I still believe we could also only make the workspace configuration a project setting because this is probably the only one that someone would change on a per project level. I will work on this, let's see when I will find the time :)

return project.getService(NixLspSettings::class.java)
}
}
}
35 changes: 30 additions & 5 deletions src/main/java/org/nixos/idea/lsp/NixLspSettingsConfigurable.kt
Original file line number Diff line number Diff line change
@@ -1,30 +1,37 @@
package org.nixos.idea.lsp

import com.intellij.json.JsonFileType
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.options.BoundSearchableConfigurable
import com.intellij.openapi.options.Configurable
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.project.Project
import com.intellij.platform.lsp.api.LspServerManager
import com.intellij.ui.EditorTextField
import com.intellij.ui.RawCommandLineEditor
import com.intellij.ui.components.JBCheckBox
import com.intellij.ui.dsl.builder.Align
import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.Cell
import com.intellij.ui.dsl.builder.LabelPosition
import com.intellij.ui.dsl.builder.bindSelected
import com.intellij.ui.dsl.builder.panel
import com.intellij.ui.dsl.builder.selected
import com.intellij.ui.dsl.builder.toMutableProperty
import org.nixos.idea.settings.ui.CommandSuggestionsPopup
import org.nixos.idea.settings.ui.UiDslExtensions.bindText
import org.nixos.idea.settings.ui.UiDslExtensions.placeholderText
import org.nixos.idea.settings.ui.UiDslExtensions.suggestionsPopup
import org.nixos.idea.settings.ui.UiDslExtensions.validateOnReset
import org.nixos.idea.settings.ui.UiDslExtensions.validateWhenTextChanged
import org.nixos.idea.settings.ui.UiDslExtensions.warnOnInput
import java.awt.Dimension

class NixLspSettingsConfigurable :
class NixLspSettingsConfigurable(val project: Project) :
BoundSearchableConfigurable("Language Server (LSP)", "org.nixos.idea.lsp.NixLspSettingsConfigurable"),
Configurable.Beta {

override fun createPanel() = panel {
val settings = NixLspSettings.getInstance()
val settings = NixLspSettings.getInstance(project)
lateinit var enabledCheckBox: Cell<JBCheckBox>
row {
enabledCheckBox = checkBox("Enable language server")
Expand All @@ -43,16 +50,34 @@ class NixLspSettingsConfigurable :
it.text.isNullOrBlank()
}
}
row {
cell(createJsonEditorTextField())
.align(Align.FILL)
.label("Workspace configuration:", LabelPosition.TOP)
.bind(
EditorTextField::getText, EditorTextField::setText,
settings::configuration.toMutableProperty()
)
}.resizableRow()
}.enabledIf(enabledCheckBox.selected)
}

@Suppress("UnstableApiUsage")
override fun apply() {
super.apply()
reset() // Update UI components to use normalized property values
for (project in ProjectManager.getInstance().openProjects) {
LspServerManager.getInstance(project).stopAndRestartIfNeeded(NixLspServerSupportProvider::class.java)
LspServerManager.getInstance(project).stopAndRestartIfNeeded(NixLspServerSupportProvider::class.java)
}

private fun NixLspSettingsConfigurable.createJsonEditorTextField(): EditorTextField {
val editorTextField = object : EditorTextField(null, project, JsonFileType.INSTANCE, false, false) {
override fun createEditor(): EditorEx {
val editor = super.createEditor()
editor.settings.isUseSoftWraps = true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Not sure if I personally would enable soft-wraps here. What was the rationale for enabling it? Would it make sense to use the user's code style settings?

Also are the scrollbars not visible by default? O.o

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I just chose them because I thought it was sensible. Maybe I would even reconsider it and use softwrap without any scrollbars. If we don't use softwrap the editor takes as much horizontal space as it needs which leads to a horizontal scrollbar of the settings panel, which I would say should not happen (the scrollbar settings don't have any effect as far as I can tell so I would remove them). So I would also not let the user choose.

image

I remove the scrollbars in 1562d0f

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I agree that we don't want a horizontal scrollbar on the settings panel.

return editor
}
}
return editorTextField
}
}

Expand Down
6 changes: 2 additions & 4 deletions src/main/resources/META-INF/nix-idea-ultimate.xml
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
<idea-plugin>
<depends>com.intellij.modules.json</depends>
Comment thread
JojOatXGME marked this conversation as resolved.
<extensions defaultExtensionNs="com.intellij">

<applicationService
serviceImplementation="org.nixos.idea.lsp.NixLspSettings"/>

<applicationConfigurable
<projectConfigurable
parentId="org.nixos.idea.settings.ui.NixLangSettingsConfigurable"
displayName="Language Server (LSP)"
id="org.nixos.idea.lsp.NixLspSettingsConfigurable"
Expand Down
Loading