Skip to content

Commit

Permalink
GDScript LSP support (JetBrains#120)
Browse files Browse the repository at this point in the history
* provide a settings page with some LSP options

* optimized icon

* provide cor runtimeType for run scene - fix JetBrains#117

* fix bug updating path of Godot4 exe

* fix JetBrains#106 Run-configurations for Godot without C#

* working on settings

* ++

* ++

* stats for pure GDScript solutions

* default UseDynamicPort to false

* wip LspServerSupportProvider and indent options

* working lsp

* live templates

* fix bug with specifying --lsp-port

* cleanup

* avoid multiple startServersIfNeeded

* support dynamic port for Godot 4.3+

* disable lsp by default, disable and hide port when "Use a random free port" is checked

* close lang server on disabling the setting

* add comment

* extract isMatchingFile logic

* merge queue for starting lsp servers

* fix merge
  • Loading branch information
van800 authored and Nepp3r committed Jan 23, 2024
1 parent 50479fd commit 28378b7
Show file tree
Hide file tree
Showing 29 changed files with 868 additions and 96 deletions.
4 changes: 2 additions & 2 deletions resharper/.run/runIde.run.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="runIde" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/build/gradle-launcher/bin/Debug/net461/gradle-launcher.exe" />
<option name="EXE_PATH" value="$PROJECT_DIR$/build/gradle-launcher/bin/Debug/net472/gradle-launcher.exe" />
<option name="PROGRAM_PARAMETERS" value="../../../rider runIde -PskipDotnet=true" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/build/gradle-launcher/" />
<option name="PASS_PARENT_ENVS" value="1" />
Expand All @@ -16,7 +16,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="0" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETFramework,Version=v4.6.1" />
<option name="PROJECT_TFM" value=".NETFramework,Version=v4.7.2" />
<method v="2">
<option name="Build" />
</method>
Expand Down
1 change: 1 addition & 0 deletions resharper/godot-support.sln
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Global
{A0CA2638-35EE-4034-B1FA-F13345121485}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A0CA2638-35EE-4034-B1FA-F13345121485}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A0CA2638-35EE-4034-B1FA-F13345121485}.Release|Any CPU.Build.0 = Release|Any CPU
{A0CA2638-35EE-4034-B1FA-F13345121485}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0D331B4B-9B49-41A6-8322-0CFE82FA238E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0D331B4B-9B49-41A6-8322-0CFE82FA238E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0D331B4B-9B49-41A6-8322-0CFE82FA238E}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down
257 changes: 257 additions & 0 deletions resharper/src/Application/Resources/Icons/Godot.cs

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions resharper/src/Application/Settings/GodotSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using JetBrains.Application.Settings;
using JetBrains.ReSharper.Resources.Settings;
using JetBrains.Rider.Model.Godot.FrontendBackend;

