Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tool Window could not be created #86

Open
DeagleGross opened this issue Nov 15, 2021 · 14 comments
Open

Tool Window could not be created #86

DeagleGross opened this issue Nov 15, 2021 · 14 comments

Comments

@DeagleGross
Copy link

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.

@matkoch
Copy link
Member

matkoch commented Nov 17, 2021

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 ?

@DeagleGross
Copy link
Author

DeagleGross commented Nov 17, 2021

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!
image

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)
    image

  • Sometimes an error like execution allowed from ui thread only appears. I've tried to use JetDispatcher.CurrentDispatcher.BeginInvoke() with lifetime overload to pass an action, but without success, because I've got
    The 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 or IL 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!

@matkoch
Copy link
Member

matkoch commented Nov 17, 2021

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 :)

@DeagleGross
Copy link
Author

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!

@matkoch
Copy link
Member

matkoch commented Nov 17, 2021

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.

@DeagleGross
Copy link
Author

DeagleGross commented Nov 17, 2021

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.

@matkoch
Copy link
Member

matkoch commented Nov 17, 2021

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.

@DeagleGross
Copy link
Author

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 :)

@auduchinok
Copy link
Member

auduchinok commented Nov 18, 2021

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.

@DeagleGross
Copy link
Author

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.

@auduchinok
Copy link
Member

auduchinok commented Nov 18, 2021

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.

@DeagleGross
Copy link
Author

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 :)

@DeagleGross
Copy link
Author

@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?

@DeagleGross
Copy link
Author

Also i wanted to ask you how to generate those C# and .kt files (models for page)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants