flutter / flutter-intellij

Flutter Plugin for IntelliJ
https://flutter.dev/using-ide
BSD 3-Clause "New" or "Revised" License
1.98k stars 317 forks source link

How to implement functionality similar to "Wrap with Center" in a flutter IntelliJ plugin? #6688

Open 5hirish opened 1 year ago

5hirish commented 1 year ago

Flutter IntelliJ plugin's functionality "Wrap with a widget..." has improved my productivity and developer experience. Thanks, everyone! I would like to extend this "Wrap with.." functionality to other custom widgets (internal design system components) that I tend to use frequently. Hence I would like to implement this functionality as a separate plugin. Please direct me to any documentation on how I can implement this (as an intentional action maybe?).

I was able to find the source code of the implementation:https://github.com/flutter/flutter-intellij/blob/d89a28108349ece4501d4bcbfe269ac460c240ea/flutter-idea/src/io/flutter/preview/WidgetEditToolbar.java#L305-L323

But I am struggling to understand how I can replicate or reuse or extend it in the plugin I am developing.

Steps to Reproduce

  1. On an existing flutter project in Android Studio set the dart file editor cursor on any widget in the widget class
  2. Android studio will suggest some quick actions in an indicator
  3. On clicking a menu with various "Wrap with..", "Swap..", "Move..", "Remove" quick actions will be listed to refactor your widgets

Screen-Grab:

Screenshot 2023-03-21 at 10 57 43 PM

Version info

[✓] Flutter (Channel stable, 3.7.7, on macOS 13.2.1 22D68 darwin-arm64, locale en-IN)
[✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 14.2)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2022.1)
[✓] IntelliJ IDEA Ultimate Edition (version 2022.3.3)
[✓] IntelliJ IDEA Ultimate Edition (version 2022.3.3)
[✓] IntelliJ IDEA Ultimate Edition (version 2022.3.2)
[✓] VS Code (version 1.75.1)
[✓] Connected device (2 available)
[✓] HTTP Host Availability

• No issues found!
stevemessick commented 1 year ago

Here's the official docs for writing plugins: https://plugins.jetbrains.com/docs/intellij/welcome.html

Additionally, there are many Flutter-specific plugins in the IntelliJ plugin marketplace. Open Settings > Plugins, select the Marketplace tab, and type "Flutter". You might find something similar to what you want to do. Most of them seem to be open-source, and their source code is linked in the Additional Info tab.

For the Flutter plugin, the actual source code change is performed by the Dart analysis server. You won't be able to re-use that.

5hirish commented 1 year ago

Yes, I have been taking a look at the source code of a few plugins on the marketplace. They are all relying on Dart PSI for now. I think I can try that approach out.

Just out of curiosity if I want to build a lightweight flutter-specific IDE how would I go about using the Dart analysis server for the same? Any documentation where it guides integrating the Dart analysis server in an IDE?

stevemessick commented 1 year ago

The Dart PSI was recently updated to support the new language features that will come with Dart 3. The Flutter plugin uses that parser in a few places, but mostly it uses the analysis server.

You'd want to use LSP. The Flutter plugin uses the older DAS protocol for comms, but it is not getting all the updates that LSP gets. The best source for LSP coding is the VS Code extension for Flutter: https://github.com/Dart-Code/Dart-Code

I'm not aware of any instructions on setting it up, but the Dart LSP spec at least explains things. https://github.com/dart-lang/sdk/tree/main/pkg/analysis_server/tool/lsp_spec

stevemessick commented 1 year ago

