├── settings.gradle.kts ├── .gitignore ├── media ├── example.png └── mini_example.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── qodana.yaml ├── src ├── main │ ├── java │ │ └── com │ │ │ └── ioannuwu │ │ │ └── inline │ │ │ ├── data │ │ │ ├── TextStyle.java │ │ │ ├── EffectType.java │ │ │ ├── MyColorConverter.java │ │ │ ├── FontSettingsState.java │ │ │ ├── DefaultSettings.java │ │ │ └── SeverityLevelState.java │ │ │ └── ui │ │ │ └── settingscomponent │ │ │ ├── Component.java │ │ │ └── components │ │ │ ├── OneGutterModeComponent.java │ │ │ ├── NumberOfWhitespacesComponent.java │ │ │ ├── MaxErrorsPerLineComponent.java │ │ │ ├── TextStyleSelectionComponent.java │ │ │ ├── EffectTypeComponent.java │ │ │ ├── IgnoreListComponent.java │ │ │ ├── fontselectioncomponent │ │ │ ├── FontListComboBox.java │ │ │ └── FontSelectionComponent.java │ │ │ └── SeverityLevel.java │ ├── kotlin │ │ └── com │ │ │ └── ioannuwu │ │ │ └── inline2 │ │ │ ├── settings │ │ │ ├── data │ │ │ │ ├── MutableState.kt │ │ │ │ ├── SettingsService.kt │ │ │ │ └── SettingsState.kt │ │ │ ├── event │ │ │ │ ├── SettingsChangePriority.kt │ │ │ │ ├── SettingsChangeListener.kt │ │ │ │ └── SettingsChangeDispatcher.kt │ │ │ └── ui │ │ │ │ ├── SettingsUIView.kt │ │ │ │ ├── InLineSettingsConfigurable.kt │ │ │ │ └── SettingsUIViewImpl.kt │ │ │ └── pluginlogic │ │ │ ├── render │ │ │ ├── metrics │ │ │ │ ├── LineMetrics.kt │ │ │ │ ├── otherdata │ │ │ │ │ ├── MaxErrorsPerLine.kt │ │ │ │ │ ├── OneGutterMode.kt │ │ │ │ │ ├── NumberOfWhitespaces.kt │ │ │ │ │ ├── TextStyleSelector.kt │ │ │ │ │ └── EffectTypeSelector.kt │ │ │ │ ├── renderdata │ │ │ │ │ ├── FontSupplier.kt │ │ │ │ │ ├── GutterIcon.kt │ │ │ │ │ └── TextMetrics.kt │ │ │ │ ├── OtherData.kt │ │ │ │ ├── FontMetrics.kt │ │ │ │ └── RenderData.kt │ │ │ ├── OtherDataSelector.kt │ │ │ ├── RenderDataSelector.kt │ │ │ ├── ElementMetrics.kt │ │ │ ├── EditorElementMetrics.kt │ │ │ ├── RenderDataElementMetrics.kt │ │ │ └── BySettingsDataSelector.kt │ │ │ ├── editormodel │ │ │ ├── BackgroundAttributes.kt │ │ │ └── EditorModelImpl.kt │ │ │ ├── utils │ │ │ ├── extensions.kt │ │ │ └── functions.kt │ │ │ ├── action │ │ │ ├── ChangeErrorTypeAction.kt │ │ │ └── ChangeErrorVisibilityAction.kt │ │ │ ├── EditorModel.kt │ │ │ ├── FileOpenedListener.kt │ │ │ ├── element │ │ │ ├── DefaultTextRenderer.kt │ │ │ └── UnderLineTextRenderer.kt │ │ │ └── ErrorMarkupModelListener.kt │ └── resources │ │ └── META-INF │ │ ├── plugin.xml │ │ └── pluginIcon.svg └── test │ ├── kotlin │ └── com │ │ └── ioannuwu │ │ └── inline │ │ └── utils │ │ ├── TestUtils.kt │ │ ├── simpletests │ │ ├── PartitionTest.kt │ │ ├── TestingUtilsTest.kt │ │ └── SortingTest.kt │ │ └── TestDocument.kt │ └── java │ └── com │ └── ioannuwu │ └── inline │ └── data │ └── DefaultSettingsTest.java ├── .github ├── dependabot.yml └── workflows │ └── buildAndTest.yml ├── .run ├── Run IDE for UI Tests.run.xml ├── Run IDE with Plugin.run.xml ├── Run Plugin Tests.run.xml ├── Run Qodana.run.xml └── Run Plugin Verification.run.xml ├── LICENSE.md ├── gradle.properties ├── gradlew.bat ├── README.md └── gradlew /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "InLine" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | .idea/* 4 | .qodana 5 | build 6 | -------------------------------------------------------------------------------- /media/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoaNNUwU/InLine/HEAD/media/example.png -------------------------------------------------------------------------------- /media/mini_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoaNNUwU/InLine/HEAD/media/mini_example.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoaNNUwU/InLine/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /qodana.yaml: -------------------------------------------------------------------------------- 1 | version: 1.0 2 | profile: 3 | name: qodana.recommended 4 | exclude: 5 | - name: All 6 | paths: 7 | - .qodana 8 | -------------------------------------------------------------------------------- /src/main/java/com/ioannuwu/inline/data/TextStyle.java: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.data; 2 | 3 | public enum TextStyle { 4 | AFTER_LINE, UNDER_LINE 5 | } 6 | -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/settings/data/MutableState.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.settings.data 2 | 3 | interface MutableState { 4 | 5 | val state: T 6 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/settings/event/SettingsChangePriority.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.settings.event 2 | 3 | enum class SettingsChangePriority { 4 | FIRST, DEFAULT, LAST 5 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/render/metrics/LineMetrics.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.render.metrics 2 | 3 | interface LineMetrics { 4 | 5 | fun lineHeight(): Int 6 | 7 | fun ascent(): Int 8 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/render/metrics/otherdata/MaxErrorsPerLine.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.render.metrics.otherdata 2 | 3 | interface MaxErrorsPerLine { 4 | 5 | fun maxErrorsPerLine(): Int 6 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/render/metrics/otherdata/OneGutterMode.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.render.metrics.otherdata 2 | 3 | interface OneGutterMode { 4 | 5 | fun showOnlyOneGutter(): Boolean 6 | } -------------------------------------------------------------------------------- /src/main/java/com/ioannuwu/inline/data/EffectType.java: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.data; 2 | 3 | import java.io.Serializable; 4 | 5 | public enum EffectType implements Serializable { 6 | NONE, 7 | BOX, 8 | SHADOW, 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/render/metrics/otherdata/NumberOfWhitespaces.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.render.metrics.otherdata 2 | 3 | interface NumberOfWhitespaces { 4 | 5 | fun numberOfWhitespaces(): Int 6 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/render/metrics/renderdata/FontSupplier.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.render.metrics.renderdata 2 | 3 | import java.awt.Font 4 | 5 | interface FontSupplier { 6 | 7 | fun font(): Font 8 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/render/metrics/renderdata/GutterIcon.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.render.metrics.renderdata 2 | 3 | import javax.swing.Icon 4 | 5 | interface GutterIcon { 6 | 7 | fun gutterIcon(): Icon? 8 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/render/metrics/otherdata/TextStyleSelector.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.render.metrics.otherdata 2 | 3 | import com.ioannuwu.inline.data.TextStyle 4 | 5 | interface TextStyleSelector { 6 | 7 | fun textStyle(): TextStyle 8 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/render/metrics/otherdata/EffectTypeSelector.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.render.metrics.otherdata 2 | 3 | import com.ioannuwu.inline.data.EffectType 4 | 5 | interface EffectTypeSelector { 6 | 7 | fun effectType(): EffectType 8 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/ioannuwu/inline/utils/TestUtils.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.utils 2 | 3 | fun TEST(): Nothing { 4 | throw Error("Should not be used in the test") 5 | } 6 | 7 | fun TEST(description: String): Nothing { 8 | throw Error("Should not be reachable in tests: $description") 9 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/render/metrics/OtherData.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.render.metrics 2 | 3 | import com.ioannuwu.inline2.pluginlogic.render.metrics.otherdata.* 4 | 5 | interface OtherData : EffectTypeSelector, MaxErrorsPerLine, NumberOfWhitespaces, 6 | OneGutterMode, TextStyleSelector -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/settings/event/SettingsChangeListener.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.settings.event 2 | 3 | import com.ioannuwu.inline2.settings.data.SettingsState 4 | import java.util.EventListener 5 | 6 | interface SettingsChangeListener : EventListener { 7 | 8 | fun onSettingsChange(newSettingsState: SettingsState) 9 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/render/metrics/FontMetrics.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.render.metrics 2 | 3 | import com.ioannuwu.inline2.pluginlogic.render.metrics.renderdata.FontSupplier 4 | 5 | interface FontMetrics : FontSupplier { 6 | 7 | fun charWidth(): Int 8 | 9 | fun stringWidth(string: String): Int 10 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/render/OtherDataSelector.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.render 2 | 3 | import com.intellij.openapi.editor.markup.RangeHighlighter 4 | import com.ioannuwu.inline2.pluginlogic.render.metrics.OtherData 5 | 6 | interface OtherDataSelector { 7 | 8 | fun selectOtherData(highlighter: RangeHighlighter): OtherData 9 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/render/metrics/renderdata/TextMetrics.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.render.metrics.renderdata 2 | 3 | import java.awt.Color 4 | 5 | interface TextMetrics { 6 | 7 | fun textColor(): Color? 8 | 9 | fun backgroundColor(): Color? 10 | 11 | fun effectColor(): Color? 12 | 13 | fun text(): String 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/settings/ui/SettingsUIView.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.settings.ui 2 | 3 | import com.ioannuwu.inline2.settings.data.MutableState 4 | import com.ioannuwu.inline2.settings.data.SettingsState 5 | import javax.swing.JComponent 6 | 7 | interface SettingsUIView : MutableState { 8 | 9 | fun createComponent(): JComponent 10 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/editormodel/BackgroundAttributes.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.editormodel 2 | 3 | import com.intellij.openapi.editor.markup.TextAttributes 4 | import java.awt.Color 5 | 6 | class BackgroundAttributes(backgroundColor: Color) : TextAttributes( 7 | null, 8 | backgroundColor, 9 | null, 10 | null, 11 | 0 12 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/render/metrics/RenderData.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.render.metrics 2 | 3 | import com.ioannuwu.inline2.pluginlogic.render.metrics.renderdata.FontSupplier 4 | import com.ioannuwu.inline2.pluginlogic.render.metrics.renderdata.GutterIcon 5 | import com.ioannuwu.inline2.pluginlogic.render.metrics.renderdata.TextMetrics 6 | 7 | interface RenderData : GutterIcon, FontSupplier, TextMetrics -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/render/RenderDataSelector.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.render 2 | 3 | import com.intellij.openapi.editor.markup.RangeHighlighter 4 | import com.ioannuwu.inline2.pluginlogic.render.metrics.RenderData 5 | 6 | @FunctionalInterface 7 | interface RenderDataSelector { 8 | 9 | operator fun invoke(highlighter: RangeHighlighter): RenderData? 10 | 11 | fun isValid(highlighter: RangeHighlighter): Boolean 12 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/utils/extensions.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.utils 2 | 3 | import com.intellij.openapi.Disposable 4 | 5 | /** 6 | * Wraps any object in Disposable using provided dispose method 7 | * 8 | * Used for types that can be disposed but don't implement Disposable 9 | */ 10 | inline fun T.makeDisposable(crossinline disposeMethod: (T) -> Unit): Disposable = 11 | Disposable { 12 | disposeMethod(this) 13 | } -------------------------------------------------------------------------------- /src/main/java/com/ioannuwu/inline/ui/settingscomponent/Component.java: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.ui.settingscomponent; 2 | 3 | import com.intellij.util.ui.FormBuilder; 4 | 5 | public interface Component { 6 | 7 | void addToBuilder(FormBuilder formBuilder); 8 | } 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Dependabot configuration: 2 | # https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | # Maintain dependencies for Gradle dependencies 7 | - package-ecosystem: "gradle" 8 | directory: "/" 9 | target-branch: "next" 10 | schedule: 11 | interval: "daily" 12 | # Maintain dependencies for GitHub Actions 13 | - package-ecosystem: "github-actions" 14 | directory: "/" 15 | target-branch: "next" 16 | schedule: 17 | interval: "daily" 18 | -------------------------------------------------------------------------------- /src/main/java/com/ioannuwu/inline/data/MyColorConverter.java: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.data; 2 | 3 | import com.intellij.util.xmlb.Converter; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.awt.*; 8 | 9 | public class MyColorConverter extends Converter { 10 | @Override 11 | public @Nullable Color fromString(@NotNull String value) { 12 | return new Color(Integer.parseInt(value)); 13 | } 14 | 15 | @Override 16 | public @Nullable String toString(@NotNull Color value) { 17 | return String.valueOf(value.getRGB()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/utils/functions.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.utils 2 | 3 | import com.intellij.openapi.application.ApplicationManager 4 | 5 | /** 6 | * Shortcut to run action on EDT 7 | */ 8 | inline fun runOnEDT(runnable: Runnable) = 9 | ApplicationManager.getApplication().invokeLater(runnable) 10 | 11 | /** 12 | * Shortcut to run action on EDT ignoring IndexOutOfBoundsException 13 | */ 14 | inline fun runOnEDTIgnoringIndexOutOfBounds(runnable: Runnable) = 15 | ApplicationManager.getApplication().invokeLater { 16 | try { 17 | runnable.run() 18 | } catch (_: IndexOutOfBoundsException) { 19 | 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/render/ElementMetrics.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.render 2 | 3 | import com.ioannuwu.inline2.pluginlogic.render.metrics.FontMetrics 4 | import com.ioannuwu.inline2.pluginlogic.render.metrics.LineMetrics 5 | import com.ioannuwu.inline2.pluginlogic.render.metrics.RenderData 6 | import com.ioannuwu.inline2.pluginlogic.render.metrics.otherdata.NumberOfWhitespaces 7 | 8 | /** 9 | * Represents text element in editor 10 | * 11 | * Provides all information needed to place and paint text element 12 | * in editor 13 | */ 14 | interface ElementMetrics : RenderData, LineMetrics, FontMetrics { 15 | fun whitespaces(n: NumberOfWhitespaces): ElementMetrics 16 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/ioannuwu/inline/utils/simpletests/PartitionTest.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.utils.simpletests 2 | 3 | import junit.framework.TestCase.assertTrue 4 | import org.junit.Test 5 | 6 | class PartitionTest { 7 | 8 | @Test 9 | fun partitionShouldDoItsWork() { 10 | val list = listOf(UglyObject, UglyObject, FancyObject) 11 | 12 | val (fancy, ugly) = list.partition(PartitionHelper::isFancy) 13 | 14 | assertTrue(fancy.count() == 1) 15 | assertTrue(ugly.count() == 2) 16 | } 17 | } 18 | 19 | private object FancyObject : PartitionHelper { 20 | override val isFancy = true 21 | } 22 | 23 | private object UglyObject : PartitionHelper { 24 | override val isFancy = false 25 | } 26 | 27 | private interface PartitionHelper { 28 | val isFancy: Boolean 29 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/ioannuwu/inline/utils/simpletests/TestingUtilsTest.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.utils.simpletests 2 | 3 | import com.intellij.testFramework.UsefulTestCase.assertThrows 4 | import com.ioannuwu.inline.utils.TEST 5 | import org.junit.Test 6 | 7 | class TestingUtilsTest { 8 | 9 | @Test 10 | fun `object should be able to use foo`() { 11 | assert(ObjectUsesFoo.foo()) 12 | } 13 | 14 | @Test 15 | fun `using bar should crash`() { 16 | assertThrows(Throwable::class.java) { 17 | ObjectUsesFoo.bar() 18 | } 19 | } 20 | } 21 | 22 | private object ObjectUsesFoo : TestingUtilsHelper { 23 | override fun foo() = true 24 | override fun bar() = TEST() 25 | } 26 | 27 | private interface TestingUtilsHelper { 28 | fun foo(): Boolean 29 | fun bar(): Boolean 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/ioannuwu/inline/data/FontSettingsState.java: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.data; 2 | 3 | import java.io.Serializable; 4 | import java.util.Objects; 5 | 6 | public class FontSettingsState implements Serializable { 7 | 8 | public FontSettingsState() {} 9 | 10 | public FontSettingsState(String fontName, String textSample) { 11 | this.fontName = fontName; 12 | this.textSample = textSample; 13 | } 14 | 15 | public String fontName; 16 | public String textSample; 17 | 18 | @Override 19 | public boolean equals(Object o) { 20 | if (this == o) return true; 21 | if (o == null || getClass() != o.getClass()) return false; 22 | FontSettingsState that = (FontSettingsState) o; 23 | return Objects.equals(fontName, that.fontName) && Objects.equals(textSample, that.textSample); 24 | } 25 | 26 | @Override 27 | public int hashCode() { 28 | return Objects.hash(fontName, textSample); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/settings/data/SettingsService.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.settings.data 2 | 3 | import com.intellij.openapi.components.PersistentStateComponent 4 | import com.intellij.openapi.components.State 5 | import com.intellij.openapi.components.Storage 6 | import com.intellij.util.xmlb.XmlSerializerUtil 7 | import com.ioannuwu.inline2.settings.event.SettingsChangeDispatcher 8 | 9 | @State( 10 | name = "com.ioannuwu.inline.data.SettingsState", 11 | storages = [Storage("com.ioannuwu.inline.Settings.xml")] 12 | ) 13 | class SettingsService : PersistentStateComponent{ 14 | 15 | private var settingsState: SettingsState = SettingsState() 16 | 17 | override fun getState(): SettingsState = settingsState 18 | 19 | override fun loadState(state: SettingsState) { 20 | XmlSerializerUtil.copyBean(state, settingsState) 21 | } 22 | 23 | override fun initializeComponent() { 24 | SettingsChangeDispatcher.notify { settingsState } 25 | } 26 | } -------------------------------------------------------------------------------- /.run/Run IDE for UI Tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 15 | 17 | true 18 | true 19 | false 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/test/kotlin/com/ioannuwu/inline/utils/simpletests/SortingTest.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.utils.simpletests 2 | 3 | import org.junit.Test 4 | 5 | class SortingTest { 6 | 7 | @Test 8 | fun `objects with lowest priorities should be rendered first`() { 9 | val list = listOf( 10 | LowPriorityObject, HighPriorityObject, MidPriorityObject, LowPriorityObject, 11 | HighPriorityObject, MidPriorityObject, LowPriorityObject, LowPriorityObject, 12 | ) 13 | 14 | val sortedList = list.sortedBy { it.priority } 15 | assert(sortedList.first().priority == -100) 16 | assert(sortedList.last().priority == 100) 17 | } 18 | } 19 | 20 | private object LowPriorityObject : SortingHelper { 21 | override val priority: Int = -100 22 | } 23 | 24 | private object HighPriorityObject : SortingHelper { 25 | override val priority: Int = 100 26 | } 27 | 28 | private object MidPriorityObject : SortingHelper { 29 | override val priority: Int = 10 30 | } 31 | 32 | private interface SortingHelper { 33 | val priority: Int 34 | } -------------------------------------------------------------------------------- /.run/Run IDE with Plugin.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | 23 | 24 | -------------------------------------------------------------------------------- /.run/Run Plugin Tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/test/java/com/ioannuwu/inline/data/DefaultSettingsTest.java: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.data; 2 | 3 | import org.junit.Test; 4 | 5 | import static junit.framework.TestCase.assertNotNull; 6 | 7 | public class DefaultSettingsTest { 8 | 9 | @Test 10 | public void defaultSettingsTest() { 11 | assertNotNull("There is no ERROR in default settings", DefaultSettings.ERROR); 12 | assertNotNull("There is no WARNING in default settings", DefaultSettings.WARNING); 13 | assertNotNull("There is no WEAK WARNING in default settings", DefaultSettings.WEAK_WARNING); 14 | assertNotNull("There is no IGNORE LIST in default settings", DefaultSettings.IGNORE_LIST); 15 | assertNotNull("There is no FONT in default settings", DefaultSettings.FONT); 16 | assertNotNull("There is no ERROR ICON in default settings", DefaultSettings.Icons.ERROR); 17 | assertNotNull("There is no SERVER ERROR ICON in default settings", DefaultSettings.Icons.SERVER_ERROR); 18 | assertNotNull("There is no WEAK WARNING ICON in default settings", DefaultSettings.Icons.WEAK_WARNING); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.run/Run Qodana.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 16 | 19 | 21 | true 22 | true 23 | false 24 | 25 | 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Ivan Chabanov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/settings/ui/InLineSettingsConfigurable.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.settings.ui 2 | 3 | import com.intellij.openapi.application.ApplicationManager 4 | import com.intellij.openapi.options.Configurable 5 | import com.ioannuwu.inline2.settings.data.SettingsService 6 | import com.ioannuwu.inline2.settings.event.SettingsChangeDispatcher 7 | import javax.swing.JComponent 8 | 9 | class InLineSettingsConfigurable : Configurable { 10 | 11 | private val settingsService: SettingsService = 12 | ApplicationManager.getApplication().getService(SettingsService::class.java) 13 | 14 | override fun getDisplayName(): String = "InLine Settings" 15 | 16 | private val settingsUIView: SettingsUIView = SettingsUIViewImpl(settingsService.state) 17 | 18 | override fun createComponent(): JComponent = settingsUIView.createComponent() 19 | 20 | override fun isModified(): Boolean = settingsService.state != settingsUIView.state 21 | 22 | override fun apply() { 23 | val newState = settingsUIView.state 24 | 25 | settingsService.loadState(newState) 26 | SettingsChangeDispatcher.notify { newState } 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/action/ChangeErrorTypeAction.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.action 2 | 3 | import com.intellij.openapi.actionSystem.AnAction 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import com.intellij.openapi.application.ApplicationManager 6 | import com.ioannuwu.inline.data.TextStyle 7 | import com.ioannuwu.inline2.settings.data.SettingsService 8 | import com.ioannuwu.inline2.settings.event.SettingsChangeDispatcher 9 | 10 | class ChangeErrorTypeAction : AnAction() { 11 | 12 | private val settingsService: SettingsService = 13 | ApplicationManager.getApplication().getService(SettingsService::class.java) 14 | 15 | override fun actionPerformed(e: AnActionEvent) { 16 | SettingsChangeDispatcher.notify { oldSettingsState -> 17 | val oldTextStyle = oldSettingsState.textStyle 18 | 19 | val copy = oldSettingsState.copy() 20 | 21 | val newTextStyle = when (oldTextStyle) { 22 | TextStyle.AFTER_LINE -> TextStyle.UNDER_LINE 23 | TextStyle.UNDER_LINE -> TextStyle.AFTER_LINE 24 | } 25 | copy.textStyle = newTextStyle 26 | 27 | settingsService.loadState(copy) 28 | copy 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/EditorModel.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic 2 | 3 | import com.intellij.openapi.Disposable 4 | import com.intellij.openapi.editor.EditorCustomElementRenderer 5 | import java.awt.Color 6 | import javax.swing.Icon 7 | 8 | import com.intellij.openapi.util.Key 9 | 10 | /** 11 | * Represents common view on the editor which can be 12 | * used to interact with necessary models 13 | * 14 | * @see com.intellij.openapi.editor.Editor 15 | * 16 | * @see com.intellij.openapi.editor.InlayModel 17 | * @see com.intellij.openapi.editor.markup.MarkupModel 18 | * 19 | * @see com.intellij.openapi.Disposable 20 | */ 21 | interface EditorModel { 22 | 23 | /** 24 | * Should be used only from EDT 25 | */ 26 | fun addAfterLineEndElement(offset: Int, renderer: EditorCustomElementRenderer): Disposable 27 | 28 | /** 29 | * Should be used only from EDT 30 | */ 31 | fun addLineHighlighter(offset: Int, backgroundColor: Color): Disposable 32 | 33 | /** 34 | * Should be used only from EDT 35 | */ 36 | fun addGutterIcon(offset: Int, icon: Icon): Disposable 37 | 38 | /** 39 | * Should be used only from EDT 40 | */ 41 | fun addUnderLineElement(offset: Int, renderer: EditorCustomElementRenderer): Disposable 42 | } -------------------------------------------------------------------------------- /src/main/java/com/ioannuwu/inline/ui/settingscomponent/components/OneGutterModeComponent.java: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.ui.settingscomponent.components; 2 | 3 | import com.intellij.ui.ContextHelpLabel; 4 | import com.intellij.ui.components.JBCheckBox; 5 | import com.intellij.util.ui.FormBuilder; 6 | import com.ioannuwu.inline.ui.settingscomponent.Component; 7 | import com.ioannuwu.inline2.settings.data.MutableState; 8 | 9 | import javax.swing.*; 10 | 11 | public class OneGutterModeComponent implements Component, MutableState { 12 | 13 | private final JBCheckBox checkBox; 14 | 15 | public OneGutterModeComponent(boolean oneGutterMode) { 16 | checkBox = new JBCheckBox(); 17 | checkBox.setSelected(oneGutterMode); 18 | } 19 | 20 | @Override 21 | public void addToBuilder(FormBuilder formBuilder) { 22 | ContextHelpLabel contextHelpLabel = ContextHelpLabel.create("Show only one gutter icon per line"); 23 | JPanel panel = new JPanel(); 24 | panel.add(new JLabel("Gutter destruction-free mode")); 25 | panel.add(contextHelpLabel); 26 | panel.add(checkBox); 27 | formBuilder.addLabeledComponent(panel, new JLabel()); 28 | } 29 | 30 | @Override 31 | public Boolean getState() { 32 | return checkBox.isSelected(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.run/Run Plugin Verification.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | false 23 | 24 | 26 | 27 | -------------------------------------------------------------------------------- /src/test/kotlin/com/ioannuwu/inline/utils/TestDocument.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.utils 2 | 3 | import com.intellij.openapi.editor.Document 4 | import com.intellij.openapi.editor.RangeMarker 5 | import com.intellij.openapi.util.Key 6 | 7 | object TestDocument : Document { 8 | 9 | val LINE_COUNT = 10 10 | 11 | override fun getLineCount(): Int = LINE_COUNT 12 | override fun getLineNumber(offset: Int): Int = offset 13 | 14 | 15 | override fun getUserData(key: Key): T = TEST() 16 | override fun putUserData(key: Key, value: T?) = TEST() 17 | override fun getImmutableCharSequence(): CharSequence = TEST() 18 | override fun getLineStartOffset(line: Int): Int = TEST() 19 | override fun getLineEndOffset(line: Int): Int = TEST() 20 | override fun insertString(offset: Int, s: CharSequence) = TEST() 21 | override fun deleteString(startOffset: Int, endOffset: Int) = TEST() 22 | override fun replaceString(startOffset: Int, endOffset: Int, s: CharSequence) = TEST() 23 | override fun isWritable(): Boolean = TEST() 24 | override fun getModificationStamp(): Long = TEST() 25 | override fun createRangeMarker(startOffset: Int, endOffset: Int, surviveOnExternalChange: Boolean): RangeMarker = TEST() 26 | override fun createGuardedBlock(startOffset: Int, endOffset: Int): RangeMarker = TEST() 27 | override fun setText(text: CharSequence) = TEST() 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/action/ChangeErrorVisibilityAction.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.action 2 | 3 | import com.intellij.openapi.actionSystem.AnAction 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import com.intellij.openapi.application.ApplicationManager 6 | import com.ioannuwu.inline2.settings.data.SettingsService 7 | import com.ioannuwu.inline2.settings.event.SettingsChangeDispatcher 8 | 9 | class ChangeErrorVisibilityAction : AnAction() { 10 | 11 | private val settingsService: SettingsService = 12 | ApplicationManager.getApplication().getService(SettingsService::class.java) 13 | 14 | override fun actionPerformed(e: AnActionEvent) { 15 | SettingsChangeDispatcher.notify { oldSettingsState -> 16 | 17 | val newSettingsState = oldSettingsState.copy() 18 | 19 | val isShowText = !oldSettingsState.error.showText 20 | 21 | newSettingsState.error.showText = isShowText 22 | newSettingsState.warning.showText = isShowText 23 | newSettingsState.weakWarning.showText = isShowText 24 | newSettingsState.information.showText = isShowText 25 | newSettingsState.serverError.showText = isShowText 26 | newSettingsState.otherError.showText = isShowText 27 | 28 | settingsService.loadState(newSettingsState) 29 | 30 | newSettingsState 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/com/ioannuwu/inline/ui/settingscomponent/components/NumberOfWhitespacesComponent.java: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.ui.settingscomponent.components; 2 | 3 | import com.intellij.ui.components.JBLabel; 4 | import com.intellij.ui.components.JBTextField; 5 | import com.intellij.util.ui.FormBuilder; 6 | import com.ioannuwu.inline.ui.settingscomponent.Component; 7 | import com.ioannuwu.inline2.settings.data.MutableState; 8 | 9 | import javax.swing.*; 10 | 11 | public class NumberOfWhitespacesComponent implements Component, MutableState { 12 | 13 | private final JBTextField numberOfWhitespacesField; 14 | 15 | public NumberOfWhitespacesComponent(int numberOfWhitespaces) { 16 | this.numberOfWhitespacesField = new JBTextField(String.valueOf(numberOfWhitespaces)); 17 | } 18 | 19 | @Override 20 | public void addToBuilder(FormBuilder formBuilder) { 21 | JPanel panel = new JPanel(); 22 | panel.add(new JBLabel("Space after end of the line and between hints")); 23 | panel.add(numberOfWhitespacesField); 24 | formBuilder.addLabeledComponent(panel, new JBLabel()); 25 | } 26 | 27 | @Override 28 | public Integer getState() { 29 | String text = numberOfWhitespacesField.getText(); 30 | int num = 0; 31 | try { 32 | num = Integer.parseInt(text); 33 | } catch (NumberFormatException ignored) { 34 | } 35 | return num; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # IntelliJ Platform Artifacts Repositories -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html 2 | 3 | pluginGroup = com.github.ioannuwu.inline 4 | pluginName = InLine 5 | pluginRepositoryUrl = https://github.com/IoaNNUwU/InLine 6 | # SemVer format -> https://semver.org 7 | pluginVersion = 1.3.0 8 | 9 | # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html 10 | pluginSinceBuild = 231 11 | pluginUntilBuild = 232.* 12 | 13 | # IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension 14 | platformType = IC 15 | platformVersion = 2023.2 16 | 17 | # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html 18 | # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 19 | platformPlugins = 20 | 21 | # Gradle Releases -> https://github.com/gradle/gradle/releases 22 | gradleVersion = 7.6 23 | 24 | # Opt-out flag for bundling Kotlin standard library -> https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library 25 | # suppress inspection "UnusedProperty" 26 | kotlin.stdlib.default.dependency = false 27 | 28 | # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html 29 | # suppress inspection "UnusedProperty" 30 | org.gradle.unsafe.configuration-cache = false 31 | -------------------------------------------------------------------------------- /src/main/java/com/ioannuwu/inline/ui/settingscomponent/components/MaxErrorsPerLineComponent.java: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.ui.settingscomponent.components; 2 | 3 | import com.intellij.ui.components.JBLabel; 4 | import com.intellij.ui.components.JBTextField; 5 | import com.intellij.util.ui.FormBuilder; 6 | import com.ioannuwu.inline.data.DefaultSettings; 7 | import com.ioannuwu.inline.ui.settingscomponent.Component; 8 | import com.ioannuwu.inline2.settings.data.MutableState; 9 | 10 | import javax.swing.*; 11 | 12 | public class MaxErrorsPerLineComponent implements Component, MutableState { 13 | 14 | private final JBTextField maxErrorsPerLineField; 15 | 16 | public MaxErrorsPerLineComponent(int maxErrorsPerLine) { 17 | this.maxErrorsPerLineField = new JBTextField("" + maxErrorsPerLine); 18 | } 19 | 20 | @Override 21 | public void addToBuilder(FormBuilder formBuilder) { 22 | JPanel panel = new JPanel(); 23 | panel.add(new JBLabel("Max number of errors per line")); 24 | panel.add(maxErrorsPerLineField); 25 | formBuilder.addLabeledComponent(panel, new JBLabel()); 26 | } 27 | 28 | @Override 29 | public Integer getState() { 30 | String text = maxErrorsPerLineField.getText(); 31 | int num = DefaultSettings.MAX_ERRORS_PER_LINE; 32 | try { 33 | num = Integer.parseInt(text); 34 | } catch (NumberFormatException ignored) { 35 | } 36 | return num; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/render/EditorElementMetrics.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.render 2 | 3 | import com.intellij.icons.AllIcons 4 | import com.intellij.openapi.editor.Editor 5 | import com.intellij.ui.JBColor 6 | import com.ioannuwu.inline2.pluginlogic.render.ElementMetrics 7 | import com.ioannuwu.inline2.pluginlogic.render.metrics.otherdata.NumberOfWhitespaces 8 | import java.awt.Color 9 | import java.awt.Font 10 | import javax.swing.Icon 11 | 12 | class EditorElementMetrics( 13 | private val editor: Editor, 14 | private val description: String, 15 | ) : ElementMetrics { 16 | 17 | override fun textColor(): Color = JBColor.RED 18 | 19 | override fun backgroundColor(): Color = JBColor.BLACK 20 | 21 | override fun effectColor(): Color = JBColor.YELLOW 22 | 23 | override fun text(): String = description 24 | 25 | override fun font(): Font = editor.component.font.deriveFont(editor.colorsScheme.editorFontSize.toFloat()) 26 | 27 | override fun charWidth(): Int = 28 | editor.component.getFontMetrics(font()).charWidth(' ') 29 | 30 | override fun stringWidth(string: String): Int = 31 | editor.component.getFontMetrics(font()).stringWidth(string) 32 | 33 | override fun whitespaces(n: NumberOfWhitespaces): ElementMetrics = this 34 | 35 | override fun gutterIcon(): Icon = AllIcons.General.Error 36 | 37 | override fun lineHeight(): Int = editor.lineHeight 38 | 39 | override fun ascent(): Int = editor.ascent 40 | } -------------------------------------------------------------------------------- /src/main/java/com/ioannuwu/inline/ui/settingscomponent/components/TextStyleSelectionComponent.java: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.ui.settingscomponent.components; 2 | 3 | import com.intellij.openapi.ui.ComboBox; 4 | import com.intellij.util.ui.FormBuilder; 5 | import com.ioannuwu.inline.data.TextStyle; 6 | import com.ioannuwu.inline.ui.settingscomponent.Component; 7 | import com.ioannuwu.inline2.settings.data.MutableState; 8 | 9 | import javax.swing.*; 10 | import java.util.ArrayList; 11 | 12 | public class TextStyleSelectionComponent implements Component, MutableState { 13 | 14 | private static final String[] values = getValues(); 15 | 16 | private final ComboBox stylesComboBox; 17 | 18 | public TextStyleSelectionComponent(TextStyle textStyle) { 19 | this.stylesComboBox = new ComboBox<>(values); 20 | 21 | for (int i = 0; i < values.length; i++) 22 | if (values[i].equals(textStyle.name())) this.stylesComboBox.setSelectedIndex(i); 23 | } 24 | 25 | @Override 26 | public void addToBuilder(FormBuilder formBuilder) { 27 | JPanel panel = new JPanel(); 28 | panel.add(new JLabel("Text style")); 29 | panel.add(stylesComboBox); 30 | formBuilder.addLabeledComponent(panel, new JLabel()); 31 | 32 | } 33 | 34 | @Override 35 | public TextStyle getState() { 36 | int index = stylesComboBox.getSelectedIndex(); 37 | return TextStyle.valueOf(values[index]); 38 | } 39 | 40 | private static String[] getValues() { 41 | ArrayList styles = new ArrayList<>(2); 42 | for (TextStyle style : TextStyle.values()) { 43 | styles.add(style.name()); 44 | } 45 | return styles.toArray(new String[0]); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/settings/event/SettingsChangeDispatcher.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.settings.event 2 | 3 | import com.ioannuwu.inline2.pluginlogic.FileOpenedListener 4 | import com.ioannuwu.inline2.settings.data.SettingsState 5 | 6 | object SettingsChangeDispatcher { 7 | 8 | private val firstQueue: MutableList = mutableListOf() 9 | private val defaultQueue: MutableList = mutableListOf() 10 | private val lastQueue: MutableList = mutableListOf() 11 | 12 | fun subscribe(listener: SettingsChangeListener, priority: SettingsChangePriority) { 13 | 14 | when (priority) { 15 | SettingsChangePriority.FIRST -> firstQueue.add(listener) 16 | SettingsChangePriority.DEFAULT -> defaultQueue.add(listener) 17 | SettingsChangePriority.LAST -> lastQueue.add(listener) 18 | } 19 | } 20 | 21 | fun subscribe(listener: SettingsChangeListener) = subscribe(listener, SettingsChangePriority.DEFAULT) 22 | 23 | fun unsubscribe(listener: SettingsChangeListener) { 24 | firstQueue.remove(listener) 25 | defaultQueue.remove(listener) 26 | lastQueue.remove(listener) 27 | } 28 | 29 | private var oldSettingsState: SettingsState = SettingsState() 30 | 31 | fun notify(newStateFromOld: (SettingsState) -> SettingsState) { 32 | 33 | val newState = newStateFromOld(oldSettingsState) 34 | 35 | firstQueue.forEach { it.onSettingsChange(newState) } 36 | defaultQueue.forEach { it.onSettingsChange(newState) } 37 | lastQueue.forEach { it.onSettingsChange(newState) } 38 | 39 | FileOpenedListener.updateAllListeners() 40 | 41 | oldSettingsState = newState 42 | } 43 | } -------------------------------------------------------------------------------- /.github/workflows/buildAndTest.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: [ main ] 5 | pull_request: 6 | 7 | jobs: 8 | build: 9 | name: Build 10 | runs-on: ubuntu-latest 11 | steps: 12 | 13 | - name: Maximize Build Space 14 | run: | 15 | sudo rm -rf /usr/share/dotnet 16 | sudo rm -rf /usr/local/lib/android 17 | sudo rm -rf /opt/ghc 18 | 19 | - name: Fetch Sources 20 | uses: actions/checkout@v3 21 | 22 | - name: Gradle Wrapper Validation 23 | uses: gradle/wrapper-validation-action@v1.0.5 24 | 25 | - name: Setup Java 26 | uses: actions/setup-java@v3 27 | with: 28 | distribution: temurin 29 | java-version: 17 30 | 31 | - name: Build 32 | run: ./gradlew build --stacktrace 33 | 34 | - name: Test 35 | run: ./gradlew test --stacktrace 36 | 37 | - name: Collect Tests Result 38 | if: ${{ failure() }} 39 | uses: actions/upload-artifact@v3 40 | with: 41 | name: tests-result 42 | path: ${{ github.workspace }}/build/reports/tests 43 | 44 | - name: Setup Plugin Verifier IDEs Cache # Cache Plugin Verifier IDEs 45 | uses: actions/cache@v3 46 | with: 47 | path: ${{ steps.properties.outputs.pluginVerifierHomeDir }}/ides 48 | key: plugin-verifier-${{ hashFiles('build/listProductsReleases.txt') }} 49 | 50 | - name: Run Plugin Verification tasks 51 | run: ./gradlew runPluginVerifier --stacktrace 52 | 53 | - name: Collect Plugin Verifier Result 54 | if: ${{ always() }} 55 | uses: actions/upload-artifact@v3 56 | with: 57 | name: pluginVerifier-result 58 | path: ${{ github.workspace }}/build/reports/pluginVerifier -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.github.ioannuwu.inline 4 | InLine 5 | IoaNN UwU 6 | 7 | com.intellij.modules.platform 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/java/com/ioannuwu/inline/ui/settingscomponent/components/EffectTypeComponent.java: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.ui.settingscomponent.components; 2 | 3 | import com.intellij.openapi.ui.ComboBox; 4 | import com.intellij.ui.components.JBLabel; 5 | import com.intellij.util.ui.FormBuilder; 6 | import com.ioannuwu.inline.data.EffectType; 7 | import com.ioannuwu.inline.ui.settingscomponent.Component; 8 | import com.ioannuwu.inline2.settings.data.MutableState; 9 | 10 | import javax.swing.*; 11 | import java.util.ArrayList; 12 | 13 | public class EffectTypeComponent implements Component, MutableState { 14 | 15 | private static final String[] values = getValues(); 16 | 17 | private final ComboBox effectTypeComboBox; 18 | 19 | public EffectTypeComponent(EffectType effectType) { 20 | 21 | this.effectTypeComboBox = new ComboBox<>(values); 22 | 23 | for (int i = 0; i < values.length; i++) { 24 | assert values[i] != null; 25 | assert effectType != null; 26 | if (values[i].equals(effectType.name())) this.effectTypeComboBox.setSelectedIndex(i); 27 | } 28 | } 29 | 30 | @Override 31 | public void addToBuilder(FormBuilder formBuilder) { 32 | JPanel panel = new JPanel(); 33 | panel.add(new JBLabel("Effect type")); 34 | panel.add(effectTypeComboBox); 35 | formBuilder.addLabeledComponent(panel, new JLabel()); 36 | } 37 | 38 | @Override 39 | public EffectType getState() { 40 | int index = effectTypeComboBox.getSelectedIndex(); 41 | return EffectType.valueOf(values[index]); 42 | } 43 | 44 | private static String[] getValues() { 45 | ArrayList effects = new ArrayList<>(5); 46 | for (EffectType type : EffectType.values()) { 47 | effects.add(type.name()); 48 | } 49 | return effects.toArray(new String[0]); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/ioannuwu/inline/ui/settingscomponent/components/IgnoreListComponent.java: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.ui.settingscomponent.components; 2 | 3 | import com.intellij.ui.ContextHelpLabel; 4 | import com.intellij.ui.components.JBLabel; 5 | import com.intellij.ui.components.JBTextArea; 6 | import com.intellij.util.ui.FormBuilder; 7 | import com.ioannuwu.inline.ui.settingscomponent.Component; 8 | import com.ioannuwu.inline2.settings.data.MutableState; 9 | 10 | import javax.swing.*; 11 | import java.util.Arrays; 12 | 13 | public class IgnoreListComponent implements Component, MutableState { 14 | 15 | private static final String PREFIX = " > "; 16 | 17 | private final JBTextArea textComponent; 18 | 19 | public IgnoreListComponent(String[] ignoreList) { 20 | 21 | if (ignoreList.length == 0) { 22 | textComponent = new JBTextArea(); 23 | } else { 24 | StringBuilder builder = new StringBuilder(); 25 | for (String text : ignoreList) { 26 | builder.append(text).append('\n'); 27 | } 28 | textComponent = new JBTextArea(builder.toString()); 29 | } 30 | } 31 | 32 | @Override 33 | public void addToBuilder(FormBuilder formBuilder) { 34 | JPanel panel = new JPanel(); 35 | panel.add(new JBLabel(PREFIX + "Ignore: ")); 36 | panel.add(ContextHelpLabel.create(description)); 37 | formBuilder.addLabeledComponent(panel, new JLabel()); 38 | formBuilder.addComponent(textComponent); 39 | } 40 | 41 | @Override 42 | public String[] getState() { 43 | String fulltext = textComponent.getText(); 44 | String[] split = fulltext.split("\n"); 45 | return Arrays.stream(split).filter(line -> !line.isBlank()).toArray(String[]::new); 46 | } 47 | 48 | private static final String description = "

