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

Sigils - IntelliLang Language Injection Support PoC #3671

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

polymorfiq
Copy link

@polymorfiq polymorfiq commented Dec 28, 2024

First I wanted to say, great plugin! Thanks so much for maintaining it!

I do not know Java, Kotlin, IntelliJ Plugin development or really any of the associated tooling - so this is definitely just an example to potentially help development of features related to #695 and #2102

I have been recently using Phoenix LiveView extensively, and the lack of Language Injection and Syntax Highlighting for the ~H and ~L sigils makes that a little difficult

This PR seems to get Language Injection working for the two sigils, though I have not figured out how to get the HTML syntax highlighting working alongside it. Since IntelliJ knows it's HTML, I'm not entirely sure why it doesn't respect the syntax highlighting of that language 🤔

Screenshot 2024-12-27 at 11 16 11 PM

Notes:

Right now I have ~L pointed at EEx, though from my understanding it should be Heex or Leex

Ideally, ElixirSigilInjector is entirely un-needed or otherwise wouldn't specify languages (languageForSigil) - but without its existence, Intellij seemed to be using the markdown.Injector and thus ElixirSigilInjectionSupport was only being passed Heredoc elements. I could not get IntelliJ to ignore the org.elixir_lang.injection.markdown.Injector, so instead ElixirSigilInjectionSupport is currently overriding it as the default, for everything besides Heredocs.

The reason ElixirSigilInjector specifying languages should be unneeded, is that using the added sigilWithName pattern and elixirInjections.xml file, allows the user to override/attach arbitrary languages to arbitrary sigils, which is likely better than hardcoding which sigils should go to what language (like the ElixirSigilInjector seems to require):
Screenshot 2024-12-27 at 11 44 35 PM

Hope this helps! 🎉

@joshuataylor
Copy link
Collaborator

This is absolutely amazing, thank you for just diving in and getting dirty -- hope it wasn't too full of spiders!

Let me know if you need anything in future as well, I'd be happy to chat about any of this, especially as IntelliJ has been changing a lot of their Custom Language Support in the last year or so.

I'll review this ASAP. <3

@elepedus
Copy link

Can't wait to get this feature! IntelliJ has such great HTML support (hello, Emmet!) and losing all of that when working with sigils is really painful!

Please let me know if there's anything I can do to help get this over the line!

@joshuataylor
Copy link
Collaborator

joshuataylor commented Jan 11, 2025 via email

@joshuataylor
Copy link
Collaborator

Having a look at this, there seems to be an error with threading, looking into this.

Happens when I open a file (also love the sass, Jetbrains):