BTW @5hirish, does Wrap with widget... get you most of what you need? If so, your plugin could use the analysis server to create the SourceChange (that's the name used by DAS, and LSP may call it something else) then write your own handler to process it, substituting your own class name for Widget. If your widgets have additional args that might not be so useful.

The main problem with using the analysis server is going to be having two instances analyzing the same code. It might be tricky to keep them in sync. @DanTup is it possible, using LSP, for two clients to share a single analysis server?

DanTup commented 1 year ago

@DanTup is it possible, using LSP, for two clients to share a single analysis server?

It depends a little on what "client" means here. The LSP server (like the original DAS server) talks over stdin/stdout so only whoever has those streams can communicate with it. It's not possible for a second client to connect to an existing Dart LSP server, although if whoever started the initial server allowed another "client" to send requests over those streams, that would allow sharing of some sort.

Although - if I understand the goal correctly, that probably wouldn't help if IntelliJ is using a DAS server anyway (the server can only run in either LSP or DAS mode, not both).

The desire for more (or custom) "Wrap with.." refactors has come up before (there's some discussion in https://github.com/Dart-Code/Dart-Code/pull/4232). @5hirish out of interest, is your need just for "Wrap with Center" but swapping "Center" for a different class name, or are you trying to customise the rest of the inserted code too?

5hirish commented 1 year ago

Thanks, Steve, I will go through LSP. I would like to explore building a lightweight IDE for flutter as a project. I have a couple of low-code ideas I would like to implement to improve the flutter developer experience and speed.

Hi, @DanTup, I was planning to build a small IntelliJ plugin which would be a widget directory with widgets from the standard flutter library, widgets from the community and internal widgets, so that it is easy to reuse them across projects while maintaining the design consistency and allowing faster development. On IDE action a GUI window opens up with a library of different widgets, where the user can pick one as per their needs and immediately wrap it or add it to a row and customise it if required. It will also allow users to add their frequently used widgets to this widget directory as internal or community widgets for later reuse. (kinda like pub.dev but for UI components)

I would love to have DAS here so that when wrapping it automatically imports dependencies or manage any style variables or even insert the widget in the tree at the appropriate node. Now, I understand that it is not there, so I will go ahead with using IntelliJ Dart PSI which will simply inject the widget snippet without any analysis from the library of widgets.

This plugin is a way for me to validate whether small functionalities like a widget directory or similar low-code features boost productivity and if so, I would like to build a flutter IDE solely focused on such low-code approaches to free devs to work on business-critical aspects of the app and reduce the barrier of entry to build flutter apps. But for now, I am just taking it one step at a time.

Any input or criticism from you guys is appreciated on how I should approach this. I will be building a plugin first to validate my hypothesis and if it reaches say 10K installs I would like to pursue building a smart, low-code IDE for flutter.

DanTup commented 1 year ago

Unfortunately I can't think of a great way to do what you want. The server doesn't currently support wrapping with custom widgets like this, and the server would need to do the work for you to get things like imports for free. It's possible an analysis server plugin could get you closer, but that API is not currently supported and could change significantly if it becomes so.

If you had access to the analysis server (LSP or DAS), you could get most of the way by obtaining the generic "Wrap with widget" fix and then just replacing out the "widget" (which is a linked edit group/placeholder) with the class you actually want. It wouldn't add imports or support customising the rest of the code though (although you probably could do some string manipulation on it).

FWIW if you are building a Flutter IDE (or just using the analysis server in general), I would encourage using LSP over the original DAS protocol. As Steve notes, the original protocol may not get all of the things that the LSP protocol is getting (for example the LSP server gets Colors and Inlay Hints that the original protocol does not).

5hirish commented 1 year ago

Perfect will take a dive into LSP protocol! So, if I understand correctly to use LSP I would have to use one of the SDKs for the LSP because the Dart implementation's (LSP) support is available in the Dart analysis server itself and

the original protocol may not get all of the things that the LSP protocol is getting (for example the LSP server gets Colors and Inlay Hints that the original protocol does not).

Correct me if I am wrong. Meanwhile, can I join any Dart community working on LSP integration?

DanTup commented 1 year ago

So, if I understand correctly to use LSP I would have to use one of the SDKs for the LSP

You could use one of those, or you could implement the protocol yourself. There isn't an official LSP client in Dart, although many of the parts of one exist in some of the test classes because many of the LSP tests are written from the perspective of an LSP client.

It's also possible there are other projects in Dart/Flutter using the LSP server, so there might be more complete packages out there you could use (I think I've seen a few Flutter editors go by in Twitter, but I haven't looked in any detail).

5hirish commented 1 year ago

Perfect I'll look around to see any exsiting implementations, and get back with my findings. Thanks Dan.

5hirish commented 1 year ago

Hi Dan, this is what I found out so far -

So, I am at a roadblock and a bit confused, trying to decide if I should venture ahead and build it from scratch. I am struggling to understand how dart-SDK has an implementation for both LSP and DAS and when they use either. To me, it looks like DAS is widely being used while LSP is just being implemented.

DanTup commented 1 year ago

(I've responded here assuming your comments are on the topic of building an editor, rather than the original title here, if that's not the case then they might not be entirely appropriate :-) )

Other IDEs like Emacs lsp-dart and Sublime LSP-Dart and Vim dart-vim-plugin too have some LSP implementation but are relying on DAS after a point.

Where did you see DAS in those? It's not possible to use both DAS and LSP without running two servers so I'd be surprised if any of those LSP integrations are doing this (any DAS code would also be completely bespoke for Dart, whereas the LSP code is reusable for other languages). FWIW the term "DAS" is a bit ambiguous - it could just refer to the "Dart Anaylsis Server" (which the LSP support is built on top of), or the original (non-LSP) protocol.

Seems like the dart-sdk itself has a significant implementation of it dart-lang/sdk/lsp_spec. So, planning to go through it in the meantime.

Yep, the LSP spec (the meta model JSON) is used to code-gen all of the types used by the server. The types are intended specifically for the server though, so they might not work directly for you (but you may be able to generate your own types in a similar way).

I am struggling to understand how dart-SDK has an implementation for both LSP and DAS and when they use either.

Both the LSP server and the DAS protocol server are subclasses of a shared abstract AnalysisServer. They have their own initialisation code and code for handling the JSON conversions of requests/responses, but function very similarly (accept a request, call some other class (usually protocol-agnostic) to perform some processing, then return the result). Only one of these servers will be started for any dart language_server process.

To me, it looks like DAS is widely being used while LSP is just being implemented.

While LSP is newer, I'd be surprised if there were more clients using the original protocol than LSP. Many of the editors like emacs, sublime, vim have Dart support using LSP because LSP is a shared protocol (used by other languages) and they had existing clients (so enabling Dart support is not much effort). I wouldn't expect that many editors have done all of the bespoke work to integrate the original protocol (especially not those with LSP in their name).

Even if you can't reuse any code of an existing LSP client, I would expect that implementing an LSP client from scratch is probably very similar work to implementing a DAS protocol client from scratch. They both function very similarly (for example when the user hovers over a symbol, you build a JSON request, sent it over the stdin stream to the server, and then get back a JSON response from stdout with the information to show in the hover).

5hirish commented 1 year ago

Perfect thanks a bunch for clarifying. I suppose unless and until I try implementing a simple example, I won't fully grasp its inner workings. Once, I do I might be able to understand how far other implementations are and try to perhaps reuse some of those elements in my implementation. I'd be happy to share once I get to my next milestone if you are interested! Let me know. We can close this issue if it doesn't serve any purpose.

stevemessick commented 1 year ago

We can close this issue if it doesn't serve any purpose.

If you find an alternate point for collaboration then let me know and we'll close this one. If you want to keep using this, it's ok with me.

DanTup commented 1 year ago

@5hirish

I suppose unless and until I try implementing a simple example, I won't fully grasp its inner workings.

FWIW, in VS Code you can run the Dart: Capture Analyzer Logs command to capture the LSP traffic to the server (click Cancel on the notification to stop logging and open the log file).

Feel free to ping me (here or elsewhere) if you hit any issues or have any questions interacting with the Dart LSP server.