namespace JetBrains.ReSharper.Plugins.Godot.Application.Settings
{
[SettingsKey(typeof(CodeEditingSettings), "Godot plugin settings")]
public class GodotSettings
{
// LSP
[SettingsEntry(LanguageServerConnectionMode.Never, "Different ways to connect LSP")]
public LanguageServerConnectionMode LanguageServerConnectionMode;

[SettingsEntry("127.0.0.1", "RemoteHost")]
public string RemoteHost;

[SettingsEntry(6005, "Remote host port")]
public int RemoteHostPort;

[SettingsEntry(false, "Use a random free port, supported with Godot 4.3+")]
public bool UseDynamicPort;

// Debugging
[SettingsEntry(true, "Enable debugger extensions")]
public bool EnableDebuggerExtensions;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
using JetBrains.ReSharper.Psi.Util;
using JetBrains.Rider.Model.Godot.FrontendBackend;

namespace JetBrains.ReSharper.Plugins.Godot.Settings
namespace JetBrains.ReSharper.Plugins.Godot.Application.Settings
{
[SolutionComponent]
public class SettingsSynchronizer
Expand All @@ -23,6 +23,16 @@ public SettingsSynchronizer(Lifetime lifetime, ISolution solution, FrontendBacke
BindSettingToProperty(lifetime, solution, host, boundStore,
(GodotSettings s) => s.EnableDebuggerExtensions,
(model, args) => model.BackendSettings.EnableDebuggerExtensions.Value = args.New);

BindSettingToProperty(lifetime, solution, host, boundStore,
(GodotSettings s) => s.LanguageServerConnectionMode,
(model, args) => model.BackendSettings.LspConnectionMode.Value = args.New);
BindSettingToProperty(lifetime, solution, host, boundStore,
(GodotSettings s) => s.RemoteHostPort,
(model, args) => model.BackendSettings.RemoteHostPort.Value = args.New);
BindSettingToProperty(lifetime, solution, host, boundStore,
(GodotSettings s) => s.UseDynamicPort,
(model, args) => model.BackendSettings.UseDynamicPort.Value = args.New);
}

private static void BindSettingToProperty<TKeyClass, TEntryMemberType>(
Expand Down
83 changes: 83 additions & 0 deletions resharper/src/Application/UI/Options/GodotOptionsPage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#nullable enable

using System;
using System.Linq.Expressions;
using JetBrains.Application.Settings;
using JetBrains.Application.UI.Options;
using JetBrains.Application.UI.Options.OptionsDialog;
using JetBrains.Application.UI.Options.OptionsDialog.SimpleOptions;
using JetBrains.Application.UI.Options.OptionsDialog.SimpleOptions.ViewModel;
using JetBrains.DataFlow;
using JetBrains.IDE.UI.Options;
using JetBrains.Lifetimes;
using JetBrains.ReSharper.Feature.Services.OptionPages.CodeEditing;
using JetBrains.ReSharper.Plugins.Godot.Application.Settings;
using JetBrains.Rider.Model.Godot.FrontendBackend;
using JetBrains.Rider.Model.UIAutomation;
using JetBrains.UI.ThemedIcons;

namespace JetBrains.ReSharper.Plugins.Godot.Application.UI.Options
{
[OptionsPage(PID, Name, typeof(QydowydThemedIconsThemedIcons.Godot), ParentId = CodeEditingPage.PID,
HelpKeyword = "Settings_Godot_Engine")]
public class GodotOptionsPage : BeSimpleOptionsPage
{
public const string PID = "GodotPluginSettings";
public const string Name = "Godot Engine";

private static readonly Expression<Func<GodotSettings, LanguageServerConnectionMode>> ourLanguageServerConnectionMode =
s => s.LanguageServerConnectionMode;

private static readonly Expression<Func<GodotSettings, bool>> ourUseDynamicPort = s => s.UseDynamicPort;

private static readonly Expression<Func<GodotSettings, int>> ourHostPortAccessor =
s => s.RemoteHostPort;

public GodotOptionsPage(Lifetime lifetime,
OptionsPageContext pageContext,
OptionsSettingsSmartContext settingsStore)
: base(lifetime, pageContext, settingsStore)
{
AddNetworkSection();
}

private void AddNetworkSection()
{
AddHeader("Network");
using (Indent())
{
AddComboOption((GodotSettings s) => s.LanguageServerConnectionMode,
"Connecting LSP server:", string.Empty, string.Empty,
new RadioOptionPoint(LanguageServerConnectionMode.StartEditorHeadless, "Automatically start headless LSP server"),
// new RadioOptionPoint(LanguageServerConnectionMode.ConnectRunningEditor, "Attempt to connect the running Godot Editor"), // todo: commented because need some tricky waiting and probing the port
new RadioOptionPoint(LanguageServerConnectionMode.Never, "Never use LSP")
);
AddKeyword("Language server");

// AddTextBox(ourHostNameAccessor, "Host"); // host is always localhot, lets not allow changing it.

// Godot 4.3 and later
var useDynamic = AddBoolOption(ourUseDynamicPort, "Use a random free port (supported in Godot 4.3+)",
toolTipText: "Only supported by the Godot 4.3+");

var portOption = AddIntOption(ourHostPortAccessor, "Port");

// AddBinding(portOption, BindingStyle.IsEnabledProperty, ourUseDynamicPort, enable => !enable);

var sourceProperty = OptionsSettingsSmartContext.GetValueProperty(Lifetime, ourUseDynamicPort);
sourceProperty
.Change.Advise(Lifetime, () =>
{
// RIDER-104651 Visibility of a BeControl based on other settings works inconsistently
portOption.Enabled.Value = !sourceProperty.Value; // this always works
portOption.Visible.Value = sourceProperty.Value ? ControlVisibility.Collapsed: ControlVisibility.Visible; // this doesn't work initially, but starts working when you change sourceProperty back and forth
});

// AddBinding(portOption, BindingStyle.IsEnabledProperty, ourLanguageServerConnectionMode,
// mode => mode is not LanguageServerConnectionMode.Never);
AddBinding(useDynamic, BindingStyle.IsEnabledProperty, ourLanguageServerConnectionMode,
mode => mode is LanguageServerConnectionMode.StartEditorHeadless);
}
}
}
}
12 changes: 6 additions & 6 deletions resharper/src/ProjectModel/GodotMessagingClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@
using GodotTools.IdeMessaging;
using GodotTools.IdeMessaging.Requests;
using JetBrains.Application.Threading;
using JetBrains.Application.Threading.Tasks;
using JetBrains.Collections.Viewable;
using JetBrains.Lifetimes;
using JetBrains.ProjectModel;
using JetBrains.Rd.Base;
using JetBrains.ReSharper.Feature.Services.Protocol;
using JetBrains.Rider.Model.Godot.FrontendBackend;
using JetBrains.Threading;
using JetBrains.Util;
using ILogger = JetBrains.Util.ILogger;

Expand All @@ -30,9 +28,11 @@ public GodotMessagingClient(ISolution solution, ILogger logger, Lifetime lifetim
myLogger = logger;
var model = solution.GetProtocolSolution().GetGodotFrontendBackendModel();

model.MainProjectBasePath.AdviseOnce(lifetime, baseDir =>
model.GodotDescriptor.AdviseOnce(lifetime, descriptor =>
{
myClient = new Client(Identity, baseDir, this, this);
if (descriptor.IsPureGdScriptProject) return;
myClient = new Client(Identity, descriptor.MainProjectBasePath, this, this);
SubscribeConnected(logger, threading, model);
SubscribeDisconnected(logger, threading, model);
myClient.Start();
Expand All @@ -42,7 +42,7 @@ public GodotMessagingClient(ISolution solution, ILogger logger, Lifetime lifetim
private void SubscribeDisconnected(ILogger logger, IThreading threading, GodotFrontendBackendModel model)
{
// it looks like it subscribes to be called just once
myClient.AwaitDisconnected().ContinueWith(task =>
myClient.AwaitDisconnected().ContinueWith(_ =>
{
logger.Info("Godot Editor disconnected...");
model.EditorState.SetValue(GodotEditorState.Disconnected);
Expand All @@ -52,7 +52,7 @@ private void SubscribeDisconnected(ILogger logger, IThreading threading, GodotFr

private void SubscribeConnected(ILogger logger, IThreading threading, GodotFrontendBackendModel model)
{
myClient.AwaitConnected().ContinueWith(task =>
myClient.AwaitConnected().ContinueWith(_ =>
{
logger.Info("Godot Editor connected...");
model.EditorState.SetValue(GodotEditorState.Connected);
Expand Down
7 changes: 6 additions & 1 deletion resharper/src/ProjectModel/GodotTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ public class GodotTracker
public GodotTracker(ISolution solution, ILogger logger, ISolutionLoadTasksScheduler tasksScheduler)
{
var model = solution.GetProtocolSolution().GetGodotFrontendBackendModel();
if (solution.SolutionFile == null && solution.SolutionDirectory.Combine("project.godot").ExistsFile)
{
model.GodotDescriptor.SetValue(new GodotDescriptor(true, solution.SolutionDirectory.FullPath));
}

tasksScheduler.EnqueueTask(new SolutionLoadTask(GetType(),
SolutionLoadTaskKinds.Done,
() =>
Expand All @@ -25,7 +30,7 @@ public GodotTracker(ISolution solution, ILogger logger, ISolutionLoadTasksSchedu
if (!file.ExistsFile) continue;
MainProjectBasePath = file.Directory;
logger.Verbose($"Godot MainProjectBasePath: {file.Directory}");
model.MainProjectBasePath.SetValue(file.Directory.FullPath);
model.GodotDescriptor.SetValue(new GodotDescriptor(false, file.Directory.FullPath));
break;
}
}));
Expand Down
9 changes: 9 additions & 0 deletions resharper/src/ProjectModel/ProjectExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,13 @@ public static bool IsGodotProject([CanBeNull] this IProject project)
configuration.DefineConstants.Contains("GODOT;"));
}
}

public static class SolutionExtensions
{
public static bool IsGdScriptSolution([CanBeNull] this ISolution solution)
{
if (solution == null || !solution.IsValid()) return false;
return solution.SolutionFile == null && solution.SolutionDirectory.Combine("project.godot").ExistsFile;
}
}
}
13 changes: 0 additions & 13 deletions resharper/src/Settings/GodotSettings.cs

This file was deleted.

23 changes: 23 additions & 0 deletions resharper/src/UsageStatistics/GodotProjectTechnologyProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,27 @@ public IEnumerable<string> GetProjectTechnology(IProject project)
}
}
}

[SolutionComponent]
public class GodotSolutionTechnologyProvider : ISolutionTechnologyProvider
{
public IEnumerable<string> GetProjectTechnology(IProject project)
{
if (project.IsGodotProject())
{
yield return "Godot";
yield return "GameDev";
}
}

public IEnumerable<string> GetSolutionTechnology(ISolution solution)
{
if (solution.IsGdScriptSolution())
{
yield return "Godot";
yield return "GameDev";
yield return "GDScript";
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ object GodotFrontendBackendModel : Ext(SolutionModel.Solution) {
+"Connected"
}

val GodotDescriptor = structdef("GodotDescriptor"){
field("isPureGdScriptProject", bool).documentation = "True for pure GdScript project"
field("mainProjectBasePath", string).documentation = "Path to the folder with the project.godot"
}

private val LanguageServerConnectionMode = enum {
+"Never"
+"ConnectRunningEditor"
+"StartEditorHeadless"
}

init {
setting(Kotlin11Generator.Namespace, "com.jetbrains.rider.model.godot.frontendBackend")
setting(CSharp50Generator.Namespace, "JetBrains.Rider.Model.Godot.FrontendBackend")
Expand All @@ -34,13 +45,18 @@ object GodotFrontendBackendModel : Ext(SolutionModel.Solution) {

// Misc backend/fronted context
property("godotPath", string).documentation = "Path to GodotEditor"
property("mainProjectBasePath", string).documentation = "Path to the folder with the project.godot"

// Settings stored in the backend
field("backendSettings", aggregatedef("GodotBackendSettings") {
property("enableDebuggerExtensions", bool)

property("lspConnectionMode", LanguageServerConnectionMode)
property("remoteHostPort", int)
property("useDynamicPort", bool)
})

property("godotDescriptor", GodotDescriptor)

property("editorState", GodotEditorState)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,15 @@ class FrontendBackendHost(project: Project) : LifetimedService() {
task
}

GodotProjectDiscoverer.getInstance(project).godotMonoPath.adviseNotNull(lifetime){
GodotProjectDiscoverer.getInstance(project).godot3Path.adviseNotNull(lifetime){
model.godotPath.set(it)
}

GodotProjectDiscoverer.getInstance(project).godotCorePath.adviseNotNull(lifetime){ s ->

GodotProjectDiscoverer.getInstance(project).godot4Path.adviseNotNull(lifetime){ s ->
// for Godot3 this is done in a separate place
RiderDebuggerWorkerModelManager.getModels().adviseNotNull(lifetime){
model.backendSettings.enableDebuggerExtensions.flowInto(lifetime,
it.value.godotDebuggerWorkerModel.showCustomRenderers)

}

model.godotPath.set(s)
Expand Down
Loading

0 comments on commit 28378b7

Please sign in to comment.