Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,11 @@ Table of Contents[![Backers on Open Collective](https://opencollective.com/intel
* [Time](#time)
* [Visibility](#visibility)
* [Call to Element](#call-to-element)
* [Experimental Features](#experimental-features)
* [~H Sigil HTML Injection](#h-sigil-html-injection-support)
* [How to enable ~H sigil HTML Injection](#how-to-enable-h-sigil-html-injection)
* [Providing feedback and reporting issues for the ~H Sigil HTML Injection Experimental Feature](#providing-feedback-and-reporting-issues-for-the-h-sigil-html-injection-experimental-feature))
* [Removing the green background for Injected language fragments](#removing-the-green-background-for-injected-language-fragments)
* [Installation](#installation)
* [Stable releases](#stable-releases)
* [Inside IDE using JetBrains repository](#inside-ide-using-jetbrains-repository)
Expand Down Expand Up @@ -5761,6 +5766,79 @@ The Visibility icons indicated whether the element is usable outside its definin
</tbody>
</table>

## Experimental Features

As we develop new functionality that requires additional testing and feedback, we offer an opt-in system for Experimental Features via the `Elixir Experimental Settings` page.

You can view the currently available Experimental Features by navigating to `Languages & Frameworks` and selecting `Elixir Experimental Settings`, which is marked with the [BETA icon](https://plugins.jetbrains.com/docs/intellij/settings-guide.html#l6vycg_378). Alternatively, you can access it directly via [Settings | Languages & Frameworks | Elixir Experimental Settings](jetbrains://Idea/settings?name=Languages+%26+Frameworks--Elixir+Experimental+Settings).

![Elixir Experimental Settings UI](/screenshots/experimental/elixir-experimental-settings-ui.png)

### ~H Sigil HTML Injection Support

**Experimental Feature – available from version 2024.3+ (243.21565.180) and later**

When working with Phoenix Live View templates within the IntelliJ Elixir plugin, you'll notice that sigils such as `~H` are rendered as strings, which means that out of the box there is no HTML syntax highlighting or autocomplete when working with Phoenix Live View, which is used for writing HEEx templates inside source files. `HEEx` is a HTML-aware and component-friendly extension of Elixir Embedded language, this can make editing templates tedious.

This Experimental Feature introduces preliminary HTML injection support within `~H` sigils, enabling HTML syntax highlighting and autocomplete.

**Before, you would see this rendered as a string:**

![~H shows as a string](/screenshots/experimental/h-sigil-html-before.png?raw=true "Shown as a string")

**After enabling ~H Sigil HTML Injection:**

![~H now shows with HTML injection](/screenshots/experimental/h-sigil-html.png?raw=true "HTML injection for autocomplete and syntax highlighting for the ~H sigil")

> [!TIP]
> The [Phoenix LiveView Documentation on sigil_H](https://hexdocs.pm/phoenix_live_view/1.0.3/Phoenix.Component.html#sigil_H/2) is a fantastic resource for understanding how the `~H` sigil works.

**Note:** Elixir code completion within HTML attributes is not yet supported.

However, it does open the door, thanks to [MultihostInjector](https://plugins.jetbrains.com/docs/intellij/language-injection.html#multihostinjector), we could possibly mix HTML+Elixir, allowing autocomplete of Elixir within HTML.. if anyone is daring enough to wrangle the MultihostInjector API!

#### IntelliLang Plugin Requirement

This functionality has a dependency on the [IntelliLang](https://plugins.jetbrains.com/plugin/13374-intellilang) plugin, which comes bundled with both IntelliJ Community/Ultimate, and other IDEs.

However this is marked as an optional dependency for the plugin, and does not need to be enabled if you are not using the functionality. THe code won't run, and you won't see injections.

More information about [Language Injections](https://www.jetbrains.com/help/idea/using-language-injections.html) is available in the IntelliJ IDEA documentation.

#### How to Enable ~H Sigil HTML Injection

To enable support for HTML syntax highlighting and autocomplete:

1. Open [Settings](https://www.jetbrains.com/help/idea/configure-project-settings.html).
2. Navigate to [Settings | Languages & Frameworks | Elixir Experimental Settings](jetbrains://Idea/settings?name=Languages+%26+Frameworks--Elixir+Experimental+Settings).
3. Enable the **~H Sigil HTML Injection** feature.

![Settings | Languages & Frameworks | Elixir Experimental Settings](/screenshots/experimental/elixir-experimental-settings-ui.png?raw=true "Elixir Experimental Settings UI")

> [!NOTE]
> This Experimental Feature is currently enabled on a **per-project** basis. We are considering adding application-level support or enabling it by default in future versions based on feedback

#### Providing feedback and reporting issues for the ~H Sigil HTML Injection Experimental Feature

Have feedback or encountered issues? Please share your thoughts, Exception Stacktraces on the dedicated [**\[Experimental Feature\] ~H Sigil HTML Injection #3678**](https://github.com/KronicDeth/intellij-elixir/issues/3678).

#### Removing the Green Background for Injected Language Fragments

By default, IntelliLang highlights injected content with a green background, which can be changed by within [Color Scheme settings](https://www.jetbrains.com/help/idea/settings-colors-and-fonts.html).

However, note that this change will apply to **all injected language fragments**, not just `~H` sigils HTML injections.

> We are investigating the use of [InjectionBackgroundSuppressor](https://github.com/JetBrains/intellij-community/blob/idea/243.21565.193/platform/analysis-impl/src/com/intellij/psi/impl/source/tree/injected/InjectionBackgroundSuppressor.java) to selectively disable background highlighting, but this is still a work in progress.

If you're okay with disabling the background for all injections:

1. Open [Settings](https://www.jetbrains.com/help/idea/configure-project-settings.html).
2. Navigate to [Settings | Editor | Color Scheme | General](jetbrains://Idea/settings?name=Editor--Color+Scheme).
3. Under the `Code` section, find `Injected Language Fragment`.
4. Uncheck **Background** or change the colour to your preference.

![How to remove the green background for Injected languge support](/screenshots/experimental/disable-injection-green-background.png?raw=true "Color Settings")

## Installation

### Stable releases
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import org.jetbrains.intellij.platform.gradle.TestFrameworkType
import org.jetbrains.intellij.platform.gradle.tasks.RunIdeTask

plugins {
id "org.jetbrains.intellij.platform" version "2.1.0"
id "org.jetbrains.intellij.platform" version "2.6.0"
id "org.jetbrains.kotlin.jvm" version "1.9.25"
id "de.undercouch.download" version "4.1.2"
id 'com.adarshr.test-logger' version '4.0.0'
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ platformVersion=2024.3
platformPlugins = PsiViewer:243.7768, com.google.ide-perf:1.3.2, org.jetbrains.action-tracker:0.3.3, com.intellij.classic.ui:243.21565.122,krasa.CpuUsageIndicator:1.18.0-IJ2023
# Example: platformBundledPlugins = com.intellij.java
# We need com.intellij.java to compile JPS, and markdown.
platformBundledPlugins=org.intellij.plugins.markdown,com.intellij.java
platformBundledPlugins=org.intellij.plugins.markdown,com.intellij.java,org.intellij.intelliLang
# Gradle Releases -> https://github.com/gradle/gradle/releases
# 8.5 is set because newer versions have weird run time caching issues, even with caching turned off.
# See https://github.com/gradle/gradle/issues/28974
Expand Down
14 changes: 14 additions & 0 deletions resources/META-INF/optional/org.elixir_lang-withIntellijLang.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<idea-plugin>
<extensions defaultExtensionNs="org.intellij.intelliLang">
<languageSupport implementation="org.elixir_lang.injection.ElixirSigilInjectionSupport"/>
<injectionConfig config="injection/elixirInjections.xml"/>
</extensions>

<extensions defaultExtensionNs="com.intellij">
<multiHostInjector implementation="org.elixir_lang.injection.markdown.Injector"/>
<multiHostInjector
id="org.elixir_lang.injection.ElixirSigilInjector"
implementation="org.elixir_lang.injection.ElixirSigilInjector"/>
</extensions>

</idea-plugin>
5 changes: 4 additions & 1 deletion resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
on how to target different products -->
<depends>com.intellij.modules.lang</depends>
<depends>org.intellij.plugins.markdown</depends>

<!-- optional modules -->
<!-- See https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html#optional-plugin-dependencies for naming convention -->
<depends config-file="rich-platform-plugin.xml" optional="true">com.intellij.modules.java</depends>
<depends config-file="optional/org.elixir_lang-withIntellijLang.xml" optional="true">org.intellij.intelliLang</depends>

<extensionPoints>
<extensionPoint qualifiedName="org.elixir_lang.packageManager" interface="org.elixir_lang.PackageManager"
Expand Down Expand Up @@ -269,7 +273,6 @@

<lang.documentationProvider implementationClass="org.elixir_lang.documentation.ElixirDocumentationProvider"
language="Elixir"/>
<multiHostInjector implementation="org.elixir_lang.injection.markdown.Injector"/>

<!-- Dialyzer -->
<globalInspection displayName="Dialyzer based inspections (Elixir)" shortName="Dialyzer"
Expand Down
7 changes: 3 additions & 4 deletions resources/colorSchemes/ElixirDarcula.xml
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,6 @@
</option>
<option name="ELIXIR_SIGIL_UPPER_H">
<value>
<option name="FOREGROUND" value="5BB347" />
</value>
</option>
<option name="ELIXIR_SIGIL_LOWER_I">
Expand Down Expand Up @@ -278,9 +277,9 @@
</value>
</option>
<option name="ELIXIR_SIGIL_LOWER_R">
<value>
<option name="FOREGROUND" value="4749B3" />
</value>
<!-- <value>-->
<!-- <option name="FOREGROUND" value="4749B3" />-->
<!-- </value>-->
</option>
<option name="ELIXIR_SIGIL_UPPER_R">
<value>
Expand Down
7 changes: 3 additions & 4 deletions resources/colorSchemes/ElixirDefault.xml
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,6 @@
</option>
<option name="ELIXIR_SIGIL_UPPER_H">
<value>
<option name="FOREGROUND" value="99186C" />
</value>
</option>
<option name="ELIXIR_SIGIL_LOWER_I">
Expand Down Expand Up @@ -278,9 +277,9 @@
</value>
</option>
<option name="ELIXIR_SIGIL_LOWER_R">
<value>
<option name="FOREGROUND" value="569918" />
</value>
<!-- <value>-->
<!-- <option name="FOREGROUND" value="569918" />-->
<!-- </value>-->
</option>
<option name="ELIXIR_SIGIL_UPPER_R">
<value>
Expand Down
17 changes: 17 additions & 0 deletions resources/injection/elixirInjections.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<component name="LanguageInjectionConfiguration">
<injection language="RegExp" injector-id="elixir">
<display-name>Sigil: Regular Expression</display-name>
<place><![CDATA[sigilWithName("r")]]></place>
</injection>

<!-- <injection language="HTML" injector-id="elixir">-->
<!-- <display-name>Sigil: (Phoenix) HTML</display-name>-->
<!-- <place><![CDATA[sigilWithName("H")]]></place>-->
<!-- </injection>-->

<injection language="EEx" injector-id="elixir">
<display-name>Sigil: (Phoenix) EEX</display-name>
<place><![CDATA[sigilWithName("L")]]></place>
</injection>
</component>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/experimental/h-sigil-html.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions src/org/elixir_lang/injection/ElixirSigilInjectionSupport.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.elixir_lang.injection

import com.intellij.psi.PsiLanguageInjectionHost
import org.intellij.plugins.intelliLang.inject.AbstractLanguageInjectionSupport
import org.jetbrains.annotations.NonNls

class ElixirSigilInjectionSupport : AbstractLanguageInjectionSupport() {
override fun getId(): String {
return ELIXIR_SUPPORT_ID
}

override fun getPatternClasses(): Array<Class<*>> {
return arrayOf(ElixirSigilPatterns::class.java)
}

override fun isApplicableTo(host: PsiLanguageInjectionHost): Boolean {
return true
}

override fun useDefaultInjector(host: PsiLanguageInjectionHost): Boolean {
return true
}

companion object {
const val ELIXIR_SUPPORT_ID: @NonNls String = "elixir"
}
}
58 changes: 58 additions & 0 deletions src/org/elixir_lang/injection/ElixirSigilInjector.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.elixir_lang.injection

import com.intellij.lang.Language
import com.intellij.lang.html.HTMLLanguage
import com.intellij.lang.injection.MultiHostInjector
import com.intellij.lang.injection.MultiHostRegistrar
import com.intellij.psi.PsiElement
import org.elixir_lang.psi.SigilHeredoc
import org.elixir_lang.psi.SigilLine
import org.intellij.lang.regexp.RegExpLanguage
import org.elixir_lang.eex.Language as EexLanguage

class ElixirSigilInjector : MultiHostInjector {
override fun getLanguagesToInject(registrar: MultiHostRegistrar, context: PsiElement) {
when (context) {
is SigilLine -> handleSigilLine(registrar, context)
is SigilHeredoc -> handleSigilHeredoc(registrar, context)
else -> return
}
}

private fun handleSigilLine(registrar: MultiHostRegistrar, sigilLine: SigilLine) {
if (!sigilLine.isValidHost) return
val lang = languageForSigil(sigilLine.sigilName()) ?: return

sigilLine.body?.let { lineBody ->
registrar
.startInjecting(lang)
.addPlace(null, null, sigilLine, lineBody.textRangeInParent)
.doneInjecting()
}
}

private fun handleSigilHeredoc(registrar: MultiHostRegistrar, sigilHeredoc: SigilHeredoc) {
if (!sigilHeredoc.isValidHost || sigilHeredoc.heredocLineList.isEmpty() || !sigilHeredoc.isValid) return

val lang = languageForSigil(sigilHeredoc.sigilName()) ?: return
registrar.startInjecting(lang)
for (item in sigilHeredoc.heredocLineList) {
if (item.isValid) {
registrar.addPlace(null, null, sigilHeredoc, item.textRangeInParent)
}
}

registrar.doneInjecting()
}

override fun elementsToInjectIn() = listOf(SigilHeredoc::class.java, SigilLine::class.java)

private fun languageForSigil(sigilName: Char): Language? {
return when (sigilName) {
'H' -> HTMLLanguage.INSTANCE
'L' -> EexLanguage.INSTANCE
'r' -> RegExpLanguage.INSTANCE
else -> null
}
}
}
38 changes: 38 additions & 0 deletions src/org/elixir_lang/injection/ElixirSigilPatterns.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.elixir_lang.injection;

import com.intellij.patterns.ElementPattern;
import com.intellij.patterns.PatternCondition;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiElement;
import com.intellij.util.ProcessingContext;
import org.elixir_lang.psi.Sigil;
import org.jetbrains.annotations.NotNull;

public class ElixirSigilPatterns extends PlatformPatterns {
public static ElementPattern<?> sigil() {
return psiElement().inside(psiElement(Sigil.class));
}

@SuppressWarnings("unused")
public static ElementPattern<?> sigilWithName(String name) {
return and(sigil(), psiElement().with(new ElixirSigilPatterns.SigilWithName(name)));
}

public static class SigilWithName extends @NotNull PatternCondition<PsiElement> {
Character expectedSigil;

public SigilWithName(String name) {
super(name);
expectedSigil = name.charAt(0);
}

@Override
public boolean accepts(@NotNull PsiElement psiElement, ProcessingContext processingContext) {
if (psiElement instanceof Sigil) {
return ((Sigil) psiElement).sigilName() == expectedSigil;
}

return false;
}
}
}
10 changes: 8 additions & 2 deletions src/org/elixir_lang/injection/PsiLanguageInjectionHost.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ import org.elixir_lang.injection.markdown.Injector
import org.elixir_lang.psi.AtUnqualifiedNoParenthesesCall
import org.elixir_lang.psi.ElixirNoParenthesesKeywords
import org.elixir_lang.psi.Parent
import org.elixir_lang.psi.Sigil

object PsiLanguageInjectionHost {
@JvmStatic
fun isValidHost(psiElement: PsiElement): Boolean =
when (val greatGrandParent = psiElement.parent?.parent?.parent) {
fun isValidHost(psiElement: PsiElement): Boolean {
if (psiElement as? Sigil != null) {
return true
}

return when (val greatGrandParent = psiElement.parent?.parent?.parent) {
is AtUnqualifiedNoParenthesesCall<*> -> Injector.isValidHost(greatGrandParent)
is ElixirNoParenthesesKeywords -> {
greatGrandParent
Expand All @@ -22,6 +27,7 @@ object PsiLanguageInjectionHost {
}
else -> false
}
}

@JvmStatic
fun createLiteralTextEscaper(parent: Parent): LiteralTextEscaper<Parent> =
Expand Down
Loading