java.lang.IllegalStateException: Wow, you must not start injecting in one thread (null) and finish in the other (Thread[#149,ForkJoinPool.commonPool-worker-3,5,main])
  at com.intellij.psi.impl.source.tree.injected.InjectionRegistrarImpl.checkThreading(InjectionRegistrarImpl.java:156)
  at com.intellij.psi.impl.source.tree.injected.InjectionRegistrarImpl.addPlace(InjectionRegistrarImpl.java:126)
  at org.elixir_lang.injection.ElixirSigilInjector.getLanguagesToInject(ElixirSigilInjector.kt:110)
  at org.elixir_lang.injection.ElixirSigilInjector.getLanguagesToInject(ElixirSigilInjector.kt:132)
  at org.elixir_lang.injection.ElixirSigilInjector.getLanguagesToInject(ElixirSigilInjector.kt:132)
  at org.elixir_lang.injection.ElixirSigilInjector.getLanguagesToInject(ElixirSigilInjector.kt:132)
  at org.elixir_lang.injection.ElixirSigilInjector.getLanguagesToInject(ElixirSigilInjector.kt:132)
  at org.elixir_lang.injection.ElixirSigilInjector.getLanguagesToInject(ElixirSigilInjector.kt:132)
  at org.elixir_lang.injection.ElixirSigilInjector.getLanguagesToInject(ElixirSigilInjector.kt:132)
  at org.elixir_lang.injection.ElixirSigilInjector.getLanguagesToInject(ElixirSigilInjector.kt:132)
  at com.intellij.psi.impl.source.tree.injected.InjectedLanguageManagerImpl.processInPlaceInjectorsFor(InjectedLanguageManagerImpl.java:498)
  at com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtilBase.probeElementsUpInner(InjectedLanguageUtilBase.java:237)
  at com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtilBase.lambda$probeElementsUp$0(InjectedLanguageUtilBase.java:217)
  at com.intellij.openapi.application.impl.ReadActionCacheImpl$allowInWriteAction$1.invoke(ReadActionCacheImpl.kt:41)
  at com.intellij.openapi.application.impl.ReadActionCacheImpl$allowInWriteAction$1.invoke(ReadActionCacheImpl.kt:41)
  at com.intellij.openapi.application.impl.ReadActionCacheImpl.allowInWriteAction(ReadActionCacheImpl.kt:29)
  at com.intellij.openapi.application.impl.ReadActionCacheImpl.allowInWriteAction(ReadActionCacheImpl.kt:41)
  at com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtilBase.probeElementsUp(InjectedLanguageUtilBase.java:216)
  at com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtilBase.enumerate(InjectedLanguageUtilBase.java:159)
  at com.intellij.psi.impl.source.tree.injected.InjectedLanguageManagerImpl.enumerateEx(InjectedLanguageManagerImpl.java:383)
  at com.intellij.codeInsight.daemon.impl.InjectedGeneralHighlightingPass.lambda$processInjectedPsiFiles$7(InjectedGeneralHighlightingPass.java:153)
  at com.intellij.concurrency.ApplierCompleter.processArrayItem(ApplierCompleter.java:126)
  at com.intellij.concurrency.ApplierCompleter.processArray(ApplierCompleter.java:207)
  at com.intellij.concurrency.ApplierCompleter.execAll(ApplierCompleter.java:176)
  at com.intellij.concurrency.JobLauncherImpl.lambda$invokeConcurrentlyUnderProgressAsync$3(JobLauncherImpl.java:108)
  at com.intellij.openapi.application.impl.AnyThreadWriteThreadingSupport.tryRunReadAction(AnyThreadWriteThreadingSupport.kt:333)
  at com.intellij.openapi.application.impl.ApplicationImpl.tryRunReadAction(ApplicationImpl.java:971)
  at com.intellij.concurrency.ApplierCompleter.lambda$wrapInReadActionAndIndicator$2(ApplierCompleter.java:158)
  at com.intellij.openapi.progress.impl.CoreProgressManager.lambda$executeProcessUnderProgress$14(CoreProgressManager.java:674)
  at com.intellij.openapi.progress.impl.CoreProgressManager.registerIndicatorAndRun(CoreProgressManager.java:749)
  at com.intellij.openapi.progress.impl.CoreProgressManager.computeUnderProgress(CoreProgressManager.java:705)
  at com.intellij.openapi.progress.impl.CoreProgressManager.executeProcessUnderProgress(CoreProgressManager.java:673)
  at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:79)
  at com.intellij.concurrency.ApplierCompleter.wrapInReadActionAndIndicator(ApplierCompleter.java:169)
  at com.intellij.concurrency.ApplierCompleter.lambda$wrapAndRun$1(ApplierCompleter.java:150)
  at com.intellij.openapi.application.impl.AnyThreadWriteThreadingSupport.executeByImpatientReader(AnyThreadWriteThreadingSupport.kt:544)
  at com.intellij.openapi.application.impl.ApplicationImpl.executeByImpatientReader(ApplicationImpl.java:176)
  at com.intellij.concurrency.ApplierCompleter.wrapAndRun(ApplierCompleter.java:150)
  at com.intellij.concurrency.JobLauncherImpl.lambda$invokeConcurrentlyUnderProgressAsync$4(JobLauncherImpl.java:108)
  at com.intellij.concurrency.JobLauncherImpl.safeIterate(JobLauncherImpl.java:176)
  at com.intellij.concurrency.JobLauncherImpl.invokeConcurrentlyUnderProgressAsync(JobLauncherImpl.java:105)
  at com.intellij.concurrency.JobLauncherImpl.invokeConcurrentlyUnderProgress(JobLauncherImpl.java:53)
  at com.intellij.concurrency.JobLauncher.invokeConcurrentlyUnderProgress(JobLauncher.java:51)
  at com.intellij.codeInsight.daemon.impl.InjectedGeneralHighlightingPass.processInjectedPsiFiles(InjectedGeneralHighlightingPass.java:151)
  at com.intellij.codeInsight.daemon.impl.InjectedGeneralHighlightingPass.lambda$collectInformationWithProgress$4(InjectedGeneralHighlightingPass.java:84)
  at com.intellij.codeInsight.daemon.impl.ManagedHighlighterRecycler.runWithRecycler(ManagedHighlighterRecycler.java:85)
  at com.intellij.codeInsight.daemon.impl.InjectedGeneralHighlightingPass.collectInformationWithProgress(InjectedGeneralHighlightingPass.java:83)
  at com.intellij.codeInsight.daemon.impl.ProgressableTextEditorHighlightingPass.doCollectInformation(ProgressableTextEditorHighlightingPass.java:86)
  at com.intellij.codeHighlighting.TextEditorHighlightingPass.collectInformation(TextEditorHighlightingPass.java:67)
  at com.intellij.codeInsight.daemon.impl.PassExecutorService$ScheduledPass.lambda$doRun$2(PassExecutorService.java:427)
  at com.intellij.platform.diagnostic.telemetry.helpers.TraceKt.use(trace.kt:30)
  at com.intellij.codeInsight.daemon.impl.PassExecutorService$ScheduledPass.lambda$doRun$3(PassExecutorService.java:423)
  at com.intellij.openapi.application.impl.AnyThreadWriteThreadingSupport.tryRunReadAction(AnyThreadWriteThreadingSupport.kt:351)
  at com.intellij.openapi.application.impl.ApplicationImpl.tryRunReadAction(ApplicationImpl.java:971)
  at com.intellij.codeInsight.daemon.impl.PassExecutorService$ScheduledPass.lambda$doRun$4(PassExecutorService.java:413)
  at com.intellij.openapi.progress.impl.CoreProgressManager.lambda$executeProcessUnderProgress$14(CoreProgressManager.java:674)
  at com.intellij.openapi.progress.impl.CoreProgressManager.registerIndicatorAndRun(CoreProgressManager.java:749)
  at com.intellij.openapi.progress.impl.CoreProgressManager.computeUnderProgress(CoreProgressManager.java:705)
  at com.intellij.openapi.progress.impl.CoreProgressManager.executeProcessUnderProgress(CoreProgressManager.java:673)
  at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:79)
  at com.intellij.codeInsight.daemon.impl.PassExecutorService$ScheduledPass.doRun(PassExecutorService.java:412)
  at com.intellij.codeInsight.daemon.impl.PassExecutorService$ScheduledPass.lambda$run$0(PassExecutorService.java:388)
  at com.intellij.openapi.fileTypes.impl.FileTypeManagerImpl.cacheFileTypesInside(FileTypeManagerImpl.java:822)
  at com.intellij.codeInsight.daemon.impl.PassExecutorService$ScheduledPass.lambda$run$1(PassExecutorService.java:388)
  at com.intellij.openapi.application.impl.AnyThreadWriteThreadingSupport.executeByImpatientReader(AnyThreadWriteThreadingSupport.kt:544)
  at com.intellij.openapi.application.impl.ApplicationImpl.executeByImpatientReader(ApplicationImpl.java:176)
  at com.intellij.codeInsight.daemon.impl.PassExecutorService$ScheduledPass.run(PassExecutorService.java:386)
  at com.intellij.concurrency.JobLauncherImpl$VoidForkJoinTask$1.exec(JobLauncherImpl.java:266)
  at java.base/java.util.concurrent.ForkJoinTask.doExec$$$capture(ForkJoinTask.java:507)
  at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java)
  at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1491)
  at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:2073)
  at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2035)
  at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:187)