List of strings divided by new line

" + 49 | "

hint will be ignored if it's description contains one of them

"; 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/ioannuwu/inline/ui/settingscomponent/components/fontselectioncomponent/FontListComboBox.java: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.ui.settingscomponent.components.fontselectioncomponent; 2 | 3 | import com.intellij.openapi.ui.ComboBox; 4 | import com.intellij.openapi.util.Pair; 5 | 6 | import java.awt.*; 7 | import java.util.Arrays; 8 | import java.util.HashMap; 9 | import java.util.function.Function; 10 | import java.util.function.Predicate; 11 | 12 | public class FontListComboBox extends ComboBox { 13 | 14 | private final Font[] allAvailableFonts; 15 | private Font[] currentFonts; 16 | 17 | private String textSample; 18 | 19 | public FontListComboBox(Font[] fonts) { 20 | super(Arrays.stream(fonts) 21 | .map(Font::getFontName) 22 | .toArray(String[]::new)); 23 | allAvailableFonts = fonts; 24 | currentFonts = allAvailableFonts; 25 | } 26 | 27 | void retainOnlySupporting(String fontSample) { 28 | textSample = fontSample; 29 | currentFonts = Arrays.stream(allAvailableFonts) 30 | .filter(filterBySample(fontSample)) 31 | .filter(distinctBy(Font::getFontName)) 32 | .toArray(Font[]::new); 33 | 34 | this.removeAllItems(); 35 | Arrays.stream(currentFonts).forEach(font -> this.addItem(font.getFontName())); 36 | } 37 | private static Predicate filterBySample(String fontSample) { 38 | return fontSample == null || fontSample.isBlank() 39 | ? font -> true 40 | : font -> font.canDisplayUpTo(fontSample) == -1; 41 | } 42 | private static Predicate distinctBy(Function keyExtractor) { 43 | HashMap seen = new HashMap<>(); 44 | return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; 45 | } 46 | 47 | 48 | Pair getSelectedFontAndTextSample() { 49 | return new Pair<>( 50 | currentFonts[this.getSelectedIndex()], 51 | textSample); 52 | } 53 | 54 | public void setSelectedFont(String fontName) { 55 | int setIndex = 0; 56 | for (int i = 0; i < currentFonts.length; i++) { 57 | if (currentFonts[i].getFontName().equals(fontName)) setIndex = i; 58 | } 59 | this.setSelectedIndex(setIndex); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/render/RenderDataElementMetrics.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.render 2 | 3 | import com.intellij.openapi.editor.Editor 4 | import com.ioannuwu.inline2.pluginlogic.render.metrics.RenderData 5 | import com.ioannuwu.inline2.pluginlogic.render.metrics.otherdata.NumberOfWhitespaces 6 | import java.awt.Color 7 | import java.awt.Font 8 | import javax.swing.Icon 9 | 10 | class RenderDataElementMetrics( 11 | private val renderData: RenderData, 12 | private val editor: Editor, 13 | ) : ElementMetrics { 14 | 15 | override fun gutterIcon(): Icon? = renderData.gutterIcon() 16 | 17 | override fun font(): Font = 18 | renderData.font().deriveFont(editor.colorsScheme.editorFontSize.toFloat()) 19 | 20 | override fun textColor(): Color? = renderData.textColor() 21 | override fun backgroundColor(): Color? = renderData.backgroundColor() 22 | override fun effectColor(): Color? = renderData.effectColor() 23 | 24 | override fun text(): String = renderData.text() 25 | 26 | override fun lineHeight(): Int = editor.lineHeight 27 | override fun ascent(): Int = editor.ascent 28 | 29 | override fun charWidth(): Int = 30 | editor.component.getFontMetrics(font()).charWidth(' ') 31 | 32 | override fun stringWidth(string: String): Int = 33 | editor.component.getFontMetrics(font()).stringWidth(string) 34 | 35 | override fun whitespaces(n: NumberOfWhitespaces): ElementMetrics { 36 | return object : ElementMetrics { 37 | override fun whitespaces(n : NumberOfWhitespaces): ElementMetrics = this 38 | override fun gutterIcon(): Icon? = null 39 | 40 | override fun font(): Font = 41 | renderData.font().deriveFont(editor.colorsScheme.editorFontSize.toFloat()) 42 | 43 | override fun textColor(): Color? = null 44 | override fun backgroundColor(): Color? = null 45 | override fun effectColor(): Color? = null 46 | 47 | override fun text(): String = " ".repeat(n.numberOfWhitespaces()) 48 | 49 | override fun lineHeight(): Int = editor.lineHeight 50 | override fun ascent(): Int = editor.ascent 51 | 52 | override fun charWidth(): Int = 53 | editor.component.getFontMetrics(font()).charWidth(' ') 54 | 55 | override fun stringWidth(string: String): Int = 56 | editor.component.getFontMetrics(font()).stringWidth(string) 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/main/java/com/ioannuwu/inline/data/DefaultSettings.java: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.data; 2 | 3 | import com.intellij.icons.AllIcons; 4 | 5 | import javax.swing.*; 6 | import java.awt.*; 7 | 8 | public class DefaultSettings { 9 | public static final int NUMBER_OF_WHITESPACES = 0; 10 | 11 | public static final EffectType EFFECT_TYPE = EffectType.NONE; 12 | 13 | public static final int MAX_ERRORS_PER_LINE = 5; 14 | 15 | public static final FontSettingsState FONT = new FontSettingsState("Dialog.plain", "sample"); 16 | 17 | public static final TextStyle TEXT_STYLE = TextStyle.AFTER_LINE; 18 | public static final boolean ONE_GUTTER_MODE = false; 19 | 20 | public static final SeverityLevelState ERROR = defaultSeverityLevelState(new Color(183, 43, 43)); 21 | public static final SeverityLevelState WARNING = defaultSeverityLevelState(new Color(189, 115, 37)); 22 | public static final SeverityLevelState WEAK_WARNING = defaultSeverityLevelState(new Color(183, 155, 41)); 23 | public static final SeverityLevelState INFORMATION = defaultSeverityLevelState(new Color(61, 108, 201)); 24 | public static final SeverityLevelState SERVER_ERROR = defaultSeverityLevelState(new Color(128, 29, 185)); 25 | public static final SeverityLevelState OTHER_ERROR = defaultSeverityLevelState(new Color(141, 169, 169)); 26 | 27 | public static final String[] IGNORE_LIST = new String[]{ 28 | "TODO", 29 | "Typo", 30 | "Automatically declared based on the expected type", 31 | "Value captured in a closure", 32 | "Expression should use clarifying parentheses", 33 | "Missing trailing comma", 34 | }; 35 | 36 | public static class Icons { 37 | public static final Icon ERROR = AllIcons.General.Error; 38 | public static final Icon WARNING = AllIcons.General.Warning; 39 | public static final Icon WEAK_WARNING = AllIcons.General.Warning; 40 | public static final Icon INFORMATION = AllIcons.General.Information; 41 | public static final Icon SERVER_ERROR = AllIcons.General.ArrowDown; 42 | public static final Icon OTHER_ERROR = AllIcons.General.Add; 43 | } 44 | 45 | private static SeverityLevelState defaultSeverityLevelState(Color color) { 46 | Color backgroundColor = color.darker().darker(); 47 | Color newBackgroundColor = new Color(backgroundColor.getRed(), backgroundColor.getGreen(), backgroundColor.getBlue(), 80); 48 | return new SeverityLevelState( 49 | color, 50 | newBackgroundColor, 51 | color.darker() 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/FileOpenedListener.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic 2 | 3 | import com.intellij.openapi.application.ApplicationManager 4 | import com.intellij.openapi.editor.ex.MarkupModelEx 5 | import com.intellij.openapi.editor.ex.RangeHighlighterEx 6 | import com.intellij.openapi.editor.impl.DocumentMarkupModel 7 | import com.intellij.openapi.fileEditor.FileEditorManager 8 | import com.intellij.openapi.fileEditor.FileOpenedSyncListener 9 | import com.intellij.openapi.fileEditor.TextEditor 10 | import com.intellij.openapi.fileEditor.ex.FileEditorWithProvider 11 | import com.intellij.openapi.vfs.VirtualFile 12 | import com.ioannuwu.inline2.pluginlogic.editormodel.EditorModelImpl 13 | import com.ioannuwu.inline2.pluginlogic.render.BySettingsDataSelector 14 | import com.ioannuwu.inline2.pluginlogic.utils.runOnEDT 15 | import com.ioannuwu.inline2.settings.data.SettingsService 16 | import com.ioannuwu.inline2.settings.event.SettingsChangeDispatcher 17 | 18 | class FileOpenedListener : FileOpenedSyncListener { 19 | 20 | override fun fileOpenedSync( 21 | source: FileEditorManager, 22 | file: VirtualFile, 23 | editorsWithProviders: List 24 | ) { 25 | for (fileEditorWithProvider in editorsWithProviders) { 26 | 27 | val fileEditor = fileEditorWithProvider.fileEditor 28 | as? TextEditor ?: continue 29 | 30 | val editor = fileEditor.editor 31 | val document = editor.document 32 | val markupModel = DocumentMarkupModel.forDocument(document, editor.project, false) 33 | 34 | markupModel as? MarkupModelEx ?: continue 35 | 36 | val dataSelector = BySettingsDataSelector( 37 | ApplicationManager.getApplication().getService(SettingsService::class.java).state 38 | ) 39 | 40 | SettingsChangeDispatcher.subscribe(dataSelector) 41 | 42 | val markupModelListener = 43 | ErrorMarkupModelListener(EditorModelImpl(editor), dataSelector, dataSelector, editor) 44 | 45 | listeners.add(Pair(markupModel, markupModelListener)) 46 | 47 | markupModel.addMarkupModelListener(fileEditor, markupModelListener) 48 | } 49 | } 50 | 51 | companion object { 52 | 53 | private val listeners: MutableList> = mutableListOf() 54 | 55 | fun updateAllListeners() { 56 | listeners.forEach { (model, listener) -> 57 | model.allHighlighters 58 | .filterIsInstance(RangeHighlighterEx::class.java) 59 | .forEach { 60 | listener.attributesChanged(it, true, true) 61 | } 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/editormodel/EditorModelImpl.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.editormodel 2 | 3 | import com.intellij.openapi.Disposable 4 | import com.intellij.openapi.editor.Editor 5 | import com.intellij.openapi.editor.EditorCustomElementRenderer 6 | import com.intellij.openapi.editor.markup.GutterIconRenderer 7 | import com.intellij.openapi.editor.markup.TextAttributes 8 | import com.ioannuwu.inline2.pluginlogic.EditorModel 9 | import com.ioannuwu.inline2.pluginlogic.utils.makeDisposable 10 | import java.awt.Color 11 | import javax.swing.Icon 12 | 13 | class EditorModelImpl(private val editor: Editor) : EditorModel { 14 | 15 | override fun addAfterLineEndElement( 16 | offset: Int, 17 | renderer: EditorCustomElementRenderer 18 | ): Disposable { 19 | return try { 20 | editor.inlayModel.addAfterLineEndElement(offset, true, renderer)!! 21 | } catch (e: IndexOutOfBoundsException) { 22 | EmptyDisposable 23 | } 24 | } 25 | 26 | override fun addUnderLineElement( 27 | offset: Int, 28 | renderer: EditorCustomElementRenderer 29 | ): Disposable { 30 | 31 | return try { 32 | editor.inlayModel.addBlockElement(offset, true, false, 9, renderer)!! 33 | } catch (e: IndexOutOfBoundsException) { 34 | EmptyDisposable 35 | } 36 | 37 | } 38 | 39 | override fun addLineHighlighter(offset: Int, backgroundColor: Color): Disposable { 40 | 41 | return try { 42 | val lineNumber = editor.document.getLineNumber(offset) 43 | 44 | val highlighter = 45 | editor.markupModel.addLineHighlighter(lineNumber, 9, BackgroundAttributes(backgroundColor)) 46 | 47 | highlighter.makeDisposable { it.dispose() } 48 | 49 | } catch (e: IndexOutOfBoundsException) { 50 | EmptyDisposable 51 | } 52 | } 53 | 54 | override fun addGutterIcon(offset: Int, icon: Icon): Disposable { 55 | 56 | return try { 57 | val lineNumber = editor.document.getLineNumber(offset) 58 | 59 | val highlighter = editor.markupModel.addLineHighlighter( 60 | lineNumber, 10, 61 | TextAttributes(null, null, null, null, 0) 62 | ) 63 | 64 | highlighter.gutterIconRenderer = object : GutterIconRenderer() { 65 | override fun equals(other: Any?): Boolean = (other as? GutterIconRenderer)?.icon == icon 66 | override fun hashCode(): Int = icon.hashCode() 67 | override fun getIcon(): Icon = icon 68 | } 69 | 70 | highlighter.makeDisposable { it.dispose() } 71 | 72 | } catch (e: IndexOutOfBoundsException) { 73 | EmptyDisposable 74 | } 75 | } 76 | } 77 | 78 | private object EmptyDisposable : Disposable { 79 | override fun dispose() = Unit 80 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/element/DefaultTextRenderer.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.element 2 | 3 | import com.intellij.openapi.editor.EditorCustomElementRenderer 4 | import com.intellij.openapi.editor.Inlay 5 | import com.intellij.openapi.editor.markup.TextAttributes 6 | import com.ioannuwu.inline.data.EffectType 7 | import com.ioannuwu.inline2.pluginlogic.render.ElementMetrics 8 | import com.ioannuwu.inline2.pluginlogic.render.metrics.otherdata.EffectTypeSelector 9 | import com.ioannuwu.inline2.pluginlogic.render.metrics.otherdata.NumberOfWhitespaces 10 | import java.awt.Graphics 11 | import java.awt.Rectangle 12 | 13 | class DefaultTextRenderer( 14 | private val renderMetrics: ElementMetrics, 15 | private val effectTypeSelector: () -> EffectType, 16 | ) : EditorCustomElementRenderer { 17 | 18 | override fun calcWidthInPixels(inlay: Inlay<*>): Int = 19 | renderMetrics.stringWidth(renderMetrics.text() + renderMetrics.charWidth() * 2) 20 | 21 | override fun paint( 22 | inlay: Inlay<*>, 23 | g: Graphics, 24 | targetRegion: Rectangle, 25 | textAttributes: TextAttributes 26 | ) { 27 | val text = renderMetrics.text() 28 | 29 | if (text.isBlank()) return 30 | if (renderMetrics.textColor() == null) return 31 | 32 | if (renderMetrics.effectColor() != null) { 33 | 34 | when (effectTypeSelector()) { 35 | EffectType.NONE -> Unit 36 | 37 | EffectType.BOX -> with(g) { 38 | color = renderMetrics.effectColor() 39 | font = renderMetrics.font() 40 | 41 | drawRoundRect( 42 | targetRegion.x - renderMetrics.lineHeight() / 20 * 3, 43 | targetRegion.y + renderMetrics.lineHeight() / 20, 44 | targetRegion.width - renderMetrics.lineHeight() / 20 * 2, 45 | targetRegion.height - renderMetrics.lineHeight() / 20 * 2, 46 | renderMetrics.lineHeight() / 6, 47 | renderMetrics.lineHeight() / 6, 48 | ) 49 | } 50 | 51 | EffectType.SHADOW -> with(g) { 52 | 53 | color = renderMetrics.effectColor() 54 | font = renderMetrics.font() 55 | 56 | drawString( 57 | text, 58 | (targetRegion.x + renderMetrics.lineHeight() * 0.1).toInt(), 59 | (targetRegion.y + renderMetrics.ascent() + renderMetrics.lineHeight() * 0.05).toInt(), 60 | ) 61 | } 62 | } 63 | } 64 | 65 | with(g) { 66 | color = renderMetrics.textColor() 67 | font = renderMetrics.font() 68 | 69 | drawString( 70 | text, 71 | targetRegion.x, 72 | targetRegion.y + renderMetrics.ascent(), 73 | ) 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /src/main/java/com/ioannuwu/inline/ui/settingscomponent/components/fontselectioncomponent/FontSelectionComponent.java: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.ui.settingscomponent.components.fontselectioncomponent; 2 | 3 | import com.intellij.ui.ContextHelpLabel; 4 | import com.intellij.ui.components.JBLabel; 5 | import com.intellij.ui.components.JBTextField; 6 | import com.intellij.util.ui.FormBuilder; 7 | import com.ioannuwu.inline.data.FontSettingsState; 8 | import com.ioannuwu.inline.ui.settingscomponent.Component; 9 | import com.ioannuwu.inline2.settings.data.MutableState; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | import javax.swing.*; 13 | import java.awt.*; 14 | 15 | public class FontSelectionComponent implements Component, MutableState { 16 | 17 | private final FontListComboBox comboBoxElement; 18 | 19 | private final JBTextField textField; 20 | 21 | public FontSelectionComponent(@Nullable String textSample, String selectedFontName, Font[] allAvailableFonts) { 22 | 23 | textField = new JBTextField(textSample); 24 | 25 | comboBoxElement = new FontListComboBox(allAvailableFonts); 26 | comboBoxElement.retainOnlySupporting(textSample); 27 | 28 | comboBoxElement.setSelectedFont(selectedFontName); 29 | 30 | textField.addActionListener(listener -> comboBoxElement.retainOnlySupporting(listener.getActionCommand())); 31 | } 32 | 33 | @Override 34 | public void addToBuilder(FormBuilder formBuilder) { 35 | JPanel panel = new JPanel(); 36 | panel.add(new JBLabel("Hints font")); 37 | panel.add(textField); 38 | panel.add(ContextHelpLabel.create(""" 39 |

