resharper-devguide
resharper-devguide copied to clipboard
Tool Window could not be created
Hello!
I am following articles Create Tool Window and Tool Windows in a dev guide. First of all, I find it confusing to see several different approaches on creating tool windows.
Also I have a trouble creating a tool window. There are lots of examples in a sample-resharper-plugin repository, however, there is no way to build that samples provided (because only c# source code is published).
Can you please help me with creation of tool windows - to describe it in a single way (and update docs if needed) or (it could be even better) add a real sample example of creating a tool window, not just putting some c# source code into a sample repo.
Thanks a lot in advance.
Hey! Before I go into detail, could you describe a little what your planning to create? Also, is this specifically for ReSharper, or should it also work in Rider ?
Hello! Thanks a lot for a reply, Matthias! Sorry for not describing the specific feature I want to add.
What I want to do
I am upgrading my SharpCoachPlugin github/jetbrains marketplace that does a job of generating mapping code from one class to another. And I want to add a tool window, where i can load logs of how the mapping went (i.e. You've succesfully mapped Request to Response
or Fields "Id" and "Name" were not found in "Response" model
).
I want to create a tool window like a Errors in a solution
to the (i.e.) right side of a Rider instance (screenshot). It is not required to work for ReSharper, however if you can describe how to make it work in ReSharper also, I would be grateful!
Documentation
So what I am doing - I am following the Create Tool Window ReSharper Dev Guide because it seems like a little bit newer than Tool Windows. I've decided to copy&paste code just to make it work.
My code
Tool Window Descriptor
namespace ReSharperPlugin.SharpCoachPlugin.Ui.Descriptors
{
[ToolWindowDescriptor(
ProductNeutralId="KeyboardHelperWindow",
Text="Keyboard Helper",
Type = ToolWindowType.SingleInstance,
VisibilityPersistenceScope = ToolWindowVisibilityPersistenceScope.Global,
Icon = typeof(MapLogo),
InitialDocking = ToolWindowInitialDocking.Right
)]
public class KeyboardHelperDescriptor : ToolWindowDescriptor
{
public KeyboardHelperDescriptor(IApplicationHost host) : base(host)
{
}
}
}
Tool Window Registar \ Accesser
namespace ReSharperPlugin.SharpCoachPlugin.Ui.Registars
{
public class KeyboardHelperRegistrar
{
private readonly SingleInstanceToolWindowClass _toolWindowClass;
private readonly ToolWindowInstance _toolWindowInstance;
public KeyboardHelperRegistrar(Lifetime lifetime, ToolWindowManager toolWindowManager,
KeyboardHelperDescriptor toolWindowDescriptor, IUIApplication uiApplication)
{
if (_toolWindowInstance != null) return;
_toolWindowClass = toolWindowManager.Classes[toolWindowDescriptor] as SingleInstanceToolWindowClass;
if (_toolWindowClass == null)
throw new ApplicationException("ToolWindowClass");
_toolWindowInstance = _toolWindowClass.RegisterInstance(lifetime, "My Tool Window", null,
(lt, twi) =>
{
var label = new RichTextLabel(uiApplication)
{
Dock = DockStyle.Fill
};
label.RichTextBlock.Add(new RichText("Hello World!", new TextStyle(FontStyle.Bold)));
label.RichTextBlock.Parameters = new RichTextBlockParameters(8, ContentAlignment.MiddleCenter);
return new EitherControl(lt, label);
});
}
public void Show()
{
_toolWindowInstance.EnsureControlCreated();
_toolWindowInstance.Show();
_toolWindowInstance.Visible.SetValue(true);
_toolWindowInstance.Active.SetValue(true);
_toolWindowInstance.QueryClose.SetValue(true);
}
}
}
I've created an action that will show that tool window in such a manner. You can see I've undertook several attempts on making it work, so I've even used IThreading.ReentrancyGuard
. I'll describe what errors I've got and how I tried to manage them below
[Action("ActionOpenMyToolWindow", "Open a sample tool window", Id = 543211)]
public class ActionOpenMyToolWindow : IExecutableAction, IInsertLast<MainMenuFeaturesGroup>
{
private async void RunAction(IDataContext context, DelegateExecute nextExecute)
{
var locks = context.GetComponent<IThreading>();
var lifetime = context.GetComponent<Lifetime>();
var toolWindowManager = context.GetComponent<ToolWindowManager>();
var toolWindowDescriptor = context.GetComponent<KeyboardHelperDescriptor>();
var environment = context.GetComponent<IUIApplication>();
var keyboardHelperRegistrar = new KeyboardHelperRegistrar(lifetime, toolWindowManager, toolWindowDescriptor, environment);
while (true)
{
if (locks.ReentrancyGuard.CanExecuteNow)
{
locks.ReentrancyGuard.Execute("nane", () =>
{
keyboardHelperRegistrar.Show();
});
}
else
{
await Task.Delay(1000, lifetime);
}
}
// lifetime.StartMainUnguarded(() => keyboardHelperRegistrar.Show());
// JetDispatcher.CurrentDispatcher.BeginInvoke(lifetime, "ActionOpenMyToolWindow", () => keyboardHelperRegistrar.Show());
}
public bool Update(IDataContext context, ActionPresentation presentation, DelegateUpdate nextUpdate)
{
return true;
}
public void Execute(IDataContext context, DelegateExecute nextExecute)
{
RunAction(context, nextExecute);
}
}
Surely, I've registered my action in a plugin.xml
file:
<action id="ActionOpenMyToolWindow" text="Show Current Solution"
class="com.jetbrains.rider.plugins.coachsharp.ActionOpenMyToolWindow"/>
And create a kotlin file for that action.
package com.jetbrains.rider.plugins.coachsharp
import com.jetbrains.rider.actions.base.RiderAnAction
class ActionOpenMyToolWindow : RiderAnAction(
"ActionOpenMyToolWindow",
"ActionOpenMyToolWindow",
"ActionOpenMyToolWindow")
Handling errors
-
A lot of times when I used
keyboardHelperRegistar.Show();
to try show a tool window, there was no error, but no toolwindow popping up also (below is a screenshot of how I launch an action) -
Sometimes an error like
execution allowed from ui thread only
appears. I've tried to useJetDispatcher.CurrentDispatcher.BeginInvoke()
withlifetime
overload to pass an action, but without success, because I've gotThe execution context must be guarded from reentrancy in order to execute this action.
. -
I've found this fix in unity plugin, studied the API and the source code and come up with such a solution to call a code using
ReentrancyGuard
:
while (true)
{
if (locks.ReentrancyGuard.CanExecuteNow)
{
locks.ReentrancyGuard.Execute("nane", () =>
{
keyboardHelperRegistrar.Show();
});
}
else
{
await Task.Delay(1000, lifetime);
}
}
But no tool window popped up and that's my problem.
My request
Please, can you help me do this:
- register a tool window in a UI (like
errors in a solution
orIL viewer
and other tool windows) - desribe how to place a design of a window in a WinForms or WPF file (i can do it from code, but if it is possible to leave a design in a *.xaml file, it is prefferable)
- if it is possible, open it at the end of a
ContextAction
in some cases. So that users of my plugin can perform an action, and if something went wrong, they will get a structured and well described error.
Sorry for such a long request, however I've tried to describe it as specifically as I could. Thanks a lot in advance!
Thanks for the details. I'm preparing something but currently run into an issue that I first need help with from a colleague.
One thing that I already can tell is that you can't use WinForms or WPF for the Rider implementation. It's JVM :)
Thanks for a reply!
So if Rider can not show any tool windows, what is the point of that section of documentation? I believe it has to alert user with BIG BOLD FONT that it can not be shown in the Rider instance itself :) Otherwise another guy believing in miracle (me) like showing a tool window with WPF markdown under the JVM implemented application will waste 10 hours straight trying to investigate any possibilities.
However, I am very excited about the upcoming ideas from yourself. Can you provide any thoughts about upcoming feature? Or maybe any planning dates of release? If I can help you with that somehow, feel free to keep in touch with me!
Thanks a lot in advance!
It's a minor thing that needs to be investigated. So should be soon.
About tool window et al – well, it's the ReSharper dev guide :) Most things will work without much additional effort, but UI components are simply a bit more difficult.
So just to make sure, is your developed feature about easing the tool window development for Rider? Sorry for disturbing you with these questions, I just want to make sure if I need to try implement it using java\kotlin or wait for your solution to be deployed.
About tool window et al – well, it's the ReSharper dev guide :) Most things will work without much additional effort, but UI components are simply a bit more difficult.
That is the reason I believe it should be documented. A lot of the things easily work for Rider and for ReSharper as well (and I want to take a moment to say a thank you for creating such a API). But there are a couple of places (like this) where there is no possibility to run it and before wasting some time on studying the documentation and trying to run the code I think it is much easier for users to read a single line "This is not working for Rider, only for ReSharper". However, officially you are right - it is a ReSharper dev guide.
It's not a feature, just a sample that I'm trying to prepare. From my side, the best source to get going is the https://github.com/jetbrains/resharper-rider-plugin repository (last commit is WIP for the sample). We also have a blog post talking about plugin development – https://blog.jetbrains.com/dotnet/2019/02/14/writing-plugins-resharper-rider/ – and the bottom line there is to get in touch once you have the intent to write something. It's just really difficult to document all the – partially moving – parts of the SDK, and some areas are simply very rarely used. So yea, that's mostly it :) Updating the dev guide is definitely on my TODO list, but I'll have to wait for some other reason, unfortunately.
That would be awesome to see a working example in a resharper-rider-plugin repository! Thanks a lot for helping me and answering very rapidly, that is cool!
I have sent an email to you just in case I could be useful for doing some community stuff or contributing to some of JetBrains projects :)
So if Rider can not show any tool windows, what is the point of that section of documentation?
Rider can show them, but as with the many of UI-related things, it became different than it used to be before Rider. ReSharper/Rider SDK now has special common UI-related APIs that internally use WPF in ReSharper and Swing in Rider. Using these APIs, UI for features like refactorings has to be implemented only once for both cases. These controls are prefixed with Be
in their names, so it makes it easier to distinguish controls working cross-tool.
There may be other options to consider instead of creating a new tool window: too many of them may degrade UX and there's been a shift in simplifying and combining some of them. For instance, Rider 2021.3 has enabled the reworked Errors
tool window (that is implemented in IntelliJ and is extensible), and it should be possible to add a new tab there. Not sure, if using this approach would be easy for implementing your idea in a way it's easily shared for both IDEs, but at least in Rider that would probably be the preffered way to do it. (I'm looking if there's a way to use it in ReSharper too.)
The logs for an action could be shown as a new tab in Run
tool window, many of IntelliJ features integrate there for similar purposes.
If a separate tool window is still preffered, please look at JetBrains.ReSharper.Feature.Services.UI.Automation.BeControlsDemo.DemoActionBeControls.ShowToolWindow
in the SDK. It creates a new tool window and populates it with some controls. These demo actions are available when running Rider in internal mode.
Hello, Eugene! Thanks for a reply.
Basically, my plugin's users are mostly developers using Rider. So I've decided to implement just a Java.Swing part and that's all.
By now I've decided just to make an integration between Java and C# part using basic .json
file: I'll store results of operation done by user in a json-file, and when tool window is opened, that json-file will be read and displayed. It seems a little bit strange, but I hope this will work fine. I can not imagine any better solution so far.
I think that Run
tool window is a little bit overloaded by logs of your application and I want to provide users a separate place to view any of information only my plugin is providing. I think Errors
tool window is also not a place I want to share logs.
Can you, please, tell me about JetBrains.ReSharper.Feature.Services.UI.Automation.BeControlsDemo.DemoActionBeControls.ShowToolWindow
- do I need still to develop a tool window using java? Do plugin end-users need to turn Rider in internal mode to see that tool windows? If yes, I think that's not a user-friendly approach.
Can you, please, tell me about JetBrains.ReSharper.Feature.Services.UI.Automation.BeControlsDemo.DemoActionBeControls.ShowToolWindow - do I need still to develop a tool window using java?
No, the initial idea of Be*
controls was to make it possible to write UI once in the .NET part, and they would properly display in both ReSharper and Rider, using respective UI toolkits.
Do plugin end-users need to turn Rider in internal mode to see that tool windows? If yes, I think that's not a user-friendly approach.
It's only these demo actions that require the internal mode, the constrols are used in many features during normal ReSharper/Rider operation. The actions could be good as an example of how you can show a tool window using Be
controls. I'm not sure if we have good documentation for them, so looking at actual examples could worth it.
Wow, so it is possible to create UI using C# only. Awesome!
If you could find any sample or actual examples, I would be very grateful. And if possible with plugin.xml
tool windows registrations and etc.
Thanks a lot for helping me :)
@matkoch I've viewed your commit and tried to replicate it, however there is a build error for me:
> Task :compileKotlin FAILED
w: Some JAR files in the classpath have the Kotlin Runtime library bundled into them. This may cause difficult to debug problems if there's a different version of the Kotlin Runtime library in the classpath. Consider removing the
se libraries from the classpath
w: F:\Projects\SharpCoachPlugin\build\riderRD-2021.2\lib\kotlin-stdlib-jdk8.jar: Library has Kotlin runtime bundled into it
e: F:\Projects\SharpCoachPlugin\src\rider\main\kotlin\com\jetbrains\rider\plugins\coachsharp\SamplePluginModelHost.kt: (23, 46): Unresolved reference: create
I don't know how to resolve it, because I am not familiar with java API of SDK and really don't understand how create()
method could be implemented. Please, fix it.
I have another question about implementation of my problem. I want to remind what I'm trying to implement - create a tool window, that receives logs for each executed contextAction of specific type.
I've created a tool window using Java (following this example) and Spring framework for designing UI.
And I wanted to make it work like this:
- C# code at the end of the action logs to a .json file at the user device
- Java code listens to updates of that file and redraws a UI
I've succeeded in showing UI once, but update of UI, after .json file contents have changed, is not working. I've tried to use Java Watch Service, but I believe it is not a right approach, because I can not save a reference to a Tool Window from ToolWindowFactory class:
public class MappingResultsWindowFactory implements ToolWindowFactory {
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
drawToolWindow(project, toolWindow);
setupContentWatcher(project, toolWindow);
}
public static void drawToolWindow(Project project, ToolWindow toolWindow){
MappingResultsWindow myToolWindow = new MappingResultsWindow(toolWindow);
ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
Content content = contentFactory.createContent(myToolWindow.getContent(), "", false);
toolWindow.getContentManager().addContent(content);
}
private void setupContentWatcher(Project project, ToolWindow toolWindow){
// here I create a thread, that watches file changes and tries to redraw toolWindow
new FileWatcher(new File("F:\\Projects\\SharpCoachPlugin\\design_builder_app\\test.json"), project, toolWindow).start();
}
}
I've searched for different approaches and have found a Message Infrastructure and Virtual File System. I can find documentation for Java, but there is no info for C# -> i.e. I tried to store logs of context action not in a real .json
, but in a VFS. But I didn't succeed to do it. I think that is the most easy to implement and suitable solution for me, can you, please, tell me how can I create a Virtual File
using C# and store all the info I want there?
Also i wanted to ask you how to generate those C# and .kt files (models for page)?