├── pub └── example.png ├── .gitignore ├── src ├── main │ ├── python │ │ ├── cour.ttf │ │ └── CharacterWeight.py │ ├── java │ │ └── net │ │ │ └── vektah │ │ │ └── codeglance │ │ │ ├── render │ │ │ ├── Utill.kt │ │ │ ├── TaskRunner.kt │ │ │ ├── Folds.kt │ │ │ ├── ScrollState.kt │ │ │ ├── CharacterWeight.kt │ │ │ └── Minimap.kt │ │ │ ├── concurrent │ │ │ └── DirtyLock.kt │ │ │ ├── config │ │ │ ├── ConfigService.kt │ │ │ ├── Config.kt │ │ │ ├── ConfigEntry.kt │ │ │ ├── ConfigForm.form │ │ │ └── ConfigForm.java │ │ │ ├── CodeGlancePlugin.kt │ │ │ ├── actions │ │ │ └── ShowHideAction.kt │ │ │ ├── Scrollbar.kt │ │ │ ├── EditorPanelInjector.kt │ │ │ └── GlancePanel.kt │ └── resources │ │ └── META-INF │ │ └── plugin.xml └── test │ └── java │ └── net │ └── vektah │ └── codeglance │ └── render │ ├── TaskRunnerTest.kt │ ├── ScrollStateTest.kt │ ├── CharacterWeightTest.kt │ ├── FoldsTest.kt │ ├── FakeFold.kt │ └── GlanceImageTest.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .editorconfig ├── circle.yml ├── LICENCE ├── README.md ├── gradlew.bat └── gradlew /pub/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vektah/CodeGlance/HEAD/pub/example.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.gradle 3 | /build 4 | /out 5 | /lib 6 | *.iml 7 | *.ipr 8 | *.iws 9 | -------------------------------------------------------------------------------- /src/main/python/cour.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vektah/CodeGlance/HEAD/src/main/python/cour.ttf -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ideaVersion = IC-14.1.4 2 | javaVersion = 1.6 3 | kotlinVersion = 1.0.5 4 | version = 1.5.4 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vektah/CodeGlance/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = lf 3 | insert_final_newline = true 4 | indent_style = space 5 | indent_size = 4 6 | 7 | [plugin.xml] 8 | indent_style = tab 9 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | test: 2 | post: 3 | - mkdir -p $CIRCLE_TEST_REPORTS/junit/ 4 | - find . -type f -regex ".*/build/test-results/test/.*xml" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \; 5 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/render/Utill.kt: -------------------------------------------------------------------------------- 1 | package net.vektah.codeglance.render 2 | 3 | fun clamp(v: Int, min: Int, max: Int): Int { 4 | if (v < min) return min 5 | if (v > max) return max 6 | return v 7 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Mar 11 13:49:45 AEDT 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-bin.zip 7 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/concurrent/DirtyLock.kt: -------------------------------------------------------------------------------- 1 | package net.vektah.codeglance.concurrent 2 | 3 | class DirtyLock { 4 | public var locked = false 5 | private set 6 | 7 | public var dirty = false 8 | private set 9 | 10 | public fun acquire() : Boolean { 11 | synchronized(this) { 12 | if (locked) { 13 | // Someone else already grabbed the lock, we are dirty now 14 | dirty = true 15 | return false 16 | } 17 | 18 | locked = true 19 | return true 20 | } 21 | } 22 | 23 | public fun release() { 24 | synchronized(this) { 25 | locked = false 26 | } 27 | } 28 | 29 | public fun clean() { 30 | synchronized(this) { 31 | dirty = false 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/config/ConfigService.kt: -------------------------------------------------------------------------------- 1 | package net.vektah.codeglance.config 2 | 3 | import com.intellij.openapi.components.* 4 | import com.intellij.util.xmlb.XmlSerializerUtil 5 | import java.lang.ref.WeakReference 6 | 7 | @State( 8 | name = "CodeGlance", 9 | storages = arrayOf( 10 | Storage(id = "other", file = StoragePathMacros.APP_CONFIG + "/CodeGlance.xml") 11 | ) 12 | ) 13 | class ConfigService : PersistentStateComponent { 14 | private val observers : MutableList Unit>> = arrayListOf() 15 | private val config = Config() 16 | 17 | override fun getState(): Config? = config 18 | public fun onChange(f :() -> Unit) = observers.add(WeakReference<() -> Unit>(f)) 19 | 20 | public fun notifyChange() { 21 | val it = observers.listIterator() 22 | while(it.hasNext()) { 23 | val f = it.next().get() 24 | 25 | if (f == null) { 26 | it.remove() 27 | } else { 28 | f() 29 | } 30 | } 31 | } 32 | 33 | override fun loadState(config: Config) { 34 | XmlSerializerUtil.copyBean(config, this.config) 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /src/main/python/CharacterWeight.py: -------------------------------------------------------------------------------- 1 | import Image,ImageDraw,ImageFont,sys 2 | 3 | # Generates java source for the static character weight map in CharacterWeight.java by 4 | # printing each char and observing the amount of black vs white present in the image. 5 | 6 | 7 | 8 | def getWeight(char,font): 9 | boostFactor = 2.0 10 | image = Image.new("RGBA", (7, 12), (255,255,255)) 11 | 12 | draw = ImageDraw.Draw(image) 13 | 14 | draw.text((0,0), char, (0, 0, 0), font=font) 15 | 16 | pix = image.load() 17 | 18 | topAverage = 0 19 | count = 0 20 | for x in xrange(0, 7): 21 | for y in xrange(0, 6): 22 | topAverage += pix[x,y][0] / 255.0 23 | count += 1 24 | topAverage /= count 25 | 26 | bottomAverage = 0 27 | count = 0 28 | for x in xrange(0, 7): 29 | for y in xrange(7, 12): 30 | bottomAverage += pix[x,y][0] / 255.0 31 | count += 1 32 | 33 | bottomAverage /= count 34 | 35 | return (1 - topAverage) * boostFactor, (1 - bottomAverage) * boostFactor 36 | 37 | font = ImageFont.truetype("./cour.ttf", 12) 38 | 39 | print "{" 40 | for char in xrange(33, 127): 41 | top, bottom = getWeight(chr(char), font); 42 | print "\t%2.4ff,\t// %03d = '%s' (top)" % (top, char, chr(char)) 43 | print "\t%2.4ff,\t// %03d = '%s' (bottom)" % (bottom, char, chr(char)) 44 | print "};" 45 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Adam Scarr 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/CodeGlancePlugin.kt: -------------------------------------------------------------------------------- 1 | package net.vektah.codeglance 2 | 3 | import com.intellij.openapi.components.ProjectComponent 4 | import com.intellij.openapi.diagnostic.Logger 5 | import com.intellij.openapi.fileEditor.FileEditorManagerListener 6 | import com.intellij.openapi.project.Project 7 | import net.vektah.codeglance.render.TaskRunner 8 | 9 | class CodeGlancePlugin(private val project: Project) : ProjectComponent { 10 | private val logger = Logger.getInstance(javaClass) 11 | private val runner = TaskRunner() 12 | private val runnerThread = Thread(runner) 13 | private val injector: EditorPanelInjector 14 | 15 | init { 16 | injector = EditorPanelInjector(project, runner) 17 | } 18 | 19 | override fun initComponent() { 20 | runnerThread.start() 21 | project.messageBus.connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, injector) 22 | logger.debug("CodeGlance2 initialized") 23 | } 24 | 25 | override fun disposeComponent() { 26 | runner.stop() 27 | } 28 | 29 | override fun getComponentName(): String { 30 | return "CodeGlancePlugin" 31 | } 32 | 33 | override fun projectOpened() { 34 | // called when project is opened 35 | } 36 | 37 | override fun projectClosed() { 38 | // called when project is being closed 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/config/Config.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2013, Adam Scarr 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | package net.vektah.codeglance.config 27 | 28 | class Config { 29 | var pixelsPerLine: Int = 3 30 | var minLineCount: Int = 1 31 | var maxFileSize: Int = 1024000 32 | var disabled: Boolean = false 33 | var jumpOnMouseDown: Boolean = true 34 | var percentageBasedClick: Boolean = false 35 | var width: Int = 110 36 | var viewportColor: String = "A0A0A0" 37 | var clean: Boolean = true 38 | var isRightAligned: Boolean = true 39 | var minWindowWidth: Int = 0 40 | var locked: Boolean = false 41 | } 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CodeGlance [![CircleCI](https://circleci.com/gh/Vektah/CodeGlance/tree/master.svg?style=svg)](https://circleci.com/gh/Vektah/CodeGlance/tree/master) 2 | ========== 3 | 4 | Plugin Repository: http://plugins.jetbrains.com/plugin/7275 5 | Latest build: https://github.com/Vektah/CodeGlance/releases 6 | 7 | InteliJ plugin that displays a zoomed out overview or minimap similar to the one found in Sublime into the editor pane. The minimap allows for quick scrolling letting you jump straight to sections of code. 8 | 9 | - Works with both light and dark themes using your customized colors for syntax highlighting. 10 | - Worker thread for rendering 11 | - Color rendering using InteliJ's tokenizer 12 | - Scrollable! 13 | - Embedded into editor window 14 | - Complete replacement for Code Outline that supports new Intellij builds. 15 | 16 | ![Dracula](https://raw.github.com/Vektah/CodeGlance/master/pub/example.png) 17 | 18 | 19 | Building using Gradle 20 | ==================== 21 | ``` 22 | git clone https://github.com/Vektah/CodeGlance 23 | cd CodeGlance 24 | # run the tests 25 | ./gradlew test 26 | 27 | # build the plugin and install it in the sandbox then start idea 28 | ./gradlew runIdea 29 | 30 | # build a release 31 | ./gradlew buildPlugin 32 | 33 | ``` 34 | The result will be saved as build/distributions/CodeGlance-{version}.zip 35 | 36 | 37 | Running from source in IntellJ 38 | =================== 39 | 1. Make sure you have the Plugin DevKit installed. 40 | 2. Checkout sources from github 41 | 3. Create a new Intellij Platform plugin project 42 | 4. Select source directory, chose a plugin sdk (create one that points to your intellij install). 43 | 5. Mark src/main/java as source root, and src/test/java as test root. 44 | 6. In order to run tests you will need to find mockito and testing jars. I usually do this with Gradle. 45 | 7. In module settings set the path to META-INF to src/main/resources 46 | 8. Hit Run. 47 | 48 | 49 | Show/Hide or Enable/Disable Minimap 50 | =================== 51 | * **Ctrl-Shift-G** to toggle minimap. 52 | * Settings > Other Settings > CodeGlance 53 | -------------------------------------------------------------------------------- /src/test/java/net/vektah/codeglance/render/TaskRunnerTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2013, Adam Scarr 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | package net.vektah.codeglance.render 27 | 28 | import org.testng.annotations.Test 29 | import org.testng.Assert.* 30 | 31 | class TaskRunnerTest { 32 | @Test fun testTimerTask() { 33 | val runner = TaskRunner() 34 | Thread(runner).start() 35 | 36 | var ran = false 37 | 38 | runner.run { 39 | ran = true 40 | } 41 | 42 | try { 43 | Thread.sleep(50) 44 | } catch (e: InterruptedException) { 45 | e.printStackTrace() 46 | } 47 | 48 | runner.stop() 49 | 50 | assertTrue(ran) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/actions/ShowHideAction.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2013, Adam Scarr 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | package net.vektah.codeglance.actions 27 | 28 | import com.intellij.openapi.actionSystem.AnAction 29 | import com.intellij.openapi.actionSystem.AnActionEvent 30 | import com.intellij.openapi.components.ServiceManager 31 | import net.vektah.codeglance.config.ConfigService 32 | 33 | class ShowHideAction : AnAction() { 34 | private val configService = ServiceManager.getService(ConfigService::class.java) 35 | 36 | override fun actionPerformed(anActionEvent: AnActionEvent) { 37 | configService.state!!.disabled = !configService.state!!.disabled 38 | configService.notifyChange() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/render/TaskRunner.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2013, Adam Scarr 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | package net.vektah.codeglance.render 27 | 28 | import java.util.concurrent.ArrayBlockingQueue 29 | 30 | /** 31 | * Runs tasks sequentially in a queue. Thread safe. 32 | */ 33 | class TaskRunner : Runnable { 34 | private var stop = false 35 | private val taskQueue = ArrayBlockingQueue<() -> Unit>(1000) 36 | 37 | fun run(task: () -> Unit) = taskQueue.add(task) 38 | 39 | fun stop() { 40 | stop = true 41 | } 42 | 43 | override fun run() { 44 | while (!stop) { 45 | try { 46 | taskQueue.take()() 47 | } catch (e: InterruptedException) {} 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/render/Folds.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2013, Adam Scarr 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | package net.vektah.codeglance.render 27 | 28 | import com.intellij.openapi.editor.FoldRegion 29 | 30 | // Is a copy of Array that only contains folded folds and can be passed safely to another thread 31 | class Folds{ 32 | private val foldsSet: HashSet = hashSetOf() 33 | 34 | constructor(allFolds: Array) { 35 | allFolds 36 | .filterNot { it.isExpanded } 37 | .forEach { foldRegion -> 38 | for (index in foldRegion.startOffset until foldRegion.endOffset) 39 | foldsSet.add(index) 40 | } 41 | } 42 | 43 | // Used by tests that want an empty fold set 44 | constructor() 45 | 46 | /** 47 | * Checks if a given position is within a folded region 48 | * @param position the offset from the start of file in chars 49 | * * 50 | * @return true if the given position is folded. 51 | */ 52 | fun isFolded(position: Int): Boolean { 53 | return foldsSet.contains(position) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/test/java/net/vektah/codeglance/render/ScrollStateTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2013, Adam Scarr 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | package net.vektah.codeglance.render 27 | 28 | import org.testng.annotations.Test 29 | import org.testng.Assert.* 30 | 31 | class ScrollStateTest { 32 | @Test fun testVisibleAreaWhenEntireDocumentIsVisible() { 33 | val ss = ScrollState() 34 | .setDocumentSize(50, 100) 35 | .setViewportArea(0, 100) 36 | .setVisibleHeight(100) 37 | 38 | assertEquals(ss.visibleStart, 0) 39 | assertEquals(ss.visibleEnd, 100) 40 | } 41 | 42 | @Test fun testVisibleAreaInMiddleOfShortDocument() { 43 | val ss = ScrollState() 44 | .setDocumentSize(50, 156) 45 | .setViewportArea(22, 124) 46 | .setVisibleHeight(600) 47 | 48 | assertEquals(0, ss.visibleStart) 49 | assertEquals(156, ss.visibleEnd) 50 | } 51 | 52 | @Test fun testVisibleAreaAtStartOfLongDocument() { 53 | val ss = ScrollState() 54 | .setDocumentSize(50, 2000) 55 | .setViewportArea(0, 100) 56 | .setVisibleHeight(300) 57 | 58 | assertEquals(0, ss.visibleStart) 59 | assertEquals(300, ss.visibleEnd) 60 | } 61 | 62 | @Test fun testVisibleAreaAtEndOfLongDocument() { 63 | val ss = ScrollState() 64 | .setDocumentSize(50, 2000) 65 | .setViewportArea(1950, 50) 66 | .setVisibleHeight(300) 67 | 68 | assertEquals(1700, ss.visibleStart) 69 | assertEquals(2000, ss.visibleEnd) 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /src/test/java/net/vektah/codeglance/render/CharacterWeightTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2013, Adam Scarr 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | package net.vektah.codeglance.render 27 | 28 | import org.testng.annotations.DataProvider 29 | import org.testng.annotations.Test 30 | 31 | import org.testng.Assert.* 32 | 33 | /** 34 | * Some basic sanity tests that the weight generation function works OK. 35 | */ 36 | class CharacterWeightTest { 37 | @Test fun test_lower_boundaries() { 38 | assertEquals(0.0f, GetTopWeight(0), 0.001f) 39 | assertEquals(0.0f,GetTopWeight(1), 0.001f) 40 | assertEquals(0.0f,GetTopWeight(32), 0.001f) 41 | assertNotEquals(0.0f,GetTopWeight(33)) 42 | assertNotEquals(0.0f,GetTopWeight(127)) 43 | assertNotEquals(0.0f,GetTopWeight(128)) 44 | } 45 | 46 | @Test(dataProvider = "Test-Relative-Weights") fun test_relative_weights_are_sane(a: Char, b: Char) { 47 | assertTrue(GetTopWeight(a.toInt()) + GetBottomWeight(a.toInt()) < GetTopWeight(b.toInt()) + GetBottomWeight(b.toInt())) 48 | } 49 | 50 | @Test fun test_known_values() { 51 | assertEquals(0.2458f, GetTopWeight('v'.toInt())) 52 | assertEquals(0.3538f, GetBottomWeight('v'.toInt())) 53 | } 54 | 55 | companion object { 56 | 57 | @DataProvider(name = "Test-Relative-Weights") fun testRelativeWeights(): Array> { 58 | return arrayOf( 59 | arrayOf('.', ','), 60 | arrayOf('1', '8'), 61 | arrayOf('.', 'a'), 62 | arrayOf(',', '1') 63 | ) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/render/ScrollState.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2013, Adam Scarr 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | package net.vektah.codeglance.render 27 | 28 | class ScrollState { 29 | var documentWidth: Int = 0 30 | private set 31 | var documentHeight: Int = 0 32 | private set 33 | 34 | var visibleStart: Int = 0 35 | private set 36 | var visibleEnd: Int = 0 37 | private set 38 | var visibleHeight: Int = 0 39 | private set 40 | var drawHeight: Int = 0 41 | private set 42 | 43 | var viewportStart: Int = 0 44 | private set 45 | var viewportHeight: Int = 0 46 | private set 47 | 48 | fun setDocumentSize(width: Int, height: Int): ScrollState { 49 | documentWidth = width 50 | documentHeight = height 51 | recomputeVisible() 52 | 53 | return this 54 | } 55 | 56 | fun setVisibleHeight(height: Int): ScrollState { 57 | visibleHeight = height 58 | recomputeVisible() 59 | 60 | return this 61 | } 62 | 63 | fun setViewportArea(start: Int, height: Int): ScrollState { 64 | viewportStart = start 65 | viewportHeight = height 66 | recomputeVisible() 67 | 68 | return this 69 | } 70 | 71 | private fun recomputeVisible() { 72 | visibleStart = ((viewportStart / (documentHeight - viewportHeight + 1).toFloat()) * (documentHeight - visibleHeight+ 1)).toInt() 73 | if (visibleStart < 0) { 74 | visibleStart = 0 75 | } 76 | 77 | drawHeight = Math.min(visibleHeight, documentHeight) 78 | visibleEnd = visibleStart + drawHeight 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/net/vektah/codeglance/render/FoldsTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2013, Adam Scarr 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | package net.vektah.codeglance.render 27 | 28 | import org.testng.annotations.Test 29 | import org.testng.Assert.* 30 | 31 | class FoldsTest { 32 | @Test fun testNothingMatchesEmptyFoldSet() { 33 | val folds = Folds() 34 | 35 | assertFalse(folds.isFolded(0)) 36 | assertFalse(folds.isFolded(-1)) 37 | assertFalse(folds.isFolded(1)) 38 | assertFalse(folds.isFolded(99)) 39 | } 40 | 41 | @Test fun testFoldedRegionMatch() { 42 | val folds = Folds(arrayOf(FakeFold(10, 20, true))) 43 | 44 | assertFalse(folds.isFolded(0)) 45 | assertTrue(folds.isFolded(10)) 46 | assertTrue(folds.isFolded(15)) 47 | assertFalse(folds.isFolded(20)) 48 | assertFalse(folds.isFolded(25)) 49 | } 50 | 51 | 52 | @Test fun testUnfoldedRegionsDontMatch() { 53 | val folds = Folds(arrayOf(FakeFold(10, 20, false))) 54 | 55 | assertFalse(folds.isFolded(0)) 56 | assertFalse(folds.isFolded(10)) 57 | assertFalse(folds.isFolded(15)) 58 | assertFalse(folds.isFolded(25)) 59 | } 60 | 61 | @Test fun testNestedFoldedRegions() { 62 | val folds = Folds(arrayOf( 63 | FakeFold(10, 20, true), 64 | FakeFold(12, 16, true), 65 | FakeFold(14, 15, true), 66 | FakeFold(18, 19, true) 67 | )) 68 | 69 | assertFalse(folds.isFolded(0)) 70 | assertTrue(folds.isFolded(11)) 71 | assertTrue(folds.isFolded(13)) 72 | assertTrue(folds.isFolded(14)) 73 | assertTrue(folds.isFolded(18)) 74 | assertFalse(folds.isFolded(20)) 75 | assertFalse(folds.isFolded(25)) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/net/vektah/codeglance/render/FakeFold.kt: -------------------------------------------------------------------------------- 1 | package net.vektah.codeglance.render 2 | 3 | import com.intellij.openapi.editor.Document 4 | import com.intellij.openapi.editor.Editor 5 | import com.intellij.openapi.editor.FoldRegion 6 | import com.intellij.openapi.editor.FoldingGroup 7 | import com.intellij.openapi.util.Key 8 | 9 | 10 | class FakeFold(val start: Int, val end: Int, val folded: Boolean) : FoldRegion { 11 | override fun isExpanded(): Boolean { 12 | return !folded 13 | } 14 | 15 | override fun getEndOffset(): Int { 16 | return end 17 | } 18 | 19 | override fun getStartOffset(): Int { 20 | return start 21 | } 22 | 23 | override fun getGroup(): FoldingGroup? { 24 | throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. 25 | } 26 | 27 | override fun isGreedyToLeft(): Boolean { 28 | throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. 29 | } 30 | 31 | override fun putUserData(p0: Key, p1: T?) { 32 | throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. 33 | } 34 | 35 | override fun setGreedyToRight(p0: Boolean) { 36 | throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. 37 | } 38 | 39 | override fun isValid(): Boolean { 40 | throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. 41 | } 42 | 43 | override fun getDocument(): Document { 44 | throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. 45 | } 46 | 47 | override fun setGreedyToLeft(p0: Boolean) { 48 | throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. 49 | } 50 | 51 | override fun setExpanded(p0: Boolean) { 52 | throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. 53 | } 54 | 55 | override fun getUserData(p0: Key): T? { 56 | throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. 57 | } 58 | 59 | override fun getPlaceholderText(): String { 60 | throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. 61 | } 62 | 63 | override fun getEditor(): Editor { 64 | throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. 65 | } 66 | 67 | override fun shouldNeverExpand(): Boolean { 68 | throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. 69 | } 70 | 71 | override fun isGreedyToRight(): Boolean { 72 | throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. 73 | } 74 | 75 | override fun dispose() { 76 | throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/Scrollbar.kt: -------------------------------------------------------------------------------- 1 | package net.vektah.codeglance 2 | 3 | import com.intellij.openapi.components.ServiceManager 4 | import com.intellij.openapi.diagnostic.Logger 5 | import com.intellij.openapi.editor.Editor 6 | import net.vektah.codeglance.config.Config 7 | import net.vektah.codeglance.config.ConfigService 8 | import net.vektah.codeglance.render.ScrollState 9 | import java.awt.* 10 | import java.awt.event.* 11 | import javax.swing.JPanel 12 | 13 | class Scrollbar(val editor: Editor, val scrollstate : ScrollState) : JPanel(), MouseListener, MouseWheelListener, MouseMotionListener { 14 | private var scrollStart: Int = 0 15 | private var mouseStart: Int = 0 16 | private val defaultCursor = Cursor(Cursor.DEFAULT_CURSOR) 17 | private var resizing = false 18 | private var dragging = false 19 | private var resizeStart: Int = 0 20 | private var widthStart: Int = 0 21 | private val configService = ServiceManager.getService(ConfigService::class.java) 22 | private var config: Config = configService.state!! 23 | private var visibleRectColor: Color = Color.decode("#" + config.viewportColor) 24 | 25 | override fun mouseEntered(e: MouseEvent?) {} 26 | override fun mouseExited(e: MouseEvent?) {} 27 | 28 | private fun isInReizeGutter(x: Int): Boolean { 29 | if (config.locked) { 30 | return false 31 | } 32 | if (config.isRightAligned) { 33 | return x >= 0 && x < 8 34 | } else { 35 | return x >= config.width - 8 && x <= config.width 36 | } 37 | } 38 | 39 | init { 40 | configService.onChange { 41 | config = configService.state!! 42 | visibleRectColor = Color.decode("#" + config.viewportColor) 43 | } 44 | 45 | addMouseWheelListener(this) 46 | addMouseListener(this) 47 | addMouseMotionListener(this) 48 | } 49 | 50 | override fun mouseDragged(e: MouseEvent?) { 51 | if (resizing) { 52 | config.width = widthStart + if(config.isRightAligned) resizeStart - e!!.xOnScreen else e!!.xOnScreen - resizeStart 53 | if (config.width < 50) { 54 | config.width = 50 55 | } else if (config.width > 250) { 56 | config.width = 250 57 | } 58 | configService.notifyChange() 59 | } 60 | 61 | if (dragging) { 62 | // Disable animation when dragging for better experience. 63 | editor.scrollingModel.disableAnimation() 64 | scrollTo(e!!.y) 65 | editor.scrollingModel.enableAnimation() 66 | } 67 | } 68 | 69 | override fun mousePressed(e: MouseEvent?) { 70 | if (!dragging && isInReizeGutter(e!!.x)) { 71 | resizing = true 72 | } else if (!resizing) { 73 | dragging = true 74 | } 75 | 76 | if (resizing) { 77 | resizeStart = e!!.xOnScreen 78 | widthStart = config.width 79 | } 80 | 81 | if (dragging) { 82 | if (config.jumpOnMouseDown) { 83 | scrollTo(e!!.y) 84 | } 85 | 86 | scrollStart = editor.scrollingModel.verticalScrollOffset 87 | mouseStart = e!!.y 88 | } 89 | } 90 | 91 | private fun scrollTo(y: Int) { 92 | val percentage = (y + scrollstate.visibleStart) / scrollstate.documentHeight.toFloat() 93 | val offset = editor.component.size.height / 2 94 | editor.scrollingModel.scrollVertically((percentage * editor.contentComponent.size.height - offset).toInt()) 95 | } 96 | 97 | override fun mouseReleased(e: MouseEvent?) { 98 | dragging = false 99 | resizing = false 100 | } 101 | 102 | override fun mouseClicked(e: MouseEvent?) { 103 | if (!config.jumpOnMouseDown) { 104 | scrollTo(e!!.y) 105 | } 106 | } 107 | 108 | override fun mouseMoved(e: MouseEvent?) { 109 | if (isInReizeGutter(e!!.x)) { 110 | cursor = if (config.isRightAligned) Cursor(Cursor.W_RESIZE_CURSOR) else Cursor(Cursor.E_RESIZE_CURSOR) 111 | } else { 112 | cursor = defaultCursor 113 | } 114 | } 115 | 116 | override fun mouseWheelMoved(mouseWheelEvent: MouseWheelEvent) { 117 | editor.scrollingModel.scrollVertically(editor.scrollingModel.verticalScrollOffset + (mouseWheelEvent.wheelRotation * editor.lineHeight * 3)) 118 | } 119 | 120 | override fun paint(gfx: Graphics?) { 121 | val g = gfx as Graphics2D 122 | 123 | g.color = visibleRectColor 124 | g.composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.20f) 125 | g.fillRect(0, scrollstate.viewportStart - scrollstate.visibleStart, width, scrollstate.viewportHeight) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/config/ConfigEntry.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2013, Adam Scarr 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | package net.vektah.codeglance.config 27 | 28 | import com.intellij.openapi.components.ServiceManager 29 | import com.intellij.openapi.options.Configurable 30 | import com.intellij.openapi.options.ConfigurationException 31 | import org.jetbrains.annotations.Nls 32 | 33 | import javax.swing.* 34 | 35 | class ConfigEntry : Configurable { 36 | private var form: ConfigForm? = null 37 | private val configService = ServiceManager.getService(ConfigService::class.java) 38 | private val config = configService.state!! 39 | 40 | @Nls override fun getDisplayName(): String { 41 | return "CodeGlance" 42 | } 43 | 44 | override fun getHelpTopic(): String? { 45 | return "Configuration for the CodeGlance minimap" 46 | } 47 | 48 | override fun createComponent(): JComponent? { 49 | form = ConfigForm() 50 | reset() 51 | return form!!.root 52 | } 53 | 54 | override fun isModified() = form != null && 55 | (config.pixelsPerLine != form!!.pixelsPerLine 56 | || config.disabled != form!!.isDisabled 57 | || config.locked != form!!.isLocked 58 | || config.jumpOnMouseDown != form!!.jumpOnMouseDown() 59 | || config.percentageBasedClick != form!!.percentageBasedClick() 60 | || config.width != form!!.width 61 | || config.viewportColor !== form!!.viewportColor 62 | || config.minLineCount != form!!.minLinesCount 63 | || config.minWindowWidth != form!!.minWindowWidth 64 | || config.clean != form!!.cleanStyle 65 | || config.isRightAligned != form!!.isRightAligned) 66 | 67 | @Throws(ConfigurationException::class) 68 | override fun apply() { 69 | if (form == null) return 70 | 71 | config.pixelsPerLine = form!!.pixelsPerLine 72 | config.disabled = form!!.isDisabled 73 | config.locked = form!!.isLocked 74 | config.jumpOnMouseDown = form!!.jumpOnMouseDown() 75 | config.percentageBasedClick = form!!.percentageBasedClick() 76 | config.width = form!!.width 77 | 78 | if (form!!.viewportColor.length == 6 && form!!.viewportColor.matches("^[a-fA-F0-9]*$".toRegex())) { 79 | config.viewportColor = form!!.viewportColor 80 | } else { 81 | config.viewportColor = "A0A0A0" 82 | } 83 | 84 | config.minLineCount = form!!.minLinesCount 85 | config.minWindowWidth = form!!.minWindowWidth 86 | config.clean = form!!.cleanStyle 87 | config.isRightAligned = form!!.isRightAligned 88 | configService.notifyChange() 89 | } 90 | 91 | override fun reset() { 92 | if (form == null) return 93 | 94 | form!!.pixelsPerLine = config.pixelsPerLine 95 | form!!.isDisabled = config.disabled 96 | form!!.isLocked= config.locked 97 | form!!.setJumpOnMouseDown(config.jumpOnMouseDown) 98 | form!!.setPercentageBasedClick(config.percentageBasedClick) 99 | form!!.viewportColor = config.viewportColor 100 | form!!.width = config.width 101 | form!!.minLinesCount = config.minLineCount 102 | form!!.minWindowWidth = config.minWindowWidth 103 | form!!.setCleanStyle(config.clean) 104 | form!!.setRightAligned(config.isRightAligned) 105 | } 106 | 107 | override fun disposeUIResources() { 108 | form = null 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/test/java/net/vektah/codeglance/render/GlanceImageTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2013, Adam Scarr 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | package net.vektah.codeglance.render 27 | 28 | import com.intellij.openapi.editor.FoldRegion 29 | import com.intellij.openapi.util.text.StringUtil 30 | import net.vektah.codeglance.config.Config 31 | import org.testng.annotations.BeforeMethod 32 | import org.testng.annotations.DataProvider 33 | import org.testng.annotations.Test 34 | 35 | import junit.framework.Assert.assertEquals 36 | 37 | class GlanceImageTest { 38 | private var img: Minimap? = null 39 | private val config = Config() 40 | 41 | @BeforeMethod fun setUp() { 42 | config.pixelsPerLine = 2 43 | img = Minimap(config) 44 | } 45 | 46 | @Test(dataProvider = "Test-Dimensions") fun test_calculate_dimensions(string: CharSequence, height: Int) { 47 | img!!.updateDimensions(string, Folds()) 48 | assertEquals(height, img!!.height) 49 | } 50 | 51 | @Test fun test_calculate_dimensions_resize() { 52 | img!!.updateDimensions("ASDF\nHJKL", Folds()) 53 | 54 | assertEquals(config.width, img!!.img!!.width) 55 | assertEquals(206, img!!.img!!.height) 56 | 57 | // Only added a little, so image should not get regenerated. 58 | img!!.updateDimensions("asdfjkl;asdfjkl;\nasdfjlkasdfjkl\nasdfjkl;a;sdfjkl", Folds()) 59 | 60 | assertEquals(config.width, img!!.img!!.width) 61 | assertEquals(206, img!!.img!!.height) 62 | 63 | // Went over the existing image boundary so a new one should be created. 64 | img!!.updateDimensions(StringUtil.repeat("\na", 152), Folds()) 65 | 66 | assertEquals(config.width, img!!.img!!.width) 67 | assertEquals(508, img!!.img!!.height) 68 | } 69 | 70 | @Test(dataProvider = "Test-Newlines") fun test_newline_search(input: CharSequence, i: Int, expected_number: Int, expected_begin: Int, expected_end: Int) { 71 | img!!.updateDimensions(input, Folds()) 72 | 73 | val line = img!!.getLine(i) 74 | 75 | assertEquals(expected_number, line.number) 76 | assertEquals(expected_begin, line.begin) 77 | assertEquals(expected_end, line.end) 78 | } 79 | 80 | companion object { 81 | 82 | @DataProvider(name = "Test-Dimensions") fun testDimensions(): Array> { 83 | return arrayOf(arrayOf("", 4), arrayOf("SingleLine", 4), arrayOf("Multi\nLine", 6), arrayOf("Line with lots of tabs\n\t\t\t\t\t\t\t\t", 6), arrayOf("ʳʳʳʳ", 4), arrayOf("ꬉꬉꬉꬉ", 4)) 84 | } 85 | 86 | @DataProvider(name = "Test-Newlines") fun testNewlines(): Array> { 87 | return arrayOf(arrayOf("", 0, 1, 0, 0), arrayOf("1111111111\n2222222222", 0, 1, 0, 10), // First line 88 | arrayOf("1111111111\n2222222222", 5, 1, 0, 10), // First line 89 | arrayOf("1111111111\n2222222222", 10, 1, 0, 10), // The newline itself 90 | arrayOf("1111111111\n2222222222", 15, 2, 11, 20), // The next line, no trailing new line 91 | arrayOf("1111111111\n2222222222\n", 15, 2, 11, 21), // The next line with trailing newline. 92 | arrayOf("1111111111\n2222222222\n3333333333", 15, 2, 11, 21), // Middle 93 | arrayOf("111 111 11\n222 222 22\n333 333 33", 25, 3, 22, 31), // End of line, but truncated to a valid char (no trailing newline) 94 | arrayOf("\n\n\n\n", -1, 1, 0, 0), arrayOf("\n\n\n\n", 0, 1, 0, 0), arrayOf("\n\n\n\n", 1, 2, 1, 1), arrayOf("\n\n\n\n", 2, 3, 2, 2), arrayOf("\n\n\n\n", 3, 4, 3, 3), arrayOf("\n\n\n\n", 4, 4, 3, 3)) 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/EditorPanelInjector.kt: -------------------------------------------------------------------------------- 1 | package net.vektah.codeglance 2 | 3 | import com.intellij.openapi.components.ServiceManager 4 | import com.intellij.openapi.diagnostic.Logger 5 | import com.intellij.openapi.fileEditor.* 6 | import com.intellij.openapi.project.Project 7 | import com.intellij.openapi.vfs.VirtualFile 8 | import com.intellij.ui.JBSplitter 9 | import net.vektah.codeglance.config.Config 10 | import net.vektah.codeglance.config.ConfigService 11 | import net.vektah.codeglance.render.TaskRunner 12 | import javax.swing.* 13 | import java.awt.* 14 | import java.util.* 15 | 16 | /** 17 | * Injects a panel into any newly created editors. 18 | */ 19 | class EditorPanelInjector(private val project: Project, private val runner: TaskRunner) : FileEditorManagerListener { 20 | private val logger = Logger.getInstance(javaClass) 21 | private val panels = HashMap() 22 | private val configService = ServiceManager.getService(ConfigService::class.java) 23 | private var config: Config = configService.state!! 24 | 25 | override fun fileOpened(fem: FileEditorManager, virtualFile: VirtualFile) { 26 | // Seems there is a case where multiple split panes can have the same file open and getSelectedEditor, and even 27 | // getEditors(virtualVile) return only one of them... So shotgun approach here. 28 | val editors = fem.allEditors 29 | for (editor in editors) { 30 | inject(editor) 31 | } 32 | 33 | freeUnusedPanels(fem) 34 | } 35 | 36 | /** 37 | * Here be dragons. No Seriously. Run! 38 | * 39 | * We are digging way down into the editor layout. This lets the codeglance panel be right next to the scroll bar. 40 | * In an ideal world it would be inside the scroll bar... maybe one day. 41 | * 42 | * vsch: added handling when the editor is even deeper, inside firstComponent of a JBSplitter, used by idea-multimarkdown 43 | * and Markdown Support to show split preview. Missed this plugin while editing markdown. These changes got it back. 44 | * 45 | * @param editor A text editor to inject into. 46 | */ 47 | private fun getPanel(editor: FileEditor): JPanel? { 48 | if (editor !is TextEditor) { 49 | logger.debug("I01: Injection failed, only text editors are supported currently.") 50 | return null 51 | } 52 | 53 | try { 54 | val outerPanel = editor.component as JPanel 55 | val outerLayout = outerPanel.layout as BorderLayout 56 | var layoutComponent = outerLayout.getLayoutComponent(BorderLayout.CENTER) 57 | 58 | if (layoutComponent is JBSplitter) { 59 | // editor is inside firstComponent of a JBSplitter 60 | val editorComp = layoutComponent.firstComponent as JPanel 61 | layoutComponent = (editorComp.layout as BorderLayout).getLayoutComponent(BorderLayout.CENTER) 62 | } 63 | 64 | val pane = layoutComponent as JLayeredPane 65 | val panel = if (pane.componentCount > 1) pane.getComponent(1) as JPanel else pane.getComponent(0) as JPanel 66 | 67 | // Assert ahead of time that we have the expected layout, so the caller dosent need to 68 | panel.layout as BorderLayout 69 | 70 | return panel 71 | } catch (e: ClassCastException) { 72 | logger.warn("Injection failed") 73 | e.printStackTrace() 74 | return null 75 | } 76 | } 77 | 78 | private fun inject(editor: FileEditor) { 79 | val panel = getPanel(editor) ?: return 80 | val innerLayout = panel.layout as BorderLayout 81 | 82 | val where = if (config.isRightAligned) BorderLayout.LINE_END else BorderLayout.LINE_START 83 | 84 | if (innerLayout.getLayoutComponent(where) == null) { 85 | val glancePanel = GlancePanel(project, editor, panel, runner) 86 | panel.add(glancePanel, where) 87 | panels.put(editor, glancePanel) 88 | } 89 | } 90 | 91 | private fun uninject(editor: FileEditor) { 92 | val panel = getPanel(editor) ?: return 93 | val innerLayout = panel.layout as BorderLayout 94 | 95 | // Ok we finally found the actual editor layout. Now make sure we have already injected into this editor. 96 | val rightPanel = innerLayout.getLayoutComponent(BorderLayout.LINE_END) 97 | if (rightPanel != null) { 98 | panel.remove(rightPanel) 99 | } 100 | 101 | val leftPanel = innerLayout.getLayoutComponent(BorderLayout.LINE_START) 102 | if (leftPanel != null) { 103 | panel.remove(leftPanel) 104 | } 105 | } 106 | 107 | override fun fileClosed(fem: FileEditorManager, virtualFile: VirtualFile) { 108 | freeUnusedPanels(fem) 109 | } 110 | 111 | /** 112 | * On close we dont know which (if any) editor was closed, just the file. And in configurations we dont even 113 | * get a fileClosed event. Lets just scan all of the open penels and make sure they are still being used by 114 | * at least one of the open editors. 115 | */ 116 | private fun freeUnusedPanels(fem: FileEditorManager) { 117 | val unseen = HashSet(panels.keys) 118 | 119 | for (editor in fem.allEditors) { 120 | if (unseen.contains(editor)) { 121 | unseen.remove(editor) 122 | } 123 | } 124 | 125 | var panel: GlancePanel 126 | for (editor in unseen) { 127 | panel = panels[editor]!! 128 | panel.onClose() 129 | uninject(editor) 130 | panels.remove(editor) 131 | } 132 | } 133 | 134 | override fun selectionChanged(fileEditorManagerEvent: FileEditorManagerEvent) { 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | net.vektah.codeglance 3 | CodeGlance 4 | 1.5.4 5 | Vektah 6 | 7 | 8 | Embeds a code minimap similar to the one found in Sublime into the editor pane. Works with both light and dark 9 | themes using your customized colors for syntax highlighting. 10 | 11 | 12 | 1.5.4 14 |
    15 |
  • Bugfix: Viewport position now calculates correctly for large files -@ru5k
  • 16 |
17 |

1.5.3

18 |
    19 |
  • Bugfix: Render style and alignment now save correctly
  • 20 |
  • Bugfix: Queue full
  • 21 |
22 |

1.5.2

23 |
    24 |
  • Add support for transparent backgrounds
  • 25 |
  • Add an option to lock the width
  • 26 |
  • Bugfix: Assertion error
  • 27 |
28 |

1.5.1

29 |
    30 |
  • Fix a bug that causes the minimap not to render
  • 31 |
  • Added a min editor width to automatically disable codeglance. Thanks github.com/bukajsytlos
  • 32 |
33 |

1.5.0

34 |
    35 |
  • Complete overhaul of scrolling logic. Smoother, faster, more accurate
  • 36 |
  • A cleaner look. Old renderer is available still in settings
  • 37 |
  • Correctly renders multiple cursors
  • 38 |
  • Fix a bug injecting into some custom editors
  • 39 |
  • Don't automatically re-enable CodeGlance on boot
  • 40 |
41 |

1.4.6

42 |
    43 |
  • Add min and max documentWidth
  • 44 |
  • Remove a useless warning
  • 45 |
  • Fix a bug when encountering tokens past the end of the document
  • 46 |
47 |

1.4.5

48 |
    49 |
  • Add support for 171.3556 EAPs
  • 50 |
  • Fix a race condition when folding
  • 51 |
52 |

1.4.4

53 |
    54 |
  • Increase maximum file size to 1MB, up from 20k
  • 55 |
56 |

1.4.3

57 |
    58 |
  • Fixed a memory leak
  • 59 |
  • Reduced the total memory used to about half
  • 60 |
  • Lots of dead bugs.
  • 61 |
62 |

1.4.2

63 |
    64 |
  • Bugfix: Disable retina scaling. This should fix some of the current retina issues.
  • 65 |
66 |

1.4.1

67 |
    68 |
  • Bugfix: NPE in getLine
  • 69 |
  • Bugfix: Increase render queue to 1000 requests
  • 70 |
71 |

1.4.0

72 |
    73 |
  • Scrolling while hovering over the overview now works
  • 74 |
  • Add a keybind to show/hide codeglance. Default is control shift G
  • 75 |
  • Display editor selection in real time
  • 76 |
  • Selection is now resizeable
  • 77 |
  • Configurable viewport color
  • 78 |
79 |

1.3.2: Fixed a memory leak

80 |

1.3.1: Fix a NPE in offsetToScreenSpace()

81 |

1.3.0: Folding support

82 |
    83 |
  • Dragging is now relative to the start point. This means less sudden jumping around
  • 84 |
  • This required some fairly heavy changes to the coordinate code.
  • 85 |
86 |

1.2.3: Added scale config item!

87 |
    88 |
  • Fix regression of 'Already disposed'
  • 89 |
  • Added ability to disable CodeGlance without restarting
  • 90 |
91 |

1.2.2: Added scale config item!

92 |
    93 |
  • Fixed an NPE in PhpStorm
  • 94 |
  • Fixed clicking on a section of code in a long file, dragging still behaves the same with percentage based movement.
  • 95 |
  • Pixels per line is now configurable
  • 96 |
97 |

1.2.1: Scale fixes

98 |
    99 |
  • Fixed a rendering issue for osx with vertical stretching.
  • 100 |
  • Fixes stretching of the view area when looking at large files.
  • 101 |
  • Increased viewport visibility a touch.
  • 102 |
103 |

1.2: Smaller with transparency

104 |
    105 |
  • Now uses a transparent selection box and the maximum documentWidth is limited to 100 chars. This will probably become configurable
  • 106 |
  • Increased the number of render jobs that can be in the queue
  • 107 |
  • Improved handling of very long lines.
  • 108 |
109 |

1.1a: Misc fixes

110 |
    111 |
  • Use new BufferedImage instead of UiUtil for Idea 11.x compatibility.
  • 112 |
  • Limited panel injection only to text editors
  • 113 |
  • Fixed a bug with split panes not displaying correctly on restart
  • 114 |
115 |

1.1: Small performance and aesthetic improvements

116 |
    117 |
  • Optimized the rendering loop a little
  • 118 |
  • Fixed a bug with scrolling in large files and the reticule not matching the top correctly.
  • 119 |
  • Made character weighting non random, removes the 'film grain' effect when rapidly updating a document.
  • 120 |
121 | 122 |

1.0: Initial release of the plugin:

123 |
    124 |
  • Worker thread for rendering
  • 125 |
  • Color rendering using intelij's tokenizer
  • 126 |
  • Scrollable!
  • 127 |
  • - Embedded into editor window
  • 128 |
129 | ]]>
130 | 131 | 132 | 133 | com.intellij.modules.lang 134 | 135 | 136 | net.vektah.codeglance.CodeGlancePlugin 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 |
150 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/render/CharacterWeight.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2013, Adam Scarr 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | package net.vektah.codeglance.render 27 | 28 | fun GetBottomWeight(c : Int) : Float { 29 | when (c) { 30 | in 0..32 -> return 0.0f 31 | 32 | 33 -> return 0.1779f // = '!' 33 | 34 -> return 0.0000f // = '"' 34 | 35 -> return 0.5714f // = '#' 35 | 36 -> return 0.6160f // = '$' 36 | 37 -> return 0.5423f // = '%' 37 | 38 -> return 0.7204f // = '&' 38 | 39 -> return 0.0000f // = ''' 39 | 40 -> return 0.5134f // = '(' 40 | 41 -> return 0.5089f // = ')' 41 | 42 -> return 0.1042f // = '*' 42 | 43 -> return 0.2286f // = '+' 43 | 44 -> return 0.2983f // = ',' 44 | 45 -> return 0.0000f // = '-' 45 | 46 -> return 0.1777f // = '.' 46 | 47 -> return 0.5943f // = '/' 47 | 48 -> return 0.5831f // = '0' 48 | 49 -> return 0.5143f // = '1' 49 | 50 -> return 0.4724f // = '2' 50 | 51 -> return 0.4715f // = '3' 51 | 52 -> return 0.5712f // = '4' 52 | 53 -> return 0.4421f // = '5' 53 | 54 -> return 0.6080f // = '6' 54 | 55 -> return 0.3399f // = '7' 55 | 56 -> return 0.6481f // = '8' 56 | 57 -> return 0.4235f // = '9' 57 | 58 -> return 0.1777f // = ':' 58 | 59 -> return 0.2983f // = ';' 59 | 60 -> return 0.3509f // = '<' 60 | 61 -> return 0.2857f // = '=' 61 | 62 -> return 0.3942f // = '>' 62 | 63 -> return 0.1766f // = '?' 63 | 64 -> return 0.7926f // = '@' 64 | 65 -> return 0.6156f // = 'A' 65 | 66 -> return 0.6830f // = 'B' 66 | 67 -> return 0.4296f // = 'C' 67 | 68 -> return 0.6364f // = 'D' 68 | 69 -> return 0.5143f // = 'E' 69 | 70 -> return 0.3429f // = 'F' 70 | 71 -> return 0.6476f // = 'G' 71 | 72 -> return 0.6857f // = 'H' 72 | 73 -> return 0.5714f // = 'I' 73 | 74 -> return 0.4083f // = 'J' 74 | 75 -> return 0.6617f // = 'K' 75 | 76 -> return 0.5143f // = 'L' 76 | 77 -> return 0.3139f // = 'M' 77 | 78 -> return 0.5416f // = 'N' 78 | 79 -> return 0.5759f // = 'O' 79 | 80 -> return 0.4576f // = 'P' 80 | 81 -> return 0.7689f // = 'Q' 81 | 82 -> return 0.6761f // = 'R' 82 | 83 -> return 0.4923f // = 'S' 83 | 84 -> return 0.3429f // = 'T' 84 | 85 -> return 0.6283f // = 'U' 85 | 86 -> return 0.3516f // = 'V' 86 | 87 -> return 0.5564f // = 'W' 87 | 88 -> return 0.5479f // = 'X' 88 | 89 -> return 0.3460f // = 'Y' 89 | 90 -> return 0.4715f // = 'Z' 90 | 91 -> return 0.6857f // = '[' 91 | 92 -> return 0.5941f // = '\' 92 | 93 -> return 0.6857f // = ']' 93 | 94 -> return 0.0000f // = '^' 94 | 95 -> return 0.3429f // = '_' 95 | 96 -> return 0.0000f // = '`' 96 | 97 -> return 0.6853f // = 'a' 97 | 98 -> return 0.6431f // = 'b' 98 | 99 -> return 0.3966f // = 'c' 99 | 100 -> return 0.6521f // = 'd' 100 | 101 -> return 0.6844f // = 'e' 101 | 102 -> return 0.3429f // = 'f' 102 | 103 -> return 0.9927f // = 'g' 103 | 104 -> return 0.6857f // = 'h' 104 | 105 -> return 0.4052f // = 'i' 105 | 106 -> return 0.6463f // = 'j' 106 | 107 -> return 0.6015f // = 'k' 107 | 108 -> return 0.4013f // = 'l' 108 | 109 -> return 0.4000f // = 'm' 109 | 110 -> return 0.6857f // = 'n' 110 | 111 -> return 0.5799f // = 'o' 111 | 112 -> return 0.9109f // = 'p' 112 | 113 -> return 0.9154f // = 'q' 113 | 114 -> return 0.3429f // = 'r' 114 | 115 -> return 0.5450f // = 's' 115 | 116 -> return 0.4627f // = 't' 116 | 117 -> return 0.6678f // = 'u' 117 | 118 -> return 0.3538f // = 'v' 118 | 119 -> return 0.6884f // = 'w' 119 | 120 -> return 0.5461f // = 'x' 120 | 121 -> return 0.6521f // = 'y' 121 | 122 -> return 0.5183f // = 'z' 122 | 123 -> return 0.6649f // = '{' 123 | 124 -> return 0.5714f // = '|' 124 | 125 -> return 0.6136f // = '}' 125 | 126 -> return 0.1950f // = '~' 126 | else -> return 0.4f 127 | } 128 | } 129 | 130 | fun GetTopWeight(c : Int) : Float { 131 | when (c) { 132 | in 0..32 -> return 0.0f 133 | 33 -> return 0.2816f // = '!' 134 | 34 -> return 0.4865f // = '"' 135 | 35 -> return 0.4769f // = '#' 136 | 36 -> return 0.5066f // = '$' 137 | 37 -> return 0.4510f // = '%' 138 | 38 -> return 0.2971f // = '&' 139 | 39 -> return 0.3274f // = ''' 140 | 40 -> return 0.4275f // = '(' 141 | 41 -> return 0.4273f // = ')' 142 | 42 -> return 0.3643f // = '*' 143 | 43 -> return 0.1905f // = '+' 144 | 44 -> return 0.0000f // = ',' 145 | 45 -> return 0.0000f // = '-' 146 | 46 -> return 0.0000f // = '.' 147 | 47 -> return 0.3961f // = '/' 148 | 48 -> return 0.5221f // = '0' 149 | 49 -> return 0.4045f // = '1' 150 | 50 -> return 0.3419f // = '2' 151 | 51 -> return 0.3830f // = '3' 152 | 52 -> return 0.4157f // = '4' 153 | 53 -> return 0.4060f // = '5' 154 | 54 -> return 0.3501f // = '6' 155 | 55 -> return 0.4228f // = '7' 156 | 56 -> return 0.5798f // = '8' 157 | 57 -> return 0.5005f // = '9' 158 | 58 -> return 0.1481f // = ':' 159 | 59 -> return 0.1481f // = ';' 160 | 60 -> return 0.3989f // = '<' 161 | 61 -> return 0.2381f // = '=' 162 | 62 -> return 0.4476f // = '>' 163 | 63 -> return 0.3414f // = '?' 164 | 64 -> return 0.3910f // = '@' 165 | 65 -> return 0.3343f // = 'A' 166 | 66 -> return 0.5707f // = 'B' 167 | 67 -> return 0.3498f // = 'C' 168 | 68 -> return 0.5326f // = 'D' 169 | 69 -> return 0.4286f // = 'E' 170 | 70 -> return 0.3810f // = 'F' 171 | 71 -> return 0.3488f // = 'G' 172 | 72 -> return 0.5714f // = 'H' 173 | 73 -> return 0.4762f // = 'I' 174 | 74 -> return 0.3810f // = 'J' 175 | 75 -> return 0.5376f // = 'K' 176 | 76 -> return 0.2857f // = 'L' 177 | 77 -> return 0.4928f // = 'M' 178 | 78 -> return 0.5173f // = 'N' 179 | 79 -> return 0.4781f // = 'O' 180 | 80 -> return 0.5619f // = 'P' 181 | 81 -> return 0.4775f // = 'Q' 182 | 82 -> return 0.5716f // = 'R' 183 | 83 -> return 0.3826f // = 'S' 184 | 84 -> return 0.4762f // = 'T' 185 | 85 -> return 0.5714f // = 'U' 186 | 86 -> return 0.3707f // = 'V' 187 | 87 -> return 0.3128f // = 'W' 188 | 88 -> return 0.4452f // = 'X' 189 | 89 -> return 0.4495f // = 'Y' 190 | 90 -> return 0.4129f // = 'Z' 191 | 91 -> return 0.4762f // = '[' 192 | 92 -> return 0.3959f // = '\' 193 | 93 -> return 0.4762f // = ']' 194 | 94 -> return 0.3647f // = '^' 195 | 95 -> return 0.0000f // = '_' 196 | 96 -> return 0.1359f // = '`' 197 | 97 -> return 0.2439f // = 'a' 198 | 98 -> return 0.5729f // = 'b' 199 | 99 -> return 0.2607f // = 'c' 200 | 100 -> return 0.5729f // = 'd' 201 | 101 -> return 0.3935f // = 'e' 202 | 102 -> return 0.6232f // = 'f' 203 | 103 -> return 0.3488f // = 'g' 204 | 104 -> return 0.5744f // = 'h' 205 | 105 -> return 0.3399f // = 'i' 206 | 106 -> return 0.3399f // = 'j' 207 | 107 -> return 0.5328f // = 'k' 208 | 108 -> return 0.4762f // = 'l' 209 | 109 -> return 0.3447f // = 'm' 210 | 110 -> return 0.4062f // = 'n' 211 | 111 -> return 0.2965f // = 'o' 212 | 112 -> return 0.3511f // = 'p' 213 | 113 -> return 0.3500f // = 'q' 214 | 114 -> return 0.3057f // = 'r' 215 | 115 -> return 0.2893f // = 's' 216 | 116 -> return 0.5238f // = 't' 217 | 117 -> return 0.3810f // = 'u' 218 | 118 -> return 0.2458f // = 'v' 219 | 119 -> return 0.4218f // = 'w' 220 | 120 -> return 0.3266f // = 'x' 221 | 121 -> return 0.3613f // = 'y' 222 | 122 -> return 0.3417f // = 'z' 223 | 123 -> return 0.4424f // = '{' 224 | 124 -> return 0.3810f // = '|' 225 | 125 -> return 0.4006f // = '}' 226 | 126 -> return 0.0000f // = '~' 227 | else -> return 0.4f 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/config/ConfigForm.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 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 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 |
215 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/GlancePanel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2013, Adam Scarr 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | package net.vektah.codeglance 27 | 28 | import com.intellij.openapi.components.ServiceManager 29 | import com.intellij.openapi.editor.colors.ColorKey 30 | import com.intellij.openapi.editor.event.* 31 | import com.intellij.openapi.fileEditor.FileEditor 32 | import com.intellij.openapi.fileEditor.TextEditor 33 | import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory 34 | import com.intellij.openapi.project.Project 35 | import com.intellij.psi.PsiDocumentManager 36 | import com.intellij.ui.JBColor 37 | import net.vektah.codeglance.concurrent.DirtyLock 38 | import net.vektah.codeglance.config.Config 39 | import net.vektah.codeglance.config.ConfigService 40 | import net.vektah.codeglance.render.* 41 | 42 | import javax.swing.* 43 | import java.awt.* 44 | import java.awt.event.* 45 | import java.awt.image.BufferedImage 46 | import java.lang.ref.SoftReference 47 | 48 | /** 49 | * This JPanel gets injected into editor windows and renders a image generated by GlanceFileRenderer 50 | */ 51 | class GlancePanel(private val project: Project, fileEditor: FileEditor, private val container: JPanel, private val runner: TaskRunner) : JPanel(), VisibleAreaListener { 52 | private val editor = (fileEditor as TextEditor).editor 53 | private var mapRef = SoftReference(null) 54 | private val configService = ServiceManager.getService(ConfigService::class.java) 55 | private var config: Config = configService.state!! 56 | private var lastFoldCount = -1 57 | private var buf: BufferedImage? = null 58 | private val renderLock = DirtyLock() 59 | private val scrollstate = ScrollState() 60 | private val scrollbar = Scrollbar(editor, scrollstate) 61 | 62 | // Anonymous Listeners that should be cleaned up. 63 | private val componentListener: ComponentListener 64 | private val documentListener: DocumentListener 65 | private val selectionListener: SelectionListener = SelectionListener { repaint() } 66 | 67 | private val isDisabled: Boolean 68 | get() = config.disabled || editor.document.textLength > config.maxFileSize || editor.document.lineCount < config.minLineCount || container.width < config.minWindowWidth 69 | 70 | private val onConfigChange = { 71 | updateImage() 72 | updateSize() 73 | this@GlancePanel.revalidate() 74 | this@GlancePanel.repaint() 75 | } 76 | 77 | init { 78 | componentListener = object : ComponentAdapter() { 79 | override fun componentResized(componentEvent: ComponentEvent?) { 80 | updateSize() 81 | scrollstate.setVisibleHeight(height) 82 | this@GlancePanel.revalidate() 83 | this@GlancePanel.repaint() 84 | } 85 | } 86 | container.addComponentListener(componentListener) 87 | 88 | documentListener = object : DocumentAdapter() { 89 | override fun documentChanged(documentEvent: DocumentEvent?) { 90 | updateImage() 91 | } 92 | } 93 | editor.document.addDocumentListener(documentListener) 94 | 95 | configService.onChange(onConfigChange) 96 | 97 | editor.scrollingModel.addVisibleAreaListener(this) 98 | 99 | editor.selectionModel.addSelectionListener(selectionListener) 100 | updateSize() 101 | updateImage() 102 | 103 | isOpaque = false 104 | layout = BorderLayout() 105 | add(scrollbar) 106 | } 107 | 108 | 109 | /** 110 | * Adjusts the panels size to be a percentage of the total window 111 | */ 112 | private fun updateSize() { 113 | if (isDisabled) { 114 | preferredSize = Dimension(0, 0) 115 | } else { 116 | val size = Dimension(config.width, 0) 117 | preferredSize = size 118 | } 119 | } 120 | 121 | // the minimap is held by a soft reference so the GC can delete it at any time. 122 | // if its been deleted and we want it again (active tab) we recreate it. 123 | private fun getOrCreateMap() : Minimap? { 124 | var map = mapRef.get() 125 | 126 | if (map == null) { 127 | map = Minimap(configService.state!!) 128 | mapRef = SoftReference(map) 129 | } 130 | 131 | return map 132 | } 133 | 134 | /** 135 | * Fires off a new task to the worker thread. This should only be called from the ui thread. 136 | */ 137 | private fun updateImage() { 138 | if (isDisabled) return 139 | if (project.isDisposed) return 140 | 141 | val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return 142 | 143 | val map = getOrCreateMap() ?: return 144 | if (!renderLock.acquire()) return 145 | 146 | val hl = SyntaxHighlighterFactory.getSyntaxHighlighter(file.language, project, file.virtualFile) 147 | 148 | val text = editor.document.text 149 | val folds = Folds(editor.foldingModel.allFoldRegions) 150 | 151 | runner.run { 152 | map.update(text, editor.colorsScheme, hl, folds) 153 | scrollstate.setDocumentSize(config.width, map.height) 154 | 155 | renderLock.release() 156 | 157 | if (renderLock.dirty) { 158 | updateImageSoon() 159 | renderLock.clean() 160 | } 161 | 162 | repaint() 163 | } 164 | } 165 | 166 | private fun updateImageSoon() = SwingUtilities.invokeLater { updateImage() } 167 | 168 | fun paintLast(gfx: Graphics?) { 169 | val g = gfx as Graphics2D 170 | 171 | 172 | if (buf != null) { 173 | g.drawImage(buf, 174 | 0, 0, buf!!.width, buf!!.height, 175 | 0, 0, buf!!.width, buf!!.height, 176 | null) 177 | } 178 | paintSelections(g) 179 | scrollbar.paint(gfx) 180 | } 181 | 182 | override fun paint(gfx: Graphics?) { 183 | if (renderLock.locked) { 184 | paintLast(gfx) 185 | return 186 | } 187 | 188 | val minimap = mapRef.get() 189 | if (minimap == null) { 190 | updateImageSoon() 191 | paintLast(gfx) 192 | return 193 | } 194 | 195 | if (buf == null || buf?.width!! < width || buf?.height!! < height) { 196 | buf = BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR) 197 | } 198 | 199 | val g = buf!!.createGraphics() 200 | 201 | g.composite = AlphaComposite.getInstance(AlphaComposite.CLEAR) 202 | g.fillRect(0, 0, width, height) 203 | g.composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER) 204 | 205 | if (editor.document.textLength != 0) { 206 | g.drawImage( 207 | minimap.img, 208 | 0, 0, scrollstate.documentWidth, scrollstate.drawHeight, 209 | 0, scrollstate.visibleStart, scrollstate.documentWidth, scrollstate.visibleEnd, 210 | null 211 | ) 212 | } 213 | 214 | paintSelections(gfx as Graphics2D) 215 | gfx.drawImage(buf, 0, 0, null) 216 | scrollbar.paint(gfx) 217 | } 218 | 219 | private fun paintSelection(g: Graphics2D, startByte: Int, endByte: Int) { 220 | val start = editor.offsetToVisualPosition(startByte) 221 | val end = editor.offsetToVisualPosition(endByte) 222 | 223 | val sX = start.column 224 | val sY = (start.line + 1) * config.pixelsPerLine - scrollstate.visibleStart 225 | val eX = end.column 226 | val eY = (end.line + 1) * config.pixelsPerLine - scrollstate.visibleStart 227 | 228 | g.composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.80f) 229 | g.color = editor.colorsScheme.getColor(ColorKey.createColorKey("SELECTION_BACKGROUND", JBColor.BLUE)) 230 | 231 | // Single line is real easy 232 | if (start.line == end.line) { 233 | g.fillRect( 234 | sX, 235 | sY, 236 | eX - sX, 237 | config.pixelsPerLine 238 | ) 239 | } else { 240 | // Draw the line leading in 241 | g.fillRect(sX, sY, width - sX, config.pixelsPerLine) 242 | 243 | // Then the line at the end 244 | g.fillRect(0, eY, eX, config.pixelsPerLine) 245 | 246 | if (eY + config.pixelsPerLine != sY) { 247 | // And if there is anything in between, fill it in 248 | g.fillRect(0, sY + config.pixelsPerLine, width, eY - sY - config.pixelsPerLine) 249 | } 250 | } 251 | } 252 | 253 | private fun paintSelections(g: Graphics2D) { 254 | paintSelection(g, editor.selectionModel.selectionStart, editor.selectionModel.selectionEnd) 255 | 256 | for ((index, start) in editor.selectionModel.blockSelectionStarts.withIndex()) { 257 | paintSelection(g, start, editor.selectionModel.blockSelectionEnds[index]) 258 | } 259 | } 260 | 261 | override fun visibleAreaChanged(visibleAreaEvent: VisibleAreaEvent) { 262 | // TODO pending http://youtrack.jetbrains.com/issue/IDEABKL-1141 - once fixed this should be a listener 263 | var currentFoldCount = 0 264 | for (fold in editor.foldingModel.allFoldRegions) { 265 | if (!fold.isExpanded) { 266 | currentFoldCount++ 267 | } 268 | } 269 | 270 | val visibleArea = editor.scrollingModel.visibleArea 271 | val factor = scrollstate.documentHeight.toDouble() / editor.contentComponent.height 272 | 273 | scrollstate.setViewportArea((factor * visibleArea.y).toInt(), (factor * visibleArea.height).toInt()) 274 | scrollstate.setVisibleHeight(height) 275 | 276 | if (currentFoldCount != lastFoldCount) { 277 | updateImage() 278 | } 279 | 280 | lastFoldCount = currentFoldCount 281 | 282 | updateSize() 283 | repaint() 284 | } 285 | 286 | fun onClose() { 287 | container.removeComponentListener(componentListener) 288 | editor.document.removeDocumentListener(documentListener) 289 | editor.selectionModel.removeSelectionListener(selectionListener) 290 | remove(scrollbar) 291 | 292 | mapRef.clear() 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/render/Minimap.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2013, Adam Scarr 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | package net.vektah.codeglance.render 27 | 28 | import com.intellij.openapi.diagnostic.Logger 29 | import com.intellij.openapi.editor.FoldRegion 30 | import com.intellij.openapi.editor.colors.EditorColorsScheme 31 | import com.intellij.openapi.fileTypes.SyntaxHighlighter 32 | import com.intellij.psi.tree.IElementType 33 | import net.vektah.codeglance.config.Config 34 | 35 | import java.awt.* 36 | import java.awt.image.BufferedImage 37 | import java.util.ArrayList 38 | 39 | /** 40 | * A rendered minimap of a document 41 | */ 42 | class Minimap(private val config: Config) { 43 | var img: BufferedImage? = null 44 | var height: Int = 0 45 | private val logger = Logger.getInstance(javaClass) 46 | private var line_endings: ArrayList? = null 47 | 48 | /** 49 | * Scans over the entire document once to work out the required dimensions then rebuilds the image if necessary. 50 | 51 | * Because java chars are UTF-8 16 bit chars this function should be UTF safe in the 2 byte range, which is all intellij 52 | * seems to handle anyway.... 53 | */ 54 | fun updateDimensions(text: CharSequence, folds: Folds) { 55 | var line_length = 0 // The current line length 56 | var longest_line = 1 // The longest line in the document 57 | var lines = 1 // The total number of lines in the document 58 | var last: Char = ' ' 59 | var ch: Char 60 | 61 | val line_endings = ArrayList() 62 | // Magical first line 63 | line_endings.add(-1) 64 | 65 | var i = 0 66 | val len = text.length 67 | while (i < len) { 68 | if (folds.isFolded(i)) { 69 | i++ 70 | continue 71 | } 72 | 73 | ch = text[i] 74 | 75 | if (ch == '\n' || (ch == '\r' && last != '\n')) { 76 | line_endings.add(i) 77 | lines++ 78 | if (line_length > longest_line) longest_line = line_length 79 | line_length = 0 80 | } else if (ch == '\t') { 81 | line_length += 4 82 | } else { 83 | line_length++ 84 | } 85 | 86 | last = ch 87 | i++ 88 | } 89 | // If there is no final newline add one. 90 | if (line_endings[line_endings.size - 1] !== text.length - 1) line_endings.add(text.length - 1) 91 | 92 | this.line_endings = line_endings 93 | height = (lines + 1) * config.pixelsPerLine 94 | 95 | // If the image is too small to represent the entire document now then regenerate it 96 | // TODO: Copy old image when incremental update is added. 97 | if (img == null || img!!.height < height || img!!.width < config.width) { 98 | if (img != null) img!!.flush() 99 | // Create an image that is a bit bigger then the one we need so we don't need to re-create it again soon. 100 | // Documents can get big, so rather then relative sizes lets just add a fixed amount on. 101 | img = BufferedImage(config.width, height + 100 * config.pixelsPerLine, BufferedImage.TYPE_4BYTE_ABGR) 102 | logger.debug("Created new image") 103 | } 104 | } 105 | 106 | /** 107 | * Binary search for a line ending. 108 | * @param i character offset from start of document 109 | * * 110 | * @return 3 element array, [line_number, o] 111 | */ 112 | fun getLine(i: Int): LineInfo { 113 | // We can get called before the line scan has been done. Just return the first line. 114 | if (line_endings == null) return NO_LINES 115 | if (line_endings!!.size == 0) return NO_LINES 116 | val lines = line_endings!![line_endings!!.size - 1] 117 | 118 | // Dummy entries if there are no lines 119 | if (line_endings!!.size == 0) return NO_LINES 120 | if (line_endings!!.size == 1) return NO_LINES 121 | if (line_endings!!.size == 2) return LineInfo(1, line_endings!![0] + 1, line_endings!![1]) 122 | 123 | var index_min = 0 124 | var index_max = line_endings!!.size - 1 125 | var index_mid: Int 126 | var value: Int 127 | 128 | val clampedI = clamp(i, 0, lines) 129 | 130 | while (true) { 131 | index_mid = Math.floor(((index_min + index_max) / 2.0f).toDouble()).toInt() // Key space is pretty linear, might be able to use that to scale our next point. 132 | value = line_endings!![index_mid] 133 | 134 | if (value < clampedI) { 135 | if (clampedI < line_endings!![index_mid + 1]) return LineInfo(index_mid + 1, value + 1, line_endings!![index_mid + 1]) 136 | 137 | index_min = index_mid + 1 138 | } else if (clampedI < value) { 139 | if (line_endings!![index_mid - 1] < clampedI) return LineInfo(index_mid, line_endings!![index_mid - 1] + 1, value) 140 | 141 | index_max = index_mid - 1 142 | } else { 143 | // character at i is actually a newline, so grab the line before it. 144 | return LineInfo(index_mid, line_endings!![index_mid - 1] + 1, clampedI) 145 | } 146 | } 147 | } 148 | 149 | /** 150 | * Works out the color a token should be rendered in. 151 | 152 | * @param element The element to get the color for 153 | * * 154 | * @param hl the syntax highlighter for this document 155 | * * 156 | * @param colorScheme the users color scheme 157 | * * 158 | * @return the RGB color to use for the given element 159 | */ 160 | private fun getColorForElementType(element: IElementType, hl: SyntaxHighlighter, colorScheme: EditorColorsScheme): Int { 161 | var color = colorScheme.defaultForeground.rgb 162 | var tmp: Color? 163 | val attributes = hl.getTokenHighlights(element) 164 | for (attribute in attributes) { 165 | val attr = colorScheme.getAttributes(attribute) 166 | if (attr != null) { 167 | tmp = attr.foregroundColor 168 | if (tmp != null) color = tmp.rgb 169 | } 170 | } 171 | 172 | return color 173 | } 174 | 175 | /** 176 | * Internal worker function to update the minimap image 177 | 178 | * @param text The entire text of the document to render 179 | * * 180 | * @param colorScheme The users color scheme 181 | * * 182 | * @param hl The syntax highlighter to use for the language this document is in. 183 | */ 184 | fun update(text: CharSequence, colorScheme: EditorColorsScheme, hl: SyntaxHighlighter, folds: Folds) { 185 | logger.debug("Updating file image.") 186 | updateDimensions(text, folds) 187 | 188 | var color: Int 189 | var ch: Char 190 | var startLine: LineInfo 191 | val lexer = hl.highlightingLexer 192 | var tokenType: IElementType? 193 | 194 | val g = img!!.graphics as Graphics2D 195 | g.composite = CLEAR 196 | g.fillRect(0, 0, img!!.width, img!!.height) 197 | 198 | lexer.start(text) 199 | tokenType = lexer.tokenType 200 | 201 | var x: Int 202 | var y: Int 203 | while (tokenType != null) { 204 | val start = lexer.tokenStart 205 | startLine = getLine(start) 206 | y = startLine.number * config.pixelsPerLine 207 | 208 | color = getColorForElementType(tokenType, hl, colorScheme) 209 | 210 | // Pre-loop to count whitespace from start of line. 211 | x = 0 212 | for (i in startLine.begin..start - 1) { 213 | // Dont count lines inside of folded regions. 214 | if (folds.isFolded(i)) { 215 | continue 216 | } 217 | 218 | if (text[i] == '\t') { 219 | x += 4 220 | } else { 221 | x += 1 222 | } 223 | 224 | // Abort if this line is getting to long... 225 | if (x > config.width) break 226 | } 227 | 228 | // Render whole token, make sure multi lines are handled gracefully. 229 | for (i in start..lexer.tokenEnd - 1) { 230 | // Don't render folds. 231 | if (folds.isFolded(i)) continue 232 | // Watch out for tokens that extend past the document... bad plugins? see issue #138 233 | if (i >= text.length) return 234 | 235 | ch = text[i] 236 | 237 | if (ch == '\n') { 238 | x = 0 239 | y += config.pixelsPerLine 240 | } else if (ch == '\t') { 241 | x += 4 242 | } else { 243 | x += 1 244 | } 245 | 246 | if (0 <= x && x < img!!.width && 0 <= y && y + config.pixelsPerLine < img!!.height) { 247 | if (config.clean) { 248 | renderClean(x, y, text[i].toInt(), color) 249 | } else { 250 | renderAccurate(x, y, text[i].toInt(), color) 251 | } 252 | } 253 | } 254 | 255 | lexer.advance() 256 | tokenType = lexer.tokenType 257 | } 258 | } 259 | 260 | private fun renderClean(x: Int, y: Int, char: Int, color: Int) { 261 | val weight = when (char) { 262 | in 0..32 -> 0.0f 263 | in 33..126 -> 0.8f 264 | else -> 0.4f 265 | } 266 | 267 | if (weight == 0.0f) return 268 | 269 | when (config.pixelsPerLine) { 270 | 1 -> // Cant show whitespace between lines any more. This looks rather ugly... 271 | setPixel(x, y + 1, color, weight * 0.6f) 272 | 273 | 2 -> { 274 | // Two lines we make the top line a little lighter to give the illusion of whitespace between lines. 275 | setPixel(x, y, color, weight * 0.3f) 276 | setPixel(x, y + 1, color, weight * 0.6f) 277 | } 278 | 3 -> { 279 | // Three lines we make the top nearly empty, and fade the bottom a little too 280 | setPixel(x, y, color, weight * 0.1f) 281 | setPixel(x, y + 1, color, weight * 0.6f) 282 | setPixel(x, y + 2, color, weight * 0.6f) 283 | } 284 | 4 -> { 285 | // Empty top line, Nice blend for everything else 286 | setPixel(x, y + 1, color, weight * 0.6f) 287 | setPixel(x, y + 2, color, weight * 0.6f) 288 | setPixel(x, y + 3, color, weight * 0.6f) 289 | } 290 | } 291 | } 292 | 293 | private fun renderAccurate(x: Int, y: Int, char: Int, color: Int) { 294 | val topWeight = GetTopWeight(char) 295 | val bottomWeight = GetBottomWeight(char) 296 | // No point rendering non visible characters. 297 | if (topWeight == 0.0f && bottomWeight == 0.0f) return 298 | 299 | when (config.pixelsPerLine) { 300 | 1 -> // Cant show whitespace between lines any more. This looks rather ugly... 301 | setPixel(x, y + 1, color, ((topWeight + bottomWeight) / 2.0).toFloat()) 302 | 303 | 2 -> { 304 | // Two lines we make the top line a little lighter to give the illusion of whitespace between lines. 305 | setPixel(x, y, color, topWeight * 0.5f) 306 | setPixel(x, y + 1, color, bottomWeight) 307 | } 308 | 3 -> { 309 | // Three lines we make the top nearly empty, and fade the bottom a little too 310 | setPixel(x, y, color, topWeight * 0.3f) 311 | setPixel(x, y + 1, color, ((topWeight + bottomWeight) / 2.0).toFloat()) 312 | setPixel(x, y + 2, color, bottomWeight * 0.7f) 313 | } 314 | 4 -> { 315 | // Empty top line, Nice blend for everything else 316 | setPixel(x, y + 1, color, topWeight) 317 | setPixel(x, y + 2, color, ((topWeight + bottomWeight) / 2.0).toFloat()) 318 | setPixel(x, y + 3, color, bottomWeight) 319 | } 320 | } 321 | } 322 | 323 | /** 324 | * mask out the alpha component and set it to the given value. 325 | * @param color Color A 326 | * * 327 | * @param alpha alpha percent from 0-1. 328 | * * 329 | * @return int color 330 | */ 331 | private fun setPixel(x: Int, y: Int, color: Int, alpha: Float) { 332 | var a = alpha 333 | if (a > 1) a = color.toFloat() 334 | if (a < 0) a = 0f 335 | 336 | // abgr is backwards? 337 | unpackedColor[3] = (a * 255).toInt() 338 | unpackedColor[0] = (color and 16711680) shr 16 339 | unpackedColor[1] = (color and 65280) shr 8 340 | unpackedColor[2] = (color and 255) 341 | 342 | img!!.raster.setPixel(x, y, unpackedColor) 343 | } 344 | 345 | class LineInfo internal constructor(var number: Int, var begin: Int, var end: Int) 346 | 347 | companion object { 348 | private val CLEAR = AlphaComposite.getInstance(AlphaComposite.CLEAR) 349 | private val unpackedColor = IntArray(4) 350 | private val NO_LINES = LineInfo(1, 0, 0) 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/config/ConfigForm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2013, Adam Scarr 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | package net.vektah.codeglance.config; 27 | 28 | import com.intellij.uiDesigner.core.GridConstraints; 29 | import com.intellij.uiDesigner.core.GridLayoutManager; 30 | import com.intellij.uiDesigner.core.Spacer; 31 | 32 | import javax.swing.*; 33 | import java.awt.*; 34 | 35 | @SuppressWarnings("unchecked") 36 | public class ConfigForm { 37 | private JComboBox pixelsPerLine; 38 | private JPanel rootPanel; 39 | private JCheckBox disabled; 40 | private JComboBox jumpToPosition; 41 | private JComboBox clickStyle; 42 | private JTextField width; 43 | private JTextField viewportColor; 44 | private JTextField minLinesCount; 45 | private JTextField minWindowWidth; 46 | private JComboBox renderStyle; 47 | private JComboBox alignment; 48 | private JCheckBox locked; 49 | 50 | public ConfigForm() { 51 | pixelsPerLine.setModel(new DefaultComboBoxModel(new Integer[]{1, 2, 3, 4})); 52 | jumpToPosition.setModel(new DefaultComboBoxModel(new String[]{"Mouse Down", "Mouse Up"})); 53 | clickStyle.setModel(new DefaultComboBoxModel(new String[]{"Scrollbar (old sublime)", "To Text (new sublime)"})); 54 | renderStyle.setModel(new DefaultComboBoxModel(new String[]{"Clean", "Accurate"})); 55 | alignment.setModel(new DefaultComboBoxModel(new String[]{"Right", "Left"})); 56 | } 57 | 58 | public JPanel getRoot() { 59 | return rootPanel; 60 | } 61 | 62 | public int getPixelsPerLine() { 63 | return (Integer) pixelsPerLine.getSelectedItem(); 64 | } 65 | 66 | public void setPixelsPerLine(int pixelsPerLine) { 67 | this.pixelsPerLine.setSelectedIndex(pixelsPerLine - 1); 68 | } 69 | 70 | public boolean isDisabled() { 71 | return disabled.getModel().isSelected(); 72 | } 73 | 74 | public void setDisabled(boolean isDisabled) { 75 | disabled.getModel().setSelected(isDisabled); 76 | } 77 | 78 | public boolean isLocked() { 79 | return locked.getModel().isSelected(); 80 | } 81 | 82 | public void setLocked(boolean isLocked) { 83 | locked.getModel().setSelected(isLocked); 84 | } 85 | 86 | public boolean jumpOnMouseDown() { 87 | return jumpToPosition.getSelectedIndex() == 0; 88 | } 89 | 90 | public void setJumpOnMouseDown(boolean jump) { 91 | jumpToPosition.setSelectedIndex(jump ? 0 : 1); 92 | } 93 | 94 | public boolean percentageBasedClick() { 95 | return clickStyle.getSelectedIndex() == 0; 96 | } 97 | 98 | public void setPercentageBasedClick(boolean click) { 99 | clickStyle.setSelectedIndex(click ? 0 : 1); 100 | } 101 | 102 | public String getViewportColor() { 103 | return viewportColor.getText(); 104 | } 105 | 106 | public void setViewportColor(String color) { 107 | viewportColor.setText(color); 108 | } 109 | 110 | public boolean getCleanStyle() { 111 | return renderStyle.getSelectedIndex() == 0; 112 | } 113 | 114 | public void setCleanStyle(boolean isClean) { 115 | renderStyle.setSelectedIndex(isClean ? 0 : 1); 116 | } 117 | 118 | public boolean isRightAligned() { 119 | return alignment.getSelectedIndex() == 0; 120 | } 121 | 122 | public void setRightAligned(boolean isRightAligned) { 123 | renderStyle.setSelectedIndex(isRightAligned ? 0 : 1); 124 | } 125 | 126 | public int getWidth() { 127 | try { 128 | return Integer.parseInt(width.getText()); 129 | } catch (NumberFormatException e) { 130 | return 100; 131 | } 132 | } 133 | 134 | public void setWidth(int width) { 135 | this.width.setText(Integer.toString(width)); 136 | } 137 | 138 | public int getMinLinesCount() { 139 | try { 140 | return Integer.parseInt(minLinesCount.getText()); 141 | } catch (NumberFormatException e) { 142 | return 85; 143 | } 144 | } 145 | 146 | public void setMinLinesCount(int minLinesCount) { 147 | this.minLinesCount.setText(Integer.toString(minLinesCount)); 148 | } 149 | 150 | public int getMinWindowWidth() { 151 | try { 152 | return Integer.parseInt(minWindowWidth.getText()); 153 | } catch (NumberFormatException e) { 154 | return 0; 155 | } 156 | } 157 | 158 | public void setMinWindowWidth(int minWindowWidth) { 159 | this.minWindowWidth.setText(Integer.toString(minWindowWidth)); 160 | } 161 | 162 | { 163 | // GUI initializer generated by IntelliJ IDEA GUI Designer 164 | // >>> IMPORTANT!! <<< 165 | // DO NOT EDIT OR ADD ANY CODE HERE! 166 | $$$setupUI$$$(); 167 | } 168 | 169 | /** 170 | * Method generated by IntelliJ IDEA GUI Designer 171 | * >>> IMPORTANT!! <<< 172 | * DO NOT edit this method OR call it in your code! 173 | * 174 | * @noinspection ALL 175 | */ 176 | private void $$$setupUI$$$() { 177 | rootPanel = new JPanel(); 178 | rootPanel.setLayout(new GridLayoutManager(11, 4, new Insets(0, 0, 0, 0), -1, -1)); 179 | final JLabel label1 = new JLabel(); 180 | label1.setText("Pixels Per Line:"); 181 | label1.setDisplayedMnemonic('P'); 182 | label1.setDisplayedMnemonicIndex(0); 183 | rootPanel.add(label1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 184 | pixelsPerLine = new JComboBox(); 185 | final DefaultComboBoxModel defaultComboBoxModel1 = new DefaultComboBoxModel(); 186 | defaultComboBoxModel1.addElement("1"); 187 | defaultComboBoxModel1.addElement("2"); 188 | defaultComboBoxModel1.addElement("3"); 189 | defaultComboBoxModel1.addElement("4"); 190 | pixelsPerLine.setModel(defaultComboBoxModel1); 191 | rootPanel.add(pixelsPerLine, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(200, 24), null, 0, false)); 192 | final Spacer spacer1 = new Spacer(); 193 | rootPanel.add(spacer1, new GridConstraints(0, 3, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false)); 194 | final JLabel label2 = new JLabel(); 195 | label2.setText("Jump to position on:"); 196 | rootPanel.add(label2, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 197 | jumpToPosition = new JComboBox(); 198 | final DefaultComboBoxModel defaultComboBoxModel2 = new DefaultComboBoxModel(); 199 | defaultComboBoxModel2.addElement("Mouse Down"); 200 | defaultComboBoxModel2.addElement("Mouse Up"); 201 | jumpToPosition.setModel(defaultComboBoxModel2); 202 | rootPanel.add(jumpToPosition, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 203 | final JLabel label3 = new JLabel(); 204 | label3.setText("Disabled:"); 205 | rootPanel.add(label3, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 206 | disabled = new JCheckBox(); 207 | disabled.setText(""); 208 | rootPanel.add(disabled, new GridConstraints(3, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 209 | final JLabel label4 = new JLabel(); 210 | label4.setText("Click style"); 211 | rootPanel.add(label4, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 212 | clickStyle = new JComboBox(); 213 | final DefaultComboBoxModel defaultComboBoxModel3 = new DefaultComboBoxModel(); 214 | defaultComboBoxModel3.addElement("Scrollbar (old sublime)"); 215 | defaultComboBoxModel3.addElement("To Text (new sublime)"); 216 | clickStyle.setModel(defaultComboBoxModel3); 217 | rootPanel.add(clickStyle, new GridConstraints(2, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 218 | width = new JTextField(); 219 | rootPanel.add(width, new GridConstraints(4, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false)); 220 | final JLabel label5 = new JLabel(); 221 | label5.setText("Width:"); 222 | rootPanel.add(label5, new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 223 | final JLabel label6 = new JLabel(); 224 | label6.setText("Viewport Color"); 225 | rootPanel.add(label6, new GridConstraints(5, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 226 | viewportColor = new JTextField(); 227 | rootPanel.add(viewportColor, new GridConstraints(5, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false)); 228 | final JLabel label7 = new JLabel(); 229 | label7.setText("Minimum lines count:"); 230 | label7.setToolTipText("Minimum number of lines to show minimap."); 231 | rootPanel.add(label7, new GridConstraints(6, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 232 | final Spacer spacer2 = new Spacer(); 233 | rootPanel.add(spacer2, new GridConstraints(10, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); 234 | minLinesCount = new JTextField(); 235 | minLinesCount.setText(""); 236 | rootPanel.add(minLinesCount, new GridConstraints(6, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false)); 237 | final JLabel label8 = new JLabel(); 238 | label8.setText("Render Style"); 239 | rootPanel.add(label8, new GridConstraints(8, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 240 | renderStyle = new JComboBox(); 241 | final DefaultComboBoxModel defaultComboBoxModel4 = new DefaultComboBoxModel(); 242 | defaultComboBoxModel4.addElement("Clean"); 243 | defaultComboBoxModel4.addElement("Accurate"); 244 | renderStyle.setModel(defaultComboBoxModel4); 245 | rootPanel.add(renderStyle, new GridConstraints(8, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 246 | final JLabel label9 = new JLabel(); 247 | label9.setText("Alignment (restart)"); 248 | rootPanel.add(label9, new GridConstraints(9, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 249 | alignment = new JComboBox(); 250 | final DefaultComboBoxModel defaultComboBoxModel5 = new DefaultComboBoxModel(); 251 | defaultComboBoxModel5.addElement("Right"); 252 | defaultComboBoxModel5.addElement("Left"); 253 | alignment.setModel(defaultComboBoxModel5); 254 | rootPanel.add(alignment, new GridConstraints(9, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 255 | final JLabel label10 = new JLabel(); 256 | label10.setText("Minimum Window Width:"); 257 | rootPanel.add(label10, new GridConstraints(7, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 258 | minWindowWidth = new JTextField(); 259 | rootPanel.add(minWindowWidth, new GridConstraints(7, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false)); 260 | locked = new JCheckBox(); 261 | locked.setText("lock"); 262 | rootPanel.add(locked, new GridConstraints(4, 2, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 263 | label1.setLabelFor(pixelsPerLine); 264 | } 265 | 266 | /** 267 | * @noinspection ALL 268 | */ 269 | public JComponent $$$getRootComponent$$$() { 270 | return rootPanel; 271 | } 272 | } 273 | --------------------------------------------------------------------------------