PRESS ENTER TO UPDATE LIST OF FONTS AFTER ENTERING CHARACTERS

40 |

Input character from your language and press Enter to update list of fonts that support this character (language)

41 |

Note: default editor font (JetBrains Mono) cannot display Chinese (and possible other) characters for some reason. If you are seeing question marks instead of some characters, input them in sample box, press Enter to update list of fonts and select font supporting your language

""")); 42 | panel.add(new JBLabel("")); 43 | panel.add(comboBoxElement); 44 | formBuilder.addLabeledComponent(panel, new JLabel()); 45 | } 46 | 47 | @Override 48 | public FontSettingsState getState() { 49 | final var fontAndExample = comboBoxElement.getSelectedFontAndTextSample(); 50 | return new FontSettingsState( 51 | fontAndExample.first.getFontName(), 52 | fontAndExample.second); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/element/UnderLineTextRenderer.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.element 2 | 3 | import com.intellij.openapi.editor.Editor 4 | import com.intellij.openapi.editor.EditorCustomElementRenderer 5 | import com.intellij.openapi.editor.Inlay 6 | import com.intellij.openapi.editor.markup.RangeHighlighter 7 | import com.intellij.openapi.editor.markup.TextAttributes 8 | import com.ioannuwu.inline.data.EffectType 9 | import com.ioannuwu.inline2.pluginlogic.render.ElementMetrics 10 | import java.awt.Graphics 11 | import java.awt.Rectangle 12 | 13 | class UnderLineTextRenderer( 14 | private val renderMetrics: ElementMetrics, 15 | private val highlighter: RangeHighlighter, 16 | private val editor: Editor, 17 | private val effectTypeSelector: () -> EffectType, 18 | ) : EditorCustomElementRenderer { 19 | 20 | override fun calcWidthInPixels(inlay: Inlay<*>): Int = 21 | renderMetrics.stringWidth(renderMetrics.text() + renderMetrics.charWidth() * 2) 22 | 23 | override fun paint( 24 | inlay: Inlay<*>, 25 | g: Graphics, 26 | targetRegion: Rectangle, 27 | textAttributes: TextAttributes 28 | ) { 29 | val text = "^ " + renderMetrics.text() 30 | 31 | if (text.isBlank()) return 32 | if (renderMetrics.textColor() == null) return 33 | 34 | val line = try { 35 | editor.document.getLineNumber(highlighter.startOffset) 36 | } catch (e: IndexOutOfBoundsException) { 37 | editor.document.lineCount 38 | } 39 | val offsetFromLineStart = highlighter.startOffset - editor.document.getLineStartOffset(line) 40 | 41 | val editorCharWidth = editor.component.getFontMetrics( 42 | editor.component.font.deriveFont(editor.colorsScheme.editorFontSize.toFloat()) 43 | ).stringWidth("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").toFloat() / 28 44 | 45 | if (renderMetrics.effectColor() != null) { 46 | 47 | when (effectTypeSelector()) { 48 | EffectType.NONE -> Unit 49 | EffectType.BOX -> Unit // TODO 50 | EffectType.SHADOW -> with(g) { 51 | 52 | color = renderMetrics.effectColor() 53 | font = renderMetrics.font() 54 | 55 | drawString( 56 | text, 57 | (targetRegion.x + offsetFromLineStart * editorCharWidth 58 | + renderMetrics.lineHeight() * 0.1).toInt(), 59 | (targetRegion.y + renderMetrics.ascent() + renderMetrics.lineHeight() * 0.05).toInt(), 60 | ) 61 | } 62 | } 63 | } 64 | 65 | with(g) { 66 | color = renderMetrics.textColor() 67 | font = renderMetrics.font() 68 | 69 | drawString( 70 | text, 71 | (targetRegion.x + offsetFromLineStart * editorCharWidth).toInt(), 72 | targetRegion.y + renderMetrics.ascent(), 73 | ) 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /src/main/java/com/ioannuwu/inline/data/SeverityLevelState.java: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.data; 2 | 3 | import com.intellij.util.xmlb.annotations.Attribute; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.awt.*; 7 | import java.io.Serializable; 8 | import java.util.Objects; 9 | 10 | public class SeverityLevelState implements Serializable { 11 | 12 | public SeverityLevelState() {} 13 | 14 | public SeverityLevelState(boolean showGutterIcon, boolean showText, boolean showBackground, boolean showEffect, 15 | @NotNull Color textColor, @NotNull Color backgroundColor, @NotNull Color effectColor) { 16 | this.showGutterIcon = showGutterIcon; 17 | this.showText = showText; 18 | this.showBackground = showBackground; 19 | this.showEffect = showEffect; 20 | this.textColor = textColor; 21 | this.backgroundColor = backgroundColor; 22 | this.effectColor = effectColor; 23 | } 24 | 25 | public SeverityLevelState(@NotNull Color textColor, @NotNull Color backgroundColor, @NotNull Color effectColor) { 26 | this(true, true, true, true, textColor, backgroundColor, effectColor); 27 | } 28 | 29 | public SeverityLevelState(@NotNull SeverityLevelState other) { 30 | this.showGutterIcon = other.showGutterIcon; 31 | this.showText = other.showText; 32 | this.showBackground = other.showBackground; 33 | this.showEffect = other.showEffect; 34 | this.textColor = other.textColor; 35 | this.backgroundColor = other.backgroundColor; 36 | this.effectColor = other.effectColor; 37 | } 38 | 39 | public boolean showGutterIcon = true; 40 | 41 | public boolean showText = true; 42 | public boolean showBackground = true; 43 | public boolean showEffect = true; 44 | @Attribute(converter = MyColorConverter.class) 45 | public @NotNull Color textColor = Color.RED; 46 | @Attribute(converter = MyColorConverter.class) 47 | public @NotNull Color backgroundColor = Color.GREEN; 48 | @Attribute(converter = MyColorConverter.class) 49 | public @NotNull Color effectColor = Color.ORANGE; 50 | 51 | @Override 52 | public boolean equals(Object o) { 53 | if (this == o) return true; 54 | if (o == null || getClass() != o.getClass()) return false; 55 | SeverityLevelState that = (SeverityLevelState) o; 56 | return showGutterIcon == that.showGutterIcon && showText == that.showText && showBackground == that.showBackground && showEffect == that.showEffect && Objects.equals(textColor, that.textColor) && Objects.equals(backgroundColor, that.backgroundColor) && Objects.equals(effectColor, that.effectColor); 57 | } 58 | 59 | @Override 60 | public int hashCode() { 61 | return Objects.hash(showGutterIcon, showText, showBackground, showEffect, textColor, backgroundColor, effectColor); 62 | } 63 | 64 | @Override 65 | public String toString() { 66 | return "SeverityLevelState{" + 67 | "showGutterIcon=" + showGutterIcon + 68 | ", showText=" + showText + 69 | ", showBackground=" + showBackground + 70 | ", showEffect=" + showEffect + 71 | ", textColor=" + textColor + 72 | ", backgroundColor=" + backgroundColor + 73 | ", effectColor=" + effectColor + 74 | '}'; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/settings/data/SettingsState.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.settings.data 2 | 3 | import com.ioannuwu.inline.data.* 4 | import java.util.* 5 | 6 | class SettingsState { 7 | 8 | var numberOfWhitespaces: Int = DefaultSettings.NUMBER_OF_WHITESPACES 9 | 10 | var effectType: EffectType = DefaultSettings.EFFECT_TYPE 11 | 12 | var maxErrorsPerLine: Int = DefaultSettings.MAX_ERRORS_PER_LINE 13 | 14 | var font: FontSettingsState = DefaultSettings.FONT 15 | 16 | var textStyle: TextStyle = DefaultSettings.TEXT_STYLE 17 | var oneGutterMode: Boolean = DefaultSettings.ONE_GUTTER_MODE 18 | 19 | var error: SeverityLevelState = DefaultSettings.ERROR 20 | var warning: SeverityLevelState = DefaultSettings.WARNING 21 | var weakWarning: SeverityLevelState = DefaultSettings.WEAK_WARNING 22 | var information: SeverityLevelState = DefaultSettings.INFORMATION 23 | var serverError: SeverityLevelState = DefaultSettings.SERVER_ERROR 24 | var otherError: SeverityLevelState = DefaultSettings.OTHER_ERROR 25 | 26 | var ignoreList: Array = DefaultSettings.IGNORE_LIST 27 | 28 | fun copy(): SettingsState { 29 | val state = SettingsState() 30 | state.numberOfWhitespaces = numberOfWhitespaces 31 | state.effectType = effectType 32 | state.maxErrorsPerLine = maxErrorsPerLine 33 | state.font = font 34 | state.textStyle = textStyle 35 | state.oneGutterMode = oneGutterMode 36 | state.error = error 37 | state.warning = warning 38 | state.weakWarning = weakWarning 39 | state.information = information 40 | state.serverError = serverError 41 | state.otherError = otherError 42 | state.ignoreList = ignoreList 43 | return state 44 | } 45 | 46 | override fun equals(other: Any?): Boolean { 47 | if (this === other) return true 48 | if (other == null || javaClass != other.javaClass) return false 49 | val that = other as SettingsState 50 | return numberOfWhitespaces == that.numberOfWhitespaces && maxErrorsPerLine == that.maxErrorsPerLine 51 | && error == that.error && warning == that.warning && weakWarning == that.weakWarning 52 | && information == that.information && serverError == that.serverError 53 | && otherError == that.otherError 54 | && ignoreList.contentEquals(that.ignoreList) 55 | && effectType == that.effectType && font == that.font && textStyle == that.textStyle 56 | && oneGutterMode == that.oneGutterMode 57 | } 58 | 59 | override fun hashCode(): Int { 60 | var result = Objects.hash( 61 | numberOfWhitespaces, maxErrorsPerLine, error, warning, weakWarning, 62 | information, serverError, otherError, effectType, font, textStyle, oneGutterMode 63 | ) 64 | result = 31 * result + ignoreList.contentHashCode() 65 | return result 66 | } 67 | 68 | override fun toString(): String = """ 69 | SettingsState{ 70 | numberOfWhitespaces=$numberOfWhitespaces 71 | effectType=$effectType 72 | maxErrorsPerLine=$maxErrorsPerLine 73 | font=$font 74 | textStyle=$textStyle 75 | oneGutterMode=$oneGutterMode 76 | error=$error 77 | warning=$warning 78 | weakWarning=$weakWarning 79 | information=$information 80 | serverError=$serverError 81 | otherError=$otherError 82 | ignoreList=${ignoreList.contentToString()}} 83 | """.trimIndent() 84 | } -------------------------------------------------------------------------------- /src/main/java/com/ioannuwu/inline/ui/settingscomponent/components/SeverityLevel.java: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline.ui.settingscomponent.components; 2 | 3 | import com.intellij.ui.ColorPanel; 4 | import com.intellij.ui.ContextHelpLabel; 5 | import com.intellij.ui.components.JBCheckBox; 6 | import com.intellij.ui.components.JBLabel; 7 | import com.intellij.util.ui.FormBuilder; 8 | import com.ioannuwu.inline.data.SeverityLevelState; 9 | import com.ioannuwu.inline.ui.settingscomponent.Component; 10 | import com.ioannuwu.inline2.settings.data.MutableState; 11 | 12 | import javax.swing.*; 13 | 14 | public class SeverityLevel implements Component, MutableState { 15 | 16 | private static final String PREFIX = " > "; 17 | 18 | private final ColorPanel textColorPanel; 19 | private final ColorPanel backgroundColorPanel; 20 | private final ColorPanel effectColorPanel; 21 | 22 | private final JBCheckBox showTextCheckBox; 23 | private final JBCheckBox showBackgroundCheckBox; 24 | private final JBCheckBox showEffectCheckBox; 25 | 26 | private final JBCheckBox showGutterIcon; 27 | 28 | private final String name; 29 | private final String helpDescription; 30 | 31 | public SeverityLevel(SeverityLevelState data, String name, String helpDescription) { 32 | this.name = name; 33 | this.helpDescription = helpDescription; 34 | 35 | this.showGutterIcon = new JBCheckBox("Show gutter icon", data.showGutterIcon); 36 | 37 | this.textColorPanel = new ColorPanel(); 38 | this.textColorPanel.setSelectedColor(data.textColor); 39 | this.backgroundColorPanel = new ColorPanel(); 40 | this.backgroundColorPanel.setSelectedColor(data.backgroundColor); 41 | this.effectColorPanel = new ColorPanel(); 42 | this.effectColorPanel.setSelectedColor(data.effectColor); 43 | 44 | this.showTextCheckBox = new JBCheckBox("Text color", data.showText); 45 | this.showBackgroundCheckBox = new JBCheckBox("Background color", data.showBackground); 46 | this.showEffectCheckBox = new JBCheckBox("Effect color", data.showEffect); 47 | } 48 | 49 | @Override 50 | public void addToBuilder(FormBuilder formBuilder) { 51 | JPanel panel = new JPanel(); 52 | ContextHelpLabel contextHelpLabel = ContextHelpLabel.create(helpDescription); 53 | JLabel label = new JBLabel(PREFIX + name + " settings: "); 54 | panel.add(label); 55 | panel.add(contextHelpLabel); 56 | 57 | JPanel panel1 = new JPanel(); 58 | panel1.add(showGutterIcon); 59 | 60 | JPanel panel2 = new JPanel(); 61 | panel2.add(showTextCheckBox); 62 | panel2.add(textColorPanel); 63 | 64 | JPanel panel3 = new JPanel(); 65 | panel3.add(showBackgroundCheckBox); 66 | panel3.add(backgroundColorPanel); 67 | 68 | JPanel panel4 = new JPanel(); 69 | panel4.add(showEffectCheckBox); 70 | panel4.add(effectColorPanel); 71 | 72 | formBuilder 73 | .addLabeledComponent(panel, new JLabel()) 74 | .addLabeledComponent(panel1, new JLabel()) 75 | .addLabeledComponent(panel2, new JLabel()) 76 | .addLabeledComponent(panel3, new JLabel()) 77 | .addLabeledComponent(panel4, new JLabel()); 78 | } 79 | 80 | @Override 81 | public SeverityLevelState getState() { 82 | return new SeverityLevelState( 83 | showGutterIcon.isSelected(), 84 | showTextCheckBox.isSelected(), 85 | showBackgroundCheckBox.isSelected(), 86 | showEffectCheckBox.isSelected(), 87 | textColorPanel.getSelectedColor(), 88 | backgroundColorPanel.getSelectedColor(), 89 | effectColorPanel.getSelectedColor() 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # InLine 2 | [![Version](https://img.shields.io/jetbrains/plugin/v/21051-inline.svg?color=aa3030)](https://plugins.jetbrains.com/plugin/21051-inline) 3 | [![Downloads](https://img.shields.io/jetbrains/plugin/d/21051-inline.svg?color=aa3090)](https://plugins.jetbrains.com/plugin/21051-inline) 4 | [![Rating](https://img.shields.io/jetbrains/plugin/r/rating/21051-inline?color=30aa30)](https://plugins.jetbrains.com/plugin/21051-inline) 5 | 6 | 7 | # !! Deprecation warning !! 8 | I'm not interested in developing `InLine` and using `IntelliJ IDEA` anymore since JetBrains dropped support for [Free Rust Plugin](https://plugins.jetbrains.com/plugin/8182--deprecated-rust) 9 | and started focusing on `new UI` that is IMO ugly. I don't like the direction this IDE is going and decided to switch away. 10 | 11 | You should consider using alternative plugins: 12 | * [Inspection Lens](https://plugins.jetbrains.com/plugin/19678-inspection-lens) 13 | * [Inline Problems](https://plugins.jetbrains.com/plugin/20789-inlineproblems) 14 | 15 | You can consider using alternative IDE: 16 | * [VSCode](https://code.visualstudio.com/) - feature & plugin Rich IDE with not great UI (By Microsoft). 17 | * [Lapce](https://lapce.dev/) - Middleground between VIM & VSCode with fast user-friendly UI similar to __old__ `IntelliJ IDEA` UI, modal editing and built-in LSP & Inline errors. Lacks certain features, __But is written in Rust__ so you can contribute. 18 | 19 | 20 | --- 21 | ### InLine is highly customizable plugin that shows errors and hints inline. 22 | #### Also supports gutter icons, colorful background and special effects. 23 | 24 | * Errors are filtered on the line by priority 25 | * Supports different fonts and languages 26 | * Supports different hints styles __After Line, Rust Style__ 27 | 28 | Settings > Appearance & Behaviour > ⚙ InLine 29 | 30 | ### Shortcuts: 31 | * Hide/Show all errors __alt K__ 32 | * Change text style (After/Underline) __alt L__ 33 | 34 | ### In plugin settings you can: 35 | * Show or hide specific __level of errors__ - _error, warning, weak warning, information_ 36 | * Change __text colors & text visibility__ for each error level 37 | * Change __text style__ to make it appear _after line end_ or _under line_ 38 | * Change __background colors__ & __background visibility__ for each error level 39 | * Change __gutter icons visibility__ for each error level and select number of gutter icons 40 | * Apply additional effect 41 | * Change font of the hints 42 | * __Ignore__ some errors by description 43 | 44 | ![](https://raw.githubusercontent.com/IoaNNUwU/InLine/main/media/example.png) 45 | 46 | ### Choices 47 | 48 | There are already multiple plugins like this inspired by VSCode ErrorLens extension - check Inspired by section. 49 | I've decided to make my own because I want to have some extra features for code __writing and demonstration__ purposes such as: 50 | * Fill __background of whole line__ 51 | * Show __error icons__ in gutter area for different kinds of errors 52 | * Turn __hint text off__ but make __background stay__ 53 | 54 | ### Future 55 | 56 | In the future I am planning to add more customization such as: 57 | * New effects 58 | * Error description changing 59 | * Additional ways to filter errors 60 | 61 | ### Contribution 62 | 63 | This plugin is open source. You can report bugs and contribute at [GitHub](https://github.com/IoaNNUwU/InLine). 64 | 65 | 66 | ### Change notes: 67 | 68 | * __1.3.0__ - Add hide & change text type actions & bug fixes 69 | * __1.2.0__ - Update to latest IntelliJ version (2023.2) 70 | * __1.1.1__ - Update to latest IntelliJ version 71 | * __1.1.0__ - Add __Rust Style Errors__ that are shown under line similar to __Rust compiler__ messages 72 | * __1.0.1__ - Bug fixes & Font change support (Fix Chinese characters being shown as `?`) 73 | * __1.0__ - Release 74 | 75 | -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/render/BySettingsDataSelector.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic.render 2 | 3 | import com.intellij.codeInsight.daemon.impl.HighlightInfo 4 | import com.intellij.icons.AllIcons 5 | import com.intellij.lang.annotation.HighlightSeverity 6 | import com.intellij.openapi.editor.markup.RangeHighlighter 7 | import com.ioannuwu.inline.data.DefaultSettings 8 | import com.ioannuwu.inline.data.EffectType 9 | import com.ioannuwu.inline.data.SeverityLevelState 10 | import com.ioannuwu.inline.data.TextStyle 11 | import com.ioannuwu.inline2.pluginlogic.render.metrics.OtherData 12 | import com.ioannuwu.inline2.pluginlogic.render.metrics.RenderData 13 | import com.ioannuwu.inline2.settings.data.SettingsState 14 | import com.ioannuwu.inline2.settings.event.SettingsChangeListener 15 | 16 | import java.awt.Color 17 | import java.awt.Font 18 | import java.awt.GraphicsEnvironment 19 | import javax.swing.Icon 20 | 21 | class BySettingsDataSelector( 22 | initialState: SettingsState 23 | ): RenderDataSelector, SettingsChangeListener, OtherDataSelector { 24 | 25 | init { 26 | onSettingsChange(initialState) 27 | } 28 | 29 | private lateinit var currentFont: Font 30 | private lateinit var currentSettingsState: SettingsState 31 | 32 | override operator fun invoke(highlighter: RangeHighlighter): RenderData? { 33 | 34 | val info: HighlightInfo = highlighter.errorStripeTooltip as? HighlightInfo ?: return null 35 | if (info.description.isNullOrBlank()) return null 36 | 37 | val description: String = info.description 38 | val state = currentSettingsState 39 | 40 | if (state.ignoreList.any { description.contains(it) }) return null 41 | 42 | val severity: SeverityLevelState = when (info.severity) { 43 | HighlightSeverity.ERROR -> state.error 44 | HighlightSeverity.WARNING -> state.warning 45 | HighlightSeverity.WEAK_WARNING -> state.weakWarning 46 | HighlightSeverity.INFORMATION -> state.information 47 | HighlightSeverity.GENERIC_SERVER_ERROR_OR_WARNING -> state.serverError 48 | else -> state.otherError 49 | } 50 | 51 | val textColor: Color? = if (severity.showText) severity.textColor else null 52 | val backgroundColor: Color? = if (severity.showBackground) severity.backgroundColor else null 53 | val effectColor: Color? = if (severity.showEffect) severity.effectColor else null 54 | 55 | val tmpIcon: Icon = when (info.severity) { 56 | HighlightSeverity.ERROR -> DefaultSettings.Icons.ERROR 57 | HighlightSeverity.WARNING -> DefaultSettings.Icons.WARNING 58 | HighlightSeverity.WEAK_WARNING -> DefaultSettings.Icons.WEAK_WARNING 59 | HighlightSeverity.INFORMATION -> DefaultSettings.Icons.INFORMATION 60 | HighlightSeverity.GENERIC_SERVER_ERROR_OR_WARNING -> DefaultSettings.Icons.SERVER_ERROR 61 | else -> DefaultSettings.Icons.OTHER_ERROR 62 | } 63 | 64 | val gutterIcon: Icon? = if (severity.showGutterIcon) tmpIcon else null 65 | 66 | return object : RenderData { 67 | override fun textColor(): Color? = textColor 68 | override fun backgroundColor(): Color? = backgroundColor 69 | override fun effectColor(): Color? = effectColor 70 | override fun text(): String = description 71 | override fun font(): Font = currentFont 72 | override fun gutterIcon(): Icon? = gutterIcon 73 | } 74 | } 75 | 76 | override fun isValid(highlighter: RangeHighlighter): Boolean { 77 | 78 | val info: HighlightInfo = highlighter.errorStripeTooltip as? HighlightInfo ?: return false 79 | if (info.description.isNullOrBlank()) return false 80 | 81 | val description: String = info.description 82 | val state = currentSettingsState 83 | 84 | return !state.ignoreList.any { description.contains(it) } 85 | } 86 | 87 | override fun selectOtherData(highlighter: RangeHighlighter): OtherData { 88 | 89 | val state = currentSettingsState 90 | 91 | return object : OtherData { 92 | override fun effectType(): EffectType = state.effectType 93 | override fun maxErrorsPerLine(): Int = state.maxErrorsPerLine 94 | override fun numberOfWhitespaces(): Int = state.numberOfWhitespaces 95 | override fun showOnlyOneGutter(): Boolean = state.oneGutterMode 96 | override fun textStyle(): TextStyle = state.textStyle 97 | } 98 | } 99 | 100 | override fun onSettingsChange(newSettingsState: SettingsState) { 101 | 102 | currentSettingsState = newSettingsState 103 | 104 | val allFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().allFonts 105 | 106 | val font: Font = allFonts.asSequence() 107 | .find { it.name == newSettingsState.font.fontName } 108 | ?: allFonts.find { it.name.contains("JetBrains", ignoreCase = false) } 109 | ?: allFonts.find { it.name.contains("Mono", ignoreCase = true) } 110 | ?: allFonts.find { it.name.contains("plain", ignoreCase = true) } 111 | ?: allFonts[0] 112 | 113 | currentFont = font 114 | } 115 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 38 | 56 | 73 | 90 | 107 | 110 | 114 | 118 | 119 | 131 | 141 | 142 | -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/settings/ui/SettingsUIViewImpl.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.settings.ui 2 | 3 | import com.intellij.util.ui.FormBuilder 4 | import com.ioannuwu.inline.ui.settingscomponent.components.* 5 | import com.ioannuwu.inline.ui.settingscomponent.components.fontselectioncomponent.FontSelectionComponent 6 | import com.ioannuwu.inline2.settings.data.SettingsState 7 | import java.awt.GraphicsEnvironment 8 | import javax.swing.JComponent 9 | 10 | class SettingsUIViewImpl(initialState: SettingsState) : SettingsUIView { 11 | 12 | private var settingsState = initialState 13 | 14 | override val state: SettingsState 15 | get() { 16 | val state = SettingsState() 17 | 18 | with(state) { 19 | numberOfWhitespaces = numberOfWhitespacesComponent.state 20 | 21 | maxErrorsPerLine = maxErrorsPerLineComponent.state 22 | effectType = effectTypeComponent.state 23 | font = fontSelectionComponent.state 24 | oneGutterMode = oneGutterModeComponent.state 25 | textStyle = textStyleSelectionComponent.state 26 | 27 | error = errorComponent.state 28 | warning = warningComponent.state 29 | weakWarning = weakWarningComponent.state 30 | information = informationComponent.state 31 | serverError = serverErrorComponent.state 32 | otherError = otherErrorComponent.state 33 | 34 | ignoreList = ignoreListComponent.state 35 | } 36 | 37 | return state 38 | } 39 | 40 | 41 | private lateinit var numberOfWhitespacesComponent: NumberOfWhitespacesComponent 42 | 43 | private lateinit var maxErrorsPerLineComponent: MaxErrorsPerLineComponent 44 | 45 | private lateinit var effectTypeComponent: EffectTypeComponent 46 | 47 | private lateinit var fontSelectionComponent: FontSelectionComponent 48 | 49 | private lateinit var oneGutterModeComponent: OneGutterModeComponent 50 | private lateinit var textStyleSelectionComponent: TextStyleSelectionComponent 51 | 52 | private lateinit var errorComponent: SeverityLevel 53 | private lateinit var warningComponent: SeverityLevel 54 | private lateinit var weakWarningComponent: SeverityLevel 55 | private lateinit var informationComponent: SeverityLevel 56 | private lateinit var serverErrorComponent: SeverityLevel 57 | private lateinit var otherErrorComponent: SeverityLevel 58 | 59 | private lateinit var ignoreListComponent: IgnoreListComponent 60 | 61 | 62 | override fun createComponent(): JComponent { 63 | numberOfWhitespacesComponent = NumberOfWhitespacesComponent(settingsState.numberOfWhitespaces) 64 | 65 | effectTypeComponent = EffectTypeComponent(settingsState.effectType) 66 | 67 | maxErrorsPerLineComponent = MaxErrorsPerLineComponent(settingsState.maxErrorsPerLine) 68 | 69 | fontSelectionComponent = FontSelectionComponent( 70 | settingsState.font.textSample, 71 | settingsState.font.fontName, 72 | GraphicsEnvironment.getLocalGraphicsEnvironment().allFonts 73 | ) 74 | 75 | oneGutterModeComponent = OneGutterModeComponent(settingsState.oneGutterMode) 76 | textStyleSelectionComponent = TextStyleSelectionComponent(settingsState.textStyle) 77 | 78 | errorComponent = SeverityLevel( 79 | settingsState.error, 80 | "Error", "Compilation errors" 81 | ) 82 | warningComponent = SeverityLevel( 83 | settingsState.warning, 84 | "Warning", "Logic errors and big improvements" 85 | ) 86 | weakWarningComponent = SeverityLevel( 87 | settingsState.weakWarning, 88 | "Weak warning", "Small improvements" 89 | ) 90 | informationComponent = SeverityLevel( 91 | settingsState.information, 92 | "Information", "Small information messages" 93 | ) 94 | serverErrorComponent = SeverityLevel( 95 | settingsState.serverError, 96 | "Server error", "Connection error" 97 | ) 98 | otherErrorComponent = SeverityLevel( 99 | settingsState.otherError, 100 | "Other error", "Small hints" 101 | ) 102 | 103 | ignoreListComponent = IgnoreListComponent(settingsState.ignoreList) 104 | 105 | val formBuilder = FormBuilder.createFormBuilder() 106 | 107 | formBuilder.addSeparator() 108 | fontSelectionComponent.addToBuilder(formBuilder) 109 | formBuilder.addSeparator() 110 | textStyleSelectionComponent.addToBuilder(formBuilder) 111 | effectTypeComponent.addToBuilder(formBuilder) 112 | 113 | formBuilder.addSeparator() 114 | oneGutterModeComponent.addToBuilder(formBuilder) 115 | formBuilder.addSeparator() 116 | maxErrorsPerLineComponent.addToBuilder(formBuilder) 117 | numberOfWhitespacesComponent.addToBuilder(formBuilder) 118 | 119 | formBuilder.addSeparator() 120 | errorComponent.addToBuilder(formBuilder) 121 | formBuilder.addSeparator() 122 | warningComponent.addToBuilder(formBuilder) 123 | formBuilder.addSeparator() 124 | weakWarningComponent.addToBuilder(formBuilder) 125 | formBuilder.addSeparator() 126 | informationComponent.addToBuilder(formBuilder) 127 | formBuilder.addSeparator() 128 | serverErrorComponent.addToBuilder(formBuilder) 129 | formBuilder.addSeparator() 130 | otherErrorComponent.addToBuilder(formBuilder) 131 | formBuilder.addSeparator() 132 | 133 | ignoreListComponent.addToBuilder(formBuilder) 134 | formBuilder.addSeparator() 135 | 136 | return formBuilder.panel 137 | } 138 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/ioannuwu/inline2/pluginlogic/ErrorMarkupModelListener.kt: -------------------------------------------------------------------------------- 1 | package com.ioannuwu.inline2.pluginlogic 2 | 3 | import com.intellij.codeInsight.daemon.impl.HighlightInfo 4 | import com.intellij.openapi.Disposable 5 | import com.intellij.openapi.editor.Editor 6 | import com.intellij.openapi.editor.ex.RangeHighlighterEx 7 | import com.intellij.openapi.editor.impl.event.MarkupModelListener 8 | import com.intellij.openapi.util.Disposer 9 | import com.intellij.openapi.util.Key 10 | import com.ioannuwu.inline.data.EffectType 11 | import com.ioannuwu.inline.data.TextStyle 12 | import com.ioannuwu.inline2.pluginlogic.element.DefaultTextRenderer 13 | import com.ioannuwu.inline2.pluginlogic.element.UnderLineTextRenderer 14 | import com.ioannuwu.inline2.pluginlogic.render.ElementMetrics 15 | import com.ioannuwu.inline2.pluginlogic.render.OtherDataSelector 16 | import com.ioannuwu.inline2.pluginlogic.render.RenderDataElementMetrics 17 | import com.ioannuwu.inline2.pluginlogic.render.RenderDataSelector 18 | import com.ioannuwu.inline2.pluginlogic.render.metrics.OtherData 19 | import com.ioannuwu.inline2.pluginlogic.render.metrics.RenderData 20 | import com.ioannuwu.inline2.pluginlogic.utils.runOnEDT 21 | import com.ioannuwu.inline2.pluginlogic.utils.runOnEDTIgnoringIndexOutOfBounds 22 | import com.jetbrains.rd.util.ConcurrentHashMap 23 | 24 | class ErrorMarkupModelListener( 25 | private val model: EditorModel, 26 | private val renderDataSelector: RenderDataSelector, 27 | private val otherDataSelector: OtherDataSelector, 28 | private val editor: Editor 29 | ) : MarkupModelListener { 30 | 31 | private val map: MutableMap> = ConcurrentHashMap() 32 | 33 | override fun afterAdded(highlighter: RangeHighlighterEx) { 34 | 35 | val info = (highlighter.errorStripeTooltip as? HighlightInfo) ?: return 36 | 37 | val description = info.description ?: return 38 | if (description.isEmpty()) return 39 | if (map.containsKey(highlighter)) return 40 | 41 | val renderData: RenderData = renderDataSelector(highlighter) ?: return 42 | val otherData: OtherData = otherDataSelector.selectOtherData(highlighter) 43 | 44 | val metrics: ElementMetrics = RenderDataElementMetrics(renderData, editor) 45 | 46 | runOnEDTIgnoringIndexOutOfBounds { 47 | 48 | highlighter.putUserData(VALID_HIGHLIGHTER_MARKER, Unit) 49 | 50 | val disposables: MutableList = map[highlighter] ?: mutableListOf() 51 | 52 | if (renderData.textColor() != null) { 53 | 54 | when (otherData.textStyle()) { 55 | 56 | TextStyle.AFTER_LINE -> { 57 | 58 | val textStyleRenderer = DefaultTextRenderer(metrics) { otherData.effectType() } 59 | /* TODO 60 | val isThereValidHighlightersOnTheLine = editor.markupModel.allHighlighters 61 | .filter { 62 | highlighter.document.getLineNumber(it.startOffset) == 63 | highlighter.document.getLineNumber(highlighter.startOffset) 64 | }.any { it.getUserData(VALID_HIGHLIGHTER_MARKER) != null } 65 | */ 66 | disposables.add( 67 | model.addAfterLineEndElement( 68 | highlighter.startOffset, 69 | DefaultTextRenderer( 70 | metrics.whitespaces(otherData) 71 | ) { EffectType.NONE } 72 | ) 73 | ) 74 | 75 | disposables.add( 76 | model.addAfterLineEndElement( 77 | highlighter.startOffset, textStyleRenderer 78 | ) 79 | ) 80 | } 81 | 82 | TextStyle.UNDER_LINE -> { 83 | 84 | val textStyleRenderer = UnderLineTextRenderer( 85 | metrics, highlighter, editor, 86 | otherData::effectType 87 | ) 88 | 89 | disposables.add( 90 | model.addUnderLineElement( 91 | highlighter.startOffset, textStyleRenderer 92 | ) 93 | ) 94 | } 95 | } 96 | } 97 | 98 | if (renderData.backgroundColor() != null) 99 | disposables.add(model.addLineHighlighter(highlighter.startOffset, renderData.backgroundColor()!!)) 100 | 101 | if (renderData.gutterIcon() != null && !otherData.showOnlyOneGutter()) 102 | disposables.add(model.addGutterIcon(highlighter.startOffset, renderData.gutterIcon()!!)) 103 | 104 | map[highlighter] = disposables 105 | } 106 | } 107 | 108 | override fun beforeRemoved(highlighter: RangeHighlighterEx) { 109 | if (!map.containsKey(highlighter)) return 110 | 111 | runOnEDT { 112 | 113 | val afterLineElement = map.remove(highlighter)!! 114 | 115 | afterLineElement.forEach(Disposer::dispose) 116 | } 117 | } 118 | 119 | override fun attributesChanged( 120 | highlighter: RangeHighlighterEx, 121 | renderersChanged: Boolean, 122 | fontStyleOrColorChanged: Boolean 123 | ) { 124 | runOnEDT { 125 | 126 | beforeRemoved(highlighter) 127 | 128 | runOnEDT { 129 | afterAdded(highlighter) 130 | } 131 | } 132 | } 133 | } 134 | 135 | private val VALID_HIGHLIGHTER_MARKER: Key = Key("inline_valid_highlighter") -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | # Collect all arguments for the java command; 201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 202 | # shell script including quotes and variable substitutions, so put them in 203 | # double quotes to make sure that they get re-expanded; and 204 | # * put everything else in single quotes, so that it's not re-expanded. 205 | 206 | set -- \ 207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 208 | -classpath "$CLASSPATH" \ 209 | org.gradle.wrapper.GradleWrapperMain \ 210 | "$@" 211 | 212 | # Stop when "xargs" is not available. 213 | if ! command -v xargs >/dev/null 2>&1 214 | then 215 | die "xargs is not available" 216 | fi 217 | 218 | # Use "xargs" to parse quoted args. 219 | # 220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 221 | # 222 | # In Bash we could simply go: 223 | # 224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 225 | # set -- "${ARGS[@]}" "$@" 226 | # 227 | # but POSIX shell has neither arrays nor command substitution, so instead we 228 | # post-process each arg (as a line of input to sed) to backslash-escape any 229 | # character that might be a shell metacharacter, then use eval to reverse 230 | # that process (while maintaining the separation between arguments), and wrap 231 | # the whole thing up as a single "set" statement. 232 | # 233 | # This will of course break if any of these variables contains a newline or 234 | # an unmatched quote. 235 | # 236 | 237 | eval "set -- $( 238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 239 | xargs -n1 | 240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 241 | tr '\n' ' ' 242 | )" '"$@"' 243 | 244 | exec "$JAVACMD" "$@" 245 | --------------------------------------------------------------------------------