@joshuataylor
Copy link
Collaborator

joshuataylor commented Jan 11, 2025

Okay, so got the threading stuff sorted, though I can't figure out why it won't highlight!

It won't work inside of strings, it's really weird.

https://plugins.jetbrains.com/docs/intellij/language-injection.html#formatting

https://github.com/joshuataylor/intellij-elixir/tree/sigil-injected-languages-poke

edit:

Okay, so by default ELIXIR_SIGIL_UPPER_H overwrites the colour for injections.

/resources/colorSchemes/ElixirDefault.xml#L185-L189

    <option name="ELIXIR_SIGIL_UPPER_H">
        <value>
            <option name="FOREGROUND" value="99186C" />
        </value>
    </option>

Settings | Editor | Color Scheme | Elixir

image

Textual -> Sigil -> H

Disable "Foreground"

Injected language fragment can have its green colour removed as well:

image

The range needs to be fixed, but it's close.

@elepedus
Copy link

This is already looking so much better! Thank you! 🤩

@joshuataylor
Copy link
Collaborator

Managed to get it to work, what a pain! The documentation is really confusing and inconsistent.

For ~H, I set it to only match on the lines inside, as if you don't it'll match for example in the quotes in ~H """

SCR-20250114-bcae

HTML autocomplete works:

SCR-20250114-bipa SCR-20250114-bips

This lays the foundations for integrated autocomplete, using a mix of HTML+Elixir, e.g we could autocomplete:

def weather_greeting(assigns) do
  ~H"""
  <div title="My div" class={@class}>
    <p>Hello {@name}</p>
    <MyApp.Weather.city name="Kraków"/>
  </div>
  """
end

If you don't want the green background, you can turn that off, though note it turns it off for everything else as well that's injected.

SCR-20250114-bjcd-2 SCR-20250114-bjdu

Regex works as well:

image

--

I've also turned off the color schemes for ~H and ~r, they were blocking inspection. I believe that you can turn that off using InjectionBackgroundSuppressor, but I couldn't get it to work. 🤷

--

If you want to try my build and let me know, that'd be great - I've tested with the latest IntelliJ 2024.3.1.1 and it's been working well. I know RubyMine is really buggy with SDKs, hoping to get that fixed soon.

Elixir-20.0.1-pre+20250113164838.zip

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

Successfully merging this pull request may close these issues.

3 participants