├── .travis.yml ├── pub ├── dark.png └── light.png ├── .gitignore ├── src ├── main │ ├── python │ │ └── CharacterWeight.py │ ├── java │ │ └── net │ │ │ └── vektah │ │ │ └── codeglance │ │ │ ├── config │ │ │ ├── ConfigChangeListener.java │ │ │ ├── Config.java │ │ │ ├── ConfigService.java │ │ │ ├── ConfigEntry.java │ │ │ ├── ConfigForm.form │ │ │ └── ConfigForm.java │ │ │ ├── actions │ │ │ └── ShowHideAction.java │ │ │ ├── render │ │ │ ├── RenderTask.java │ │ │ ├── TaskRunner.java │ │ │ ├── CoordinateHelper.java │ │ │ ├── CharacterWeight.java │ │ │ └── Minimap.java │ │ │ ├── Observable.java │ │ │ ├── CodeGlancePlugin.java │ │ │ ├── EditorPanelInjector.java │ │ │ └── GlancePanel.java │ └── resources │ │ └── META-INF │ │ └── plugin.xml └── test │ └── java │ └── net │ └── vektah │ └── codeglance │ └── render │ ├── TaskRunnerTest.java │ ├── CharacterWeightTest.java │ └── GlanceImageTest.java ├── readme.md └── pom.xml /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: openjdk6 -------------------------------------------------------------------------------- /pub/dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/CodeGlance/master/pub/dark.png -------------------------------------------------------------------------------- /pub/light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/CodeGlance/master/pub/light.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /target 3 | /codeglance.xml 4 | *.iml 5 | *.jar 6 | /module_codeglance.xml 7 | out 8 | -------------------------------------------------------------------------------- /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("/usr/share/fonts/truetype/ubuntu-font-family/UbuntuMono-B.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 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | CodeGlance [![Build Status](https://travis-ci.org/Vektah/CodeGlance.png?branch=master)](https://travis-ci.org/Vektah/CodeGlance) 2 | ========== 3 | 4 | Plugin Repository: http://plugins.jetbrains.com/plugin/7275 5 | Latest build: http://public.vektah.net/codeglance/net/vektah/CodeGlance/1.4.0/CodeGlance-1.4.0.jar 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 | Dark: 17 | ![Dracula](https://raw.github.com/Vektah/CodeGlance/master/pub/dark.png) 18 | 19 | Light: 20 | ![Default](https://raw.github.com/Vektah/CodeGlance/master/pub/light.png) 21 | 22 | 23 | Building using maven 24 | ==================== 25 | With maven installed building the plugin yourself is a simple as: 26 | ``` 27 | git clone https://github.com/Vektah/CodeGlance 28 | cd CodeGlance 29 | mvn package 30 | ``` 31 | After it finsihes downloading dependencies and building you should now have 32 | a **CodeGlance-${VERSION}.jar** in the directory. This can be tested in intellij by 33 | going to settings->plugins->install from disk. 34 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/config/ConfigChangeListener.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 | public interface ConfigChangeListener { 29 | public void configChanged(); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/config/Config.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 | public class Config { 29 | public int pixelsPerLine = 3; 30 | public boolean disabled = false; 31 | public boolean jumpOnMouseDown = true; 32 | public boolean percentageBasedClick = false; 33 | public int width = 110; 34 | public String viewportColor = "A0A0A0"; 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/net/vektah/codeglance/render/TaskRunnerTest.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.render; 27 | 28 | import org.testng.annotations.Test; 29 | 30 | import static org.mockito.Mockito.*; 31 | 32 | public class TaskRunnerTest { 33 | @Test public void testTimerTask() { 34 | TaskRunner runner = new TaskRunner(); 35 | new Thread(runner).start(); 36 | 37 | Runnable task = mock(Runnable.class); 38 | 39 | runner.add(task); 40 | 41 | try { 42 | Thread.sleep(50); 43 | } catch (InterruptedException e) { 44 | e.printStackTrace(); 45 | } 46 | 47 | runner.stop(); 48 | 49 | verify(task).run(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/actions/ShowHideAction.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.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 | public class ShowHideAction extends AnAction { 34 | private ConfigService configService = ServiceManager.getService(ConfigService.class); 35 | 36 | @Override public void actionPerformed(AnActionEvent anActionEvent) { 37 | configService.getState().disabled = !configService.getState().disabled; 38 | configService.dispatch().configChanged(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/config/ConfigService.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.openapi.components.*; 29 | import com.intellij.util.xmlb.XmlSerializerUtil; 30 | import net.vektah.codeglance.Observable; 31 | import org.jetbrains.annotations.Nullable; 32 | 33 | 34 | @State( 35 | name = "CodeGlance", 36 | storages = { 37 | @Storage(id="other", file = StoragePathMacros.APP_CONFIG + "/CodeGlance.xml") 38 | } 39 | ) 40 | public class ConfigService extends Observable implements PersistentStateComponent { 41 | private Config config = new Config(); 42 | 43 | public ConfigService() { 44 | super(ConfigChangeListener.class); 45 | } 46 | 47 | @Nullable @Override public Config getState() { 48 | return config; 49 | } 50 | 51 | @Override public void loadState(Config config) { 52 | XmlSerializerUtil.copyBean(config, this.config); 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/render/RenderTask.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.render; 27 | 28 | import com.intellij.openapi.editor.FoldRegion; 29 | import com.intellij.openapi.editor.colors.EditorColorsScheme; 30 | import com.intellij.openapi.fileTypes.SyntaxHighlighter; 31 | 32 | public class RenderTask implements Runnable { 33 | private Minimap minimap; 34 | private CharSequence text; 35 | private EditorColorsScheme cs; 36 | private SyntaxHighlighter hl; 37 | private final Runnable then; 38 | private FoldRegion[] folding; 39 | 40 | public RenderTask(Minimap minimap, CharSequence text, EditorColorsScheme cs, SyntaxHighlighter hl, FoldRegion[] folding, Runnable then) { 41 | this.minimap = minimap; 42 | this.text = text; 43 | this.cs = cs; 44 | this.hl = hl; 45 | this.then = then; 46 | this.folding = folding; 47 | } 48 | 49 | @Override public void run() { 50 | minimap.update(text, cs, hl, folding); 51 | then.run(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/render/TaskRunner.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.render; 27 | 28 | import com.intellij.openapi.diagnostic.Logger; 29 | 30 | import java.util.concurrent.ArrayBlockingQueue; 31 | 32 | /** 33 | * Runs tasks sequentially in a queue. Thread safe. 34 | */ 35 | public class TaskRunner implements Runnable { 36 | private boolean stop = false; 37 | private ArrayBlockingQueue taskQueue = new ArrayBlockingQueue(100); 38 | private Logger logger = Logger.getInstance(getClass()); 39 | 40 | public void add(Runnable task) { 41 | logger.debug("Added new task"); 42 | taskQueue.add(task); 43 | } 44 | 45 | public void stop() { 46 | stop = true; 47 | } 48 | 49 | @Override public void run() { 50 | while(!stop) { 51 | try { 52 | logger.debug("Starting task"); 53 | taskQueue.take().run(); 54 | logger.debug("Task completed"); 55 | } catch (InterruptedException e) { 56 | return; 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/Observable.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; 27 | 28 | import java.lang.reflect.InvocationHandler; 29 | import java.lang.reflect.Method; 30 | import java.lang.reflect.Proxy; 31 | import java.util.List; 32 | import java.util.concurrent.CopyOnWriteArrayList; 33 | 34 | public class Observable implements InvocationHandler { 35 | private List pool = new CopyOnWriteArrayList(); 36 | private T eventDispatcher; 37 | private Class collectionClass; 38 | 39 | public Observable(Class collectionClass) { 40 | this.collectionClass = collectionClass; 41 | } 42 | 43 | public void add(T observer) { 44 | pool.add(observer); 45 | } 46 | 47 | public boolean remove(T observer) { 48 | return pool.remove(observer); 49 | } 50 | 51 | @SuppressWarnings( "unchecked" ) 52 | public T dispatch() { 53 | if(eventDispatcher == null) { 54 | T dispatcher = (T) Proxy.newProxyInstance( 55 | collectionClass.getClassLoader(), 56 | new Class[]{collectionClass}, 57 | this 58 | ); 59 | 60 | eventDispatcher = dispatcher; 61 | } 62 | 63 | return eventDispatcher; 64 | } 65 | 66 | @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 67 | for(T observer: pool) { 68 | method.invoke(observer, args); 69 | } 70 | 71 | return null; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/CodeGlancePlugin.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; 27 | 28 | import com.intellij.openapi.components.ProjectComponent; 29 | import com.intellij.openapi.diagnostic.Logger; 30 | import com.intellij.openapi.fileEditor.FileEditorManagerListener; 31 | import com.intellij.openapi.project.Project; 32 | import net.vektah.codeglance.render.TaskRunner; 33 | import org.jetbrains.annotations.NotNull; 34 | 35 | /** 36 | * Main plugin 37 | */ 38 | public class CodeGlancePlugin implements ProjectComponent { 39 | private Project project; 40 | private Logger logger = Logger.getInstance(getClass()); 41 | private TaskRunner runner = new TaskRunner(); 42 | private Thread runnerThread = new Thread(runner); 43 | private EditorPanelInjector injector; 44 | 45 | public CodeGlancePlugin(Project project) { 46 | this.project = project; 47 | injector = new EditorPanelInjector(project, runner); 48 | } 49 | 50 | public void initComponent() { 51 | runnerThread.start(); 52 | project.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, injector); 53 | logger.debug("CodeGlance initialized"); 54 | } 55 | 56 | public void disposeComponent() { 57 | runner.stop(); 58 | } 59 | 60 | @NotNull public String getComponentName() { 61 | return "CodeGlancePlugin"; 62 | } 63 | 64 | public void projectOpened() { 65 | // called when project is opened 66 | } 67 | 68 | public void projectClosed() { 69 | // called when project is being closed 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/net/vektah/codeglance/render/CharacterWeightTest.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.render; 27 | 28 | import org.testng.annotations.DataProvider; 29 | import org.testng.annotations.Test; 30 | 31 | import static org.testng.Assert.*; 32 | 33 | /** 34 | * Some basic sanity tests that the weight generation function works OK. 35 | */ 36 | public class CharacterWeightTest { 37 | @Test public void test_lower_boundaries() { 38 | assertEquals(0, CharacterWeight.getTopWeight((char) 0), 0.001); 39 | assertEquals(0, CharacterWeight.getTopWeight((char) 1), 0.001); 40 | assertEquals(0, CharacterWeight.getTopWeight((char) 32), 0.001); 41 | assertNotEquals(0, CharacterWeight.getTopWeight((char) 33)); 42 | assertNotEquals(0, CharacterWeight.getTopWeight((char) 126)); 43 | assertNotEquals(0, CharacterWeight.getTopWeight((char) 127)); 44 | assertNotEquals(0, CharacterWeight.getTopWeight((char) 128)); 45 | } 46 | 47 | @DataProvider(name="Test-Relative-Weights") public static Object[][] testRelativeWeights() { 48 | return new Object[][] { 49 | {'.', ','}, 50 | {'1', '8'}, 51 | {'.', 'a'}, 52 | {',', '1'}, 53 | }; 54 | } 55 | 56 | @Test(dataProvider = "Test-Relative-Weights") public void test_relative_weights_are_sane(char a, char b) { 57 | assertTrue(CharacterWeight.getTopWeight(a) + CharacterWeight.getBottomWeight(a) < CharacterWeight.getTopWeight(b) + CharacterWeight.getBottomWeight(b)); 58 | } 59 | 60 | @Test public void test_known_values() { 61 | assertEquals(0.2458f, CharacterWeight.getTopWeight('v')); 62 | assertEquals(0.3538f, CharacterWeight.getBottomWeight('v')); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | CodeGlance 4 | net.vektah 5 | CodeGlance 6 | 1.4.0 7 | jar 8 | 9 | 10 | 11 | vektah-openapi 12 | 3rd party openapi repository 13 | http://public.vektah.net/maven-intellij-openapi/ 14 | 15 | 16 | 17 | 18 | UTF-8 19 | UTF-8 20 | UTF-8 21 | 1.6 22 | 1.6 23 | true 24 | 25 | 26 | 27 | 28 | ssh-repository 29 | scpexe://public/codeglance 30 | 31 | 32 | 33 | 34 | 35 | 36 | org.apache.maven.plugins 37 | maven-compiler-plugin 38 | 39 | ${project.build.sourceEncoding} 40 | 41 | 42 | 43 | org.apache.maven.plugins 44 | maven-jar-plugin 45 | 46 | ./ 47 | 48 | 49 | 50 | 51 | 52 | 53 | org.apache.maven.wagon 54 | wagon-ssh-external 55 | 56 | 57 | 58 | 59 | 60 | 61 | provided 62 | net.vektah 63 | intellij-openapi 64 | 12.1.4 65 | 66 | 67 | provided 68 | net.vektah 69 | intellij-util 70 | 12.1.4 71 | 72 | 73 | provided 74 | net.vektah 75 | intellij-annotations 76 | 12.1.4 77 | 78 | 79 | net.vektah 80 | provided 81 | intellij-extensions 82 | 12.1.4 83 | 84 | 85 | net.vektah 86 | provided 87 | intellij-forms_rt 88 | 12.1.4 89 | 90 | 91 | net.vektah 92 | provided 93 | intellij-javac2 94 | 12.1.4 95 | 96 | 97 | org.mockito 98 | test 99 | mockito-core 100 | 1.9.5 101 | 102 | 103 | org.testng 104 | test 105 | testng 106 | 6.8.5 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/config/ConfigEntry.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.openapi.components.ServiceManager; 29 | import com.intellij.openapi.options.Configurable; 30 | import com.intellij.openapi.options.ConfigurationException; 31 | import org.jetbrains.annotations.Nls; 32 | import org.jetbrains.annotations.Nullable; 33 | 34 | import javax.swing.*; 35 | 36 | public class ConfigEntry implements Configurable { 37 | private ConfigForm form; 38 | private ConfigService configService = ServiceManager.getService(ConfigService.class); 39 | private Config config = configService.getState(); 40 | 41 | @Nls @Override public String getDisplayName() { 42 | return "CodeGlance"; 43 | } 44 | 45 | @Nullable @Override public String getHelpTopic() { 46 | return "Configuration for the CodeGlance minimap"; 47 | } 48 | 49 | @Nullable @Override public JComponent createComponent() { 50 | form = new ConfigForm(); 51 | reset(); 52 | return form.getRoot(); 53 | } 54 | 55 | @Override public boolean isModified() { 56 | if (form == null) return false; 57 | 58 | return config.pixelsPerLine != form.getPixelsPerLine() || 59 | config.disabled != form.isDisabled() || 60 | config.jumpOnMouseDown != form.jumpOnMouseDown() || 61 | config.percentageBasedClick != form.percentageBasedClick() || 62 | config.width != form.getWidth() || 63 | config.viewportColor != form.getViewportColor(); 64 | } 65 | 66 | @Override public void apply() throws ConfigurationException { 67 | if(form == null) return; 68 | 69 | config.pixelsPerLine = form.getPixelsPerLine(); 70 | config.disabled = form.isDisabled(); 71 | config.jumpOnMouseDown = form.jumpOnMouseDown(); 72 | config.percentageBasedClick = form.percentageBasedClick(); 73 | config.width = form.getWidth(); 74 | 75 | if (form.getViewportColor().length() == 6 && form.getViewportColor().matches("^[a-fA-F0-9]*$")) { 76 | config.viewportColor = form.getViewportColor(); 77 | } else { 78 | config.viewportColor = "A0A0A0"; 79 | } 80 | 81 | configService.dispatch().configChanged(); 82 | } 83 | 84 | @Override public void reset() { 85 | if(form == null) return; 86 | 87 | form.setPixelsPerLine(config.pixelsPerLine); 88 | form.setDisabled(config.disabled); 89 | form.setJumpOnMouseDown(config.jumpOnMouseDown); 90 | form.setPercentageBasedClick(config.percentageBasedClick); 91 | form.setViewportColor(config.viewportColor); 92 | form.setWidth(config.width); 93 | } 94 | 95 | @Override public void disposeUIResources() { 96 | form = null; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | net.vektah.codeglance 3 | CodeGlance 4 | 1.4.0 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.4.0 14 |
    15 |
  • Scrolling while hovering over the overview now works
  • 16 |
  • Add a keybind to show/hide codeglance. Default is control shift G
  • 17 |
  • Display editor selection in real time
  • 18 |
  • Selection is now resizeable
  • 19 |
  • Configurable viewport color
  • 20 |
21 |

1.3.2: Fixed a memory leak

22 |

1.3.1: Fix a NPE in offsetToScreenSpace()

23 |

1.3.0: Folding support

24 |
    25 |
  • Dragging is now relative to the start point. This means less sudden jumping around
  • 26 |
  • This required some fairly heavy changes to the coordinate code.
  • 27 |
28 |

1.2.3: Added scale config item!

29 |
    30 |
  • Fix regression of 'Already disposed'
  • 31 |
  • Added ability to disable CodeGlance without restarting
  • 32 |
33 |

1.2.2: Added scale config item!

34 |
    35 |
  • Fixed an NPE in PhpStorm
  • 36 |
  • Fixed clicking on a section of code in a long file, dragging still behaves the same with percentage based movement.
  • 37 |
  • Pixels per line is now configurable
  • 38 |
39 |

1.2.1: Scale fixes

40 |
    41 |
  • Fixed a rendering issue for osx with vertical stretching.
  • 42 |
  • Fixes stretching of the view area when looking at large files.
  • 43 |
  • Increased viewport visibility a touch.
  • 44 |
45 |

1.2: Smaller with transparency

46 |
    47 |
  • Now uses a transparent selection box and the maximum width is limited to 100 chars. This will probably become configurable
  • 48 |
  • Increased the number of render jobs that can be in the queue
  • 49 |
  • Improved handling of very long lines.
  • 50 |
51 |

1.1a: Misc fixes

52 |
    53 |
  • Use new BufferedImage instead of UiUtil for Idea 11.x compatibility.
  • 54 |
  • Limited panel injection only to text editors
  • 55 |
  • Fixed a bug with split panes not displaying correctly on restart
  • 56 |
57 |

1.1: Small performance and aesthetic improvements

58 |
    59 |
  • Optimized the rendering loop a little
  • 60 |
  • Fixed a bug with scrolling in large files and the reticule not matching the top correctly.
  • 61 |
  • Made character weighting non random, removes the 'film grain' effect when rapidly updating a document.
  • 62 |
63 | 64 |

1.0: Initial release of the plugin:

65 |
    66 |
  • Worker thread for rendering
  • 67 |
  • Color rendering using intelij's tokenizer
  • 68 |
  • Scrollable!
  • 69 |
  • - Embedded into editor window
  • 70 |
71 | ]]>
72 | 73 | 74 | 75 | com.intellij.modules.lang 76 | 77 | 78 | net.vektah.codeglance.CodeGlancePlugin 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 |
92 | -------------------------------------------------------------------------------- /src/test/java/net/vektah/codeglance/render/GlanceImageTest.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.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 static junit.framework.Assert.assertEquals; 36 | 37 | public class GlanceImageTest { 38 | private Minimap img; 39 | private Config config = new Config(); 40 | 41 | @BeforeMethod public void setUp() { 42 | config.pixelsPerLine = 2; 43 | img = new Minimap(config); 44 | } 45 | 46 | @DataProvider(name="Test-Dimensions") public static Object[][] testDimensions() { 47 | return new Object[][] { 48 | {"", 2}, 49 | {"SingleLine", 2}, 50 | {"Multi\nLine", 4}, 51 | {"Line with lots of tabs\n\t\t\t\t\t\t\t\t", 4}, 52 | {"ʳʳʳʳ", 2}, 53 | {"ꬉꬉꬉꬉ", 2}, 54 | }; 55 | } 56 | 57 | @Test(dataProvider = "Test-Dimensions") public void test_calculate_dimensions(CharSequence string, int height) { 58 | img.updateDimensions(string, new FoldRegion[] {}); 59 | assertEquals(height, img.height); 60 | } 61 | 62 | @Test public void test_calculate_dimensions_resize() { 63 | img.updateDimensions("ASDF\nHJKL", new FoldRegion[] {}); 64 | 65 | assertEquals(config.width, img.img.getWidth()); 66 | assertEquals(204, img.img.getHeight()); 67 | 68 | // Only added a little, so image should not get regenerated. 69 | img.updateDimensions("asdfjkl;asdfjkl;\nasdfjlkasdfjkl\nasdfjkl;a;sdfjkl", new FoldRegion[] {}); 70 | 71 | assertEquals(config.width, img.img.getWidth()); 72 | assertEquals(204, img.img.getHeight()); 73 | 74 | // Went over the existing image boundary so a new one should be created. 75 | img.updateDimensions(StringUtil.repeat("\na", 150), new FoldRegion[] {}); 76 | 77 | assertEquals(config.width, img.img.getWidth()); 78 | assertEquals(502, img.img.getHeight()); 79 | } 80 | 81 | @DataProvider(name="Test-Newlines") public static Object[][] testNewlines() { 82 | return new Object[][] { 83 | {"", 0, 1, 0, 0}, 84 | {"1111111111\n2222222222", 0, 1, 0, 10}, // First line 85 | {"1111111111\n2222222222", 5, 1, 0, 10}, // First line 86 | {"1111111111\n2222222222", 10, 1, 0, 10}, // The newline itself 87 | {"1111111111\n2222222222", 15, 2, 11, 20}, // The next line, no trailing new line 88 | {"1111111111\n2222222222\n", 15, 2, 11, 21}, // The next line with trailing newline. 89 | {"1111111111\n2222222222\n3333333333", 15, 2, 11, 21}, // Middle 90 | {"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) 91 | {"\n\n\n\n", -1, 1, 0, 0}, 92 | {"\n\n\n\n", 0, 1, 0, 0}, 93 | {"\n\n\n\n", 1, 2, 1, 1}, 94 | {"\n\n\n\n", 2, 3, 2, 2}, 95 | {"\n\n\n\n", 3, 4, 3, 3}, 96 | {"\n\n\n\n", 4, 4, 3, 3} 97 | }; 98 | } 99 | 100 | @Test(dataProvider = "Test-Newlines") public void test_newline_search(CharSequence input, int i, int expected_number, int expected_begin, int expected_end) { 101 | img.updateDimensions(input, new FoldRegion[] {}); 102 | 103 | Minimap.LineInfo line = img.getLine(i); 104 | 105 | assertEquals(expected_number, line.number); 106 | assertEquals(expected_begin, line.begin); 107 | assertEquals(expected_end, line.end); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/render/CoordinateHelper.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.render; 27 | 28 | import java.awt.*; 29 | 30 | 31 | public class CoordinateHelper { 32 | private int panelHeight = 0; 33 | private int panelWidth = 0; 34 | private float hidpiScale = 1.0f; 35 | private int srcHeight = 0; 36 | private int pixelsPerLine = 2; 37 | private int imageHeight; 38 | private Minimap map; 39 | private double complete; 40 | 41 | public CoordinateHelper setPixelsPerLine(int pixelsPerLine) { 42 | this.pixelsPerLine = pixelsPerLine; 43 | 44 | return this; 45 | } 46 | 47 | public CoordinateHelper setPanelHeight(int panelHeight) { 48 | this.panelHeight = panelHeight; 49 | 50 | return this; 51 | } 52 | 53 | public CoordinateHelper setPanelWidth(int panelWidth) { 54 | this.panelWidth = panelWidth; 55 | 56 | return this; 57 | } 58 | 59 | public CoordinateHelper setMinimap(Minimap map) { 60 | this.map = map; 61 | this.imageHeight = map.height; 62 | 63 | return this; 64 | } 65 | 66 | public CoordinateHelper setHidpiScale(float hidpiScale) { 67 | this.hidpiScale = hidpiScale; 68 | 69 | return this; 70 | } 71 | 72 | public CoordinateHelper setPercentageComplete(double complete) { 73 | this.complete = complete; 74 | 75 | return this; 76 | } 77 | 78 | /** 79 | * @return how far through the current document the user is as a percentage (0-1) 80 | */ 81 | public double getPercentComplete() { 82 | return complete; 83 | } 84 | 85 | public int getOffset() { 86 | // If the panel is 1:1 then just draw everything in the top left hand corner, otherwise we need to gracefully scroll. 87 | if(imageHeight > panelHeight * hidpiScale) { 88 | return (int) ((imageHeight - panelHeight * hidpiScale) * getPercentComplete()); 89 | } else { 90 | return 0; 91 | } 92 | } 93 | 94 | public Rectangle getImageSource() { 95 | int offset = getOffset(); 96 | int end = (int) Math.min(offset + panelHeight * hidpiScale, imageHeight); 97 | srcHeight = end - offset; 98 | return new Rectangle(0, offset, panelWidth, end); 99 | } 100 | 101 | /** 102 | * Calculates the coordinates to draw the image onto within the frame. Make sure getImageSource has been called first! 103 | */ 104 | public Rectangle getImageDestination() { 105 | return new Rectangle(0, 0, panelWidth, Math.min(srcHeight, panelHeight)); 106 | } 107 | 108 | public Rectangle getViewport(int firstVisibleLine, int lastVisibleLine) { 109 | return new Rectangle( 110 | 0, 111 | (int)((firstVisibleLine * pixelsPerLine - getOffset()) / hidpiScale), 112 | panelWidth - 1, 113 | (int)((lastVisibleLine - firstVisibleLine) * pixelsPerLine / hidpiScale) 114 | ); 115 | } 116 | 117 | /** 118 | * Offset: The character offset from the start of file. 119 | * LogicalPosition: This is the actual position within the document. 120 | * ScreenSpace: Raw position the user can see. This is a scrolling window for long documents! 121 | */ 122 | public int screenSpaceToOffset(int y, boolean dragged) { 123 | if(y < 0) y = 0; 124 | if(y > panelHeight) y = panelHeight; 125 | int line; 126 | 127 | if (imageHeight < panelHeight) { 128 | // If the panel is short enough to fit on the screen then 1:1 is good. 129 | line = (int) (y / pixelsPerLine * hidpiScale); 130 | } else if (dragged) { 131 | // If we are dragging, then act like a conventional scroll bar. 132 | line = (int) (y / (float)panelHeight * imageHeight) / pixelsPerLine; 133 | } else { 134 | // Otherwise 1:1 with an offset so that clicks in long documents line up correctly. 135 | line = (int) ((y + getOffset()) / pixelsPerLine * hidpiScale); 136 | } 137 | 138 | if (map == null) return line * pixelsPerLine; 139 | 140 | return map.getOffsetForLine(line); 141 | } 142 | 143 | public int offsetToScreenSpace(int offset) { 144 | if (map == null) return offset / pixelsPerLine; 145 | 146 | int line = map.getLine(offset).number; 147 | 148 | if (imageHeight < panelHeight) { 149 | return (int) (line * pixelsPerLine * hidpiScale); 150 | } else { 151 | return (int) (line * pixelsPerLine * hidpiScale - getOffset()); 152 | } 153 | } 154 | 155 | public int offsetToCharacterInLine(int offset) { 156 | if (map == null) return offset / pixelsPerLine; 157 | 158 | return offset - map.getLine(offset).begin; 159 | } 160 | 161 | public int linesToPixels(int lines) { 162 | return (int) (lines * pixelsPerLine * hidpiScale); 163 | } 164 | 165 | public int pixelsToLines(int pixels) { 166 | if (imageHeight < panelHeight) { 167 | return (int) (pixels / pixelsPerLine * hidpiScale); 168 | } else { 169 | return (int) (pixels / pixelsPerLine / (panelHeight / (float)imageHeight) * hidpiScale); 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/EditorPanelInjector.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; 27 | 28 | import com.intellij.openapi.diagnostic.Logger; 29 | import com.intellij.openapi.fileEditor.*; 30 | import com.intellij.openapi.project.Project; 31 | import com.intellij.openapi.vfs.VirtualFile; 32 | import net.vektah.codeglance.render.TaskRunner; 33 | 34 | import javax.swing.*; 35 | import java.awt.*; 36 | import java.util.*; 37 | 38 | /** 39 | * Injects a panel into any newly created editors. 40 | */ 41 | public class EditorPanelInjector implements FileEditorManagerListener { 42 | private Project project; 43 | private Logger logger = Logger.getInstance(getClass()); 44 | private TaskRunner runner; 45 | private Map panels = new HashMap(); 46 | 47 | public EditorPanelInjector(Project project, TaskRunner runner) { 48 | this.project = project; 49 | this.runner = runner; 50 | } 51 | 52 | @Override 53 | public void fileOpened(FileEditorManager fileEditorManager, VirtualFile virtualFile) { 54 | // Seems there is a case where multiple split panes can have the same file open and getSelectedEditor, and even 55 | // getEditors(virtualVile) return only one of them... So shotgun approach here. 56 | FileEditor[] editors = fileEditorManager.getAllEditors(); 57 | for(FileEditor editor: editors) { 58 | inject(editor); 59 | } 60 | } 61 | 62 | /** 63 | * Here be dragons. No Seriously. Run! 64 | * 65 | * There is a loading pane that proxies stuff here blah blah.. We need to dig down so we can check 66 | * if we have already injected into a given component... On the plus side might be a bit closer to being able to 67 | * injecting into the editor space itself... 68 | * 69 | * @param editor A text editor to inject into. 70 | */ 71 | private void inject(FileEditor editor) { 72 | if(!(editor instanceof TextEditor)) { 73 | logger.debug("I01: Injection failed, only text editors are supported currently."); 74 | return; 75 | } 76 | 77 | try { 78 | JPanel outerPanel = (JPanel)editor.getComponent(); 79 | BorderLayout outerLayout = (BorderLayout)outerPanel.getLayout(); 80 | JLayeredPane pane = (JLayeredPane)outerLayout.getLayoutComponent(BorderLayout.CENTER); 81 | JPanel panel = (JPanel)pane.getComponent(1); 82 | BorderLayout innerLayout = (BorderLayout)panel.getLayout(); 83 | 84 | // Ok we finally found the actual editor layout. Now make sure we haven't already injected into this editor. 85 | if(innerLayout.getLayoutComponent(BorderLayout.LINE_END) == null) { 86 | GlancePanel glancePanel = new GlancePanel(project, editor, panel, runner); 87 | panel.add(glancePanel, BorderLayout.LINE_END); 88 | panels.put(editor, glancePanel); 89 | } else { 90 | logger.debug("I07: Injection skipped. Looks like we have already injected something here."); 91 | } 92 | } catch(ClassCastException e) { 93 | logger.warn(String.format("Injection failed '%s' on line %d.", e.getMessage(), e.getStackTrace()[0].getLineNumber())); 94 | return; 95 | } 96 | } 97 | 98 | private void uninject(FileEditor editor) { 99 | if(!(editor instanceof TextEditor)) { 100 | logger.debug("I01: Uninjection failed, only text editors are supported currently."); 101 | return; 102 | } 103 | 104 | try { 105 | JPanel outerPanel = (JPanel)editor.getComponent(); 106 | BorderLayout outerLayout = (BorderLayout)outerPanel.getLayout(); 107 | JLayeredPane pane = (JLayeredPane)outerLayout.getLayoutComponent(BorderLayout.CENTER); 108 | JPanel panel = (JPanel)pane.getComponent(1); 109 | BorderLayout innerLayout = (BorderLayout)panel.getLayout(); 110 | 111 | // Ok we finally found the actual editor layout. Now make sure we haven't already injected into this editor. 112 | Component glancePanel = innerLayout.getLayoutComponent(BorderLayout.LINE_END); 113 | if (glancePanel != null) { 114 | panel.remove(glancePanel); 115 | } 116 | } catch(ClassCastException e) { 117 | logger.warn(String.format("Uninjection failed '%s' on line %d.", e.getMessage(), e.getStackTrace()[0].getLineNumber())); 118 | return; 119 | } 120 | } 121 | 122 | @Override 123 | public void fileClosed(FileEditorManager fileEditorManager, VirtualFile virtualFile) { 124 | // Again we don't know which EDITOR was closed, just the file - which could be shared between some editors that 125 | // are still open. Lets play 'spot the missing editor'. 126 | Set unseen = new HashSet(panels.keySet()); 127 | 128 | for(FileEditor editor: fileEditorManager.getAllEditors()) { 129 | if (unseen.contains(editor)) { 130 | unseen.remove(editor); 131 | } 132 | } 133 | 134 | GlancePanel panel; 135 | for (FileEditor editor: unseen) { 136 | panel = panels.get(editor); 137 | panel.onClose(); 138 | uninject(editor); 139 | panels.remove(editor); 140 | } 141 | } 142 | 143 | @Override 144 | public void selectionChanged(FileEditorManagerEvent fileEditorManagerEvent) { } 145 | } 146 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 45 | public ConfigForm() { 46 | pixelsPerLine.setModel(new DefaultComboBoxModel(new Integer[]{1, 2, 3, 4})); 47 | jumpToPosition.setModel(new DefaultComboBoxModel(new String[]{"Mouse Down", "Mouse Up"})); 48 | clickStyle.setModel(new DefaultComboBoxModel(new String[]{"Scrollbar (old sublime)", "To Text (new sublime)"})); 49 | } 50 | 51 | public JPanel getRoot() { 52 | return rootPanel; 53 | } 54 | 55 | public int getPixelsPerLine() { 56 | return (Integer) pixelsPerLine.getSelectedItem(); 57 | } 58 | 59 | public void setPixelsPerLine(int pixelsPerLine) { 60 | this.pixelsPerLine.setSelectedIndex(pixelsPerLine - 1); 61 | } 62 | 63 | public boolean isDisabled() { 64 | return disabled.getModel().isSelected(); 65 | } 66 | 67 | public void setDisabled(boolean isDisabled) { 68 | disabled.getModel().setSelected(isDisabled); 69 | } 70 | 71 | public boolean jumpOnMouseDown() { 72 | return jumpToPosition.getSelectedIndex() == 0; 73 | } 74 | 75 | public void setJumpOnMouseDown(boolean jump) { 76 | jumpToPosition.setSelectedIndex(jump ? 0 : 1); 77 | } 78 | 79 | public boolean percentageBasedClick() { 80 | return clickStyle.getSelectedIndex() == 0; 81 | } 82 | 83 | public void setPercentageBasedClick(boolean click) { 84 | clickStyle.setSelectedIndex(click ? 0 : 1); 85 | } 86 | 87 | public String getViewportColor() { 88 | return viewportColor.getText(); 89 | } 90 | 91 | public void setViewportColor(String color) { 92 | viewportColor.setText(color); 93 | } 94 | 95 | public int getWidth() { 96 | try { 97 | return Integer.parseInt(width.getText()); 98 | } catch (NumberFormatException e) { 99 | return 100; 100 | } 101 | } 102 | 103 | public void setWidth(int width) { 104 | this.width.setText(Integer.toString(width)); 105 | } 106 | 107 | { 108 | // GUI initializer generated by IntelliJ IDEA GUI Designer 109 | // >>> IMPORTANT!! <<< 110 | // DO NOT EDIT OR ADD ANY CODE HERE! 111 | $$$setupUI$$$(); 112 | } 113 | 114 | /** 115 | * Method generated by IntelliJ IDEA GUI Designer 116 | * >>> IMPORTANT!! <<< 117 | * DO NOT edit this method OR call it in your code! 118 | * 119 | * @noinspection ALL 120 | */ 121 | private void $$$setupUI$$$() { 122 | rootPanel = new JPanel(); 123 | rootPanel.setLayout(new GridLayoutManager(7, 3, new Insets(0, 0, 0, 0), -1, -1)); 124 | final JLabel label1 = new JLabel(); 125 | label1.setText("Pixels Per Line:"); 126 | label1.setDisplayedMnemonic('P'); 127 | label1.setDisplayedMnemonicIndex(0); 128 | 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)); 129 | pixelsPerLine = new JComboBox(); 130 | final DefaultComboBoxModel defaultComboBoxModel1 = new DefaultComboBoxModel(); 131 | defaultComboBoxModel1.addElement("1"); 132 | defaultComboBoxModel1.addElement("2"); 133 | defaultComboBoxModel1.addElement("3"); 134 | defaultComboBoxModel1.addElement("4"); 135 | pixelsPerLine.setModel(defaultComboBoxModel1); 136 | 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)); 137 | final Spacer spacer1 = new Spacer(); 138 | rootPanel.add(spacer1, new GridConstraints(0, 2, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false)); 139 | final Spacer spacer2 = new Spacer(); 140 | rootPanel.add(spacer2, new GridConstraints(6, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); 141 | final JLabel label2 = new JLabel(); 142 | label2.setText("Jump to position on:"); 143 | 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)); 144 | jumpToPosition = new JComboBox(); 145 | final DefaultComboBoxModel defaultComboBoxModel2 = new DefaultComboBoxModel(); 146 | defaultComboBoxModel2.addElement("Mouse Down"); 147 | defaultComboBoxModel2.addElement("Mouse Up"); 148 | jumpToPosition.setModel(defaultComboBoxModel2); 149 | 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)); 150 | final JLabel label3 = new JLabel(); 151 | label3.setText("Disabled:"); 152 | 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)); 153 | disabled = new JCheckBox(); 154 | disabled.setText(""); 155 | 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)); 156 | final JLabel label4 = new JLabel(); 157 | label4.setText("Click style"); 158 | 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)); 159 | clickStyle = new JComboBox(); 160 | final DefaultComboBoxModel defaultComboBoxModel3 = new DefaultComboBoxModel(); 161 | defaultComboBoxModel3.addElement("Scrollbar (old sublime)"); 162 | defaultComboBoxModel3.addElement("To Text (new sublime)"); 163 | clickStyle.setModel(defaultComboBoxModel3); 164 | 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)); 165 | width = new JTextField(); 166 | 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)); 167 | final JLabel label5 = new JLabel(); 168 | label5.setText("Width:"); 169 | 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)); 170 | final JLabel label6 = new JLabel(); 171 | label6.setText("Viewport Color"); 172 | 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)); 173 | viewportColor = new JTextField(); 174 | 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)); 175 | label1.setLabelFor(pixelsPerLine); 176 | } 177 | 178 | /** 179 | * @noinspection ALL 180 | */ 181 | public JComponent $$$getRootComponent$$$() { 182 | return rootPanel; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/render/CharacterWeight.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.render; 27 | 28 | /** 29 | * Works out a weight for each character from 0-1, 1 being fairly opaque (lets say, like an M), 0 being totally transparent. 30 | */ 31 | public class CharacterWeight { 32 | 33 | // This list of character weights has been generated by CharacterWeight.py - see the source for details. 34 | private static float[] weights = new float[] { 35 | 0.2816f, // 033 = '!' (top) 36 | 0.1779f, // 033 = '!' (bottom) 37 | 0.4865f, // 034 = '"' (top) 38 | 0.0000f, // 034 = '"' (bottom) 39 | 0.4769f, // 035 = '#' (top) 40 | 0.5714f, // 035 = '#' (bottom) 41 | 0.5066f, // 036 = '$' (top) 42 | 0.6160f, // 036 = '$' (bottom) 43 | 0.4510f, // 037 = '%' (top) 44 | 0.5423f, // 037 = '%' (bottom) 45 | 0.2971f, // 038 = '&' (top) 46 | 0.7204f, // 038 = '&' (bottom) 47 | 0.3274f, // 039 = ''' (top) 48 | 0.0000f, // 039 = ''' (bottom) 49 | 0.4275f, // 040 = '(' (top) 50 | 0.5134f, // 040 = '(' (bottom) 51 | 0.4273f, // 041 = ')' (top) 52 | 0.5089f, // 041 = ')' (bottom) 53 | 0.3643f, // 042 = '*' (top) 54 | 0.1042f, // 042 = '*' (bottom) 55 | 0.1905f, // 043 = '+' (top) 56 | 0.2286f, // 043 = '+' (bottom) 57 | 0.0000f, // 044 = ',' (top) 58 | 0.2983f, // 044 = ',' (bottom) 59 | 0.0000f, // 045 = '-' (top) 60 | 0.0000f, // 045 = '-' (bottom) 61 | 0.0000f, // 046 = '.' (top) 62 | 0.1777f, // 046 = '.' (bottom) 63 | 0.3961f, // 047 = '/' (top) 64 | 0.5943f, // 047 = '/' (bottom) 65 | 0.5221f, // 048 = '0' (top) 66 | 0.5831f, // 048 = '0' (bottom) 67 | 0.4045f, // 049 = '1' (top) 68 | 0.5143f, // 049 = '1' (bottom) 69 | 0.3419f, // 050 = '2' (top) 70 | 0.4724f, // 050 = '2' (bottom) 71 | 0.3830f, // 051 = '3' (top) 72 | 0.4715f, // 051 = '3' (bottom) 73 | 0.4157f, // 052 = '4' (top) 74 | 0.5712f, // 052 = '4' (bottom) 75 | 0.4060f, // 053 = '5' (top) 76 | 0.4421f, // 053 = '5' (bottom) 77 | 0.3501f, // 054 = '6' (top) 78 | 0.6080f, // 054 = '6' (bottom) 79 | 0.4228f, // 055 = '7' (top) 80 | 0.3399f, // 055 = '7' (bottom) 81 | 0.5798f, // 056 = '8' (top) 82 | 0.6481f, // 056 = '8' (bottom) 83 | 0.5005f, // 057 = '9' (top) 84 | 0.4235f, // 057 = '9' (bottom) 85 | 0.1481f, // 058 = ':' (top) 86 | 0.1777f, // 058 = ':' (bottom) 87 | 0.1481f, // 059 = ';' (top) 88 | 0.2983f, // 059 = ';' (bottom) 89 | 0.3989f, // 060 = '<' (top) 90 | 0.3509f, // 060 = '<' (bottom) 91 | 0.2381f, // 061 = '=' (top) 92 | 0.2857f, // 061 = '=' (bottom) 93 | 0.4476f, // 062 = '>' (top) 94 | 0.3942f, // 062 = '>' (bottom) 95 | 0.3414f, // 063 = '?' (top) 96 | 0.1766f, // 063 = '?' (bottom) 97 | 0.3910f, // 064 = '@' (top) 98 | 0.7926f, // 064 = '@' (bottom) 99 | 0.3343f, // 065 = 'A' (top) 100 | 0.6156f, // 065 = 'A' (bottom) 101 | 0.5707f, // 066 = 'B' (top) 102 | 0.6830f, // 066 = 'B' (bottom) 103 | 0.3498f, // 067 = 'C' (top) 104 | 0.4296f, // 067 = 'C' (bottom) 105 | 0.5326f, // 068 = 'D' (top) 106 | 0.6364f, // 068 = 'D' (bottom) 107 | 0.4286f, // 069 = 'E' (top) 108 | 0.5143f, // 069 = 'E' (bottom) 109 | 0.3810f, // 070 = 'F' (top) 110 | 0.3429f, // 070 = 'F' (bottom) 111 | 0.3488f, // 071 = 'G' (top) 112 | 0.6476f, // 071 = 'G' (bottom) 113 | 0.5714f, // 072 = 'H' (top) 114 | 0.6857f, // 072 = 'H' (bottom) 115 | 0.4762f, // 073 = 'I' (top) 116 | 0.5714f, // 073 = 'I' (bottom) 117 | 0.3810f, // 074 = 'J' (top) 118 | 0.4083f, // 074 = 'J' (bottom) 119 | 0.5376f, // 075 = 'K' (top) 120 | 0.6617f, // 075 = 'K' (bottom) 121 | 0.2857f, // 076 = 'L' (top) 122 | 0.5143f, // 076 = 'L' (bottom) 123 | 0.4928f, // 077 = 'M' (top) 124 | 0.3139f, // 077 = 'M' (bottom) 125 | 0.5173f, // 078 = 'N' (top) 126 | 0.5416f, // 078 = 'N' (bottom) 127 | 0.4781f, // 079 = 'O' (top) 128 | 0.5759f, // 079 = 'O' (bottom) 129 | 0.5619f, // 080 = 'P' (top) 130 | 0.4576f, // 080 = 'P' (bottom) 131 | 0.4775f, // 081 = 'Q' (top) 132 | 0.7689f, // 081 = 'Q' (bottom) 133 | 0.5716f, // 082 = 'R' (top) 134 | 0.6761f, // 082 = 'R' (bottom) 135 | 0.3826f, // 083 = 'S' (top) 136 | 0.4923f, // 083 = 'S' (bottom) 137 | 0.4762f, // 084 = 'T' (top) 138 | 0.3429f, // 084 = 'T' (bottom) 139 | 0.5714f, // 085 = 'U' (top) 140 | 0.6283f, // 085 = 'U' (bottom) 141 | 0.3707f, // 086 = 'V' (top) 142 | 0.3516f, // 086 = 'V' (bottom) 143 | 0.3128f, // 087 = 'W' (top) 144 | 0.5564f, // 087 = 'W' (bottom) 145 | 0.4452f, // 088 = 'X' (top) 146 | 0.5479f, // 088 = 'X' (bottom) 147 | 0.4495f, // 089 = 'Y' (top) 148 | 0.3460f, // 089 = 'Y' (bottom) 149 | 0.4129f, // 090 = 'Z' (top) 150 | 0.4715f, // 090 = 'Z' (bottom) 151 | 0.4762f, // 091 = '[' (top) 152 | 0.6857f, // 091 = '[' (bottom) 153 | 0.3959f, // 092 = '\' (top) 154 | 0.5941f, // 092 = '\' (bottom) 155 | 0.4762f, // 093 = ']' (top) 156 | 0.6857f, // 093 = ']' (bottom) 157 | 0.3647f, // 094 = '^' (top) 158 | 0.0000f, // 094 = '^' (bottom) 159 | 0.0000f, // 095 = '_' (top) 160 | 0.3429f, // 095 = '_' (bottom) 161 | 0.1359f, // 096 = '`' (top) 162 | 0.0000f, // 096 = '`' (bottom) 163 | 0.2439f, // 097 = 'a' (top) 164 | 0.6853f, // 097 = 'a' (bottom) 165 | 0.5729f, // 098 = 'b' (top) 166 | 0.6431f, // 098 = 'b' (bottom) 167 | 0.2607f, // 099 = 'c' (top) 168 | 0.3966f, // 099 = 'c' (bottom) 169 | 0.5729f, // 100 = 'd' (top) 170 | 0.6521f, // 100 = 'd' (bottom) 171 | 0.3935f, // 101 = 'e' (top) 172 | 0.6844f, // 101 = 'e' (bottom) 173 | 0.6232f, // 102 = 'f' (top) 174 | 0.3429f, // 102 = 'f' (bottom) 175 | 0.3488f, // 103 = 'g' (top) 176 | 0.9927f, // 103 = 'g' (bottom) 177 | 0.5744f, // 104 = 'h' (top) 178 | 0.6857f, // 104 = 'h' (bottom) 179 | 0.3399f, // 105 = 'i' (top) 180 | 0.4052f, // 105 = 'i' (bottom) 181 | 0.3399f, // 106 = 'j' (top) 182 | 0.6463f, // 106 = 'j' (bottom) 183 | 0.5328f, // 107 = 'k' (top) 184 | 0.6015f, // 107 = 'k' (bottom) 185 | 0.4762f, // 108 = 'l' (top) 186 | 0.4013f, // 108 = 'l' (bottom) 187 | 0.3447f, // 109 = 'm' (top) 188 | 0.4000f, // 109 = 'm' (bottom) 189 | 0.4062f, // 110 = 'n' (top) 190 | 0.6857f, // 110 = 'n' (bottom) 191 | 0.2965f, // 111 = 'o' (top) 192 | 0.5799f, // 111 = 'o' (bottom) 193 | 0.3511f, // 112 = 'p' (top) 194 | 0.9109f, // 112 = 'p' (bottom) 195 | 0.3500f, // 113 = 'q' (top) 196 | 0.9154f, // 113 = 'q' (bottom) 197 | 0.3057f, // 114 = 'r' (top) 198 | 0.3429f, // 114 = 'r' (bottom) 199 | 0.2893f, // 115 = 's' (top) 200 | 0.5450f, // 115 = 's' (bottom) 201 | 0.5238f, // 116 = 't' (top) 202 | 0.4627f, // 116 = 't' (bottom) 203 | 0.3810f, // 117 = 'u' (top) 204 | 0.6678f, // 117 = 'u' (bottom) 205 | 0.2458f, // 118 = 'v' (top) 206 | 0.3538f, // 118 = 'v' (bottom) 207 | 0.4218f, // 119 = 'w' (top) 208 | 0.6884f, // 119 = 'w' (bottom) 209 | 0.3266f, // 120 = 'x' (top) 210 | 0.5461f, // 120 = 'x' (bottom) 211 | 0.3613f, // 121 = 'y' (top) 212 | 0.6521f, // 121 = 'y' (bottom) 213 | 0.3417f, // 122 = 'z' (top) 214 | 0.5183f, // 122 = 'z' (bottom) 215 | 0.4424f, // 123 = '{' (top) 216 | 0.6649f, // 123 = '{' (bottom) 217 | 0.3810f, // 124 = '|' (top) 218 | 0.5714f, // 124 = '|' (bottom) 219 | 0.4006f, // 125 = '}' (top) 220 | 0.6136f, // 125 = '}' (bottom) 221 | 0.0000f, // 126 = '~' (top) 222 | 0.1950f, // 126 = '~' (bottom) 223 | }; 224 | 225 | 226 | public static float getTopWeight(char c) { 227 | // Whitespace and non printing characters are totally transparent. 228 | if(c <= 32) return 0; 229 | 230 | // Unicode chars... Yeah not going to list all of those out. 231 | if(c >= 127) return 0.4f; 232 | 233 | return weights[c * 2 - 66]; 234 | } 235 | 236 | public static float getBottomWeight(char c) { 237 | // Whitespace and non printing characters are totally transparent. 238 | if(c <= 32) return 0; 239 | 240 | // Unicode chars... Yeah not going to list all of those out. 241 | if(c >= 127) return 0.4f; 242 | 243 | return weights[c * 2 - 65]; 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/render/Minimap.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.render; 27 | 28 | import com.intellij.lexer.Lexer; 29 | import com.intellij.openapi.diagnostic.Logger; 30 | import com.intellij.openapi.editor.FoldRegion; 31 | import com.intellij.openapi.editor.colors.EditorColorsScheme; 32 | import com.intellij.openapi.editor.colors.TextAttributesKey; 33 | import com.intellij.openapi.editor.markup.TextAttributes; 34 | import com.intellij.openapi.fileTypes.SyntaxHighlighter; 35 | import com.intellij.psi.tree.IElementType; 36 | import net.vektah.codeglance.config.Config; 37 | 38 | import java.awt.*; 39 | import java.awt.image.BufferedImage; 40 | import java.util.ArrayList; 41 | 42 | /** 43 | * A rendered minimap of a document 44 | */ 45 | public class Minimap { 46 | public BufferedImage img; 47 | public int height; 48 | private Logger logger = Logger.getInstance(getClass()); 49 | private ArrayList line_endings; 50 | private Config config; 51 | private static final Composite CLEAR = AlphaComposite.getInstance(AlphaComposite.CLEAR); 52 | private static final int[] unpackedColor = new int[4]; 53 | 54 | public Minimap(Config config) { 55 | this.config = config; 56 | } 57 | 58 | /** 59 | * Scans over the entire document once to work out the required dimensions then rebuilds the image if necessary. 60 | * 61 | * Because java chars are UTF-8 16 bit chars this function should be UTF safe in the 2 byte range, which is all intellij 62 | * seems to handle anyway.... 63 | */ 64 | public void updateDimensions(CharSequence text, FoldRegion[] folding) { 65 | int line_length = 0; // The current line length 66 | int longest_line = 1; // The longest line in the document 67 | int lines = 1; // The total number of lines in the document 68 | char last = 0; 69 | char ch; 70 | 71 | line_endings = new ArrayList(); 72 | // Magical first line 73 | line_endings.add(-1); 74 | 75 | for (int i = 0, len = text.length(); i < len; i++) { 76 | if(isFolded(i, folding)) { 77 | continue; 78 | } 79 | 80 | ch = text.charAt(i); 81 | 82 | if(ch == '\n' || (ch == '\r' && last != '\n')) { 83 | line_endings.add(i); 84 | lines++; 85 | if(line_length > longest_line) longest_line = line_length; 86 | line_length = 0; 87 | } else if (ch == '\t') { 88 | line_length += 4; 89 | } else { 90 | line_length++; 91 | } 92 | 93 | last = ch; 94 | } 95 | // If there is no final newline add one. 96 | if(line_endings.get(line_endings.size() - 1) != text.length() - 1) line_endings.add(text.length() - 1); 97 | 98 | height = lines * config.pixelsPerLine; 99 | 100 | // If the image is too small to represent the entire document now then regenerate it 101 | // TODO: Copy old image when incremental update is added. 102 | if (img == null || img.getHeight() < height || img.getWidth() < config.width) { 103 | if(img != null) img.flush(); 104 | // Create an image that is a bit bigger then the one we need so we don't need to re-create it again soon. 105 | // Documents can get big, so rather then relative sizes lets just add a fixed amount on. 106 | img = new BufferedImage(config.width, height + 100 * config.pixelsPerLine, BufferedImage.TYPE_4BYTE_ABGR); 107 | logger.debug("Created new image"); 108 | } 109 | } 110 | 111 | /** 112 | * @return the offset that a line starts at within the file. 113 | */ 114 | public int getOffsetForLine(int line) { 115 | if (line < 1) { 116 | return line_endings.get(1); 117 | } 118 | 119 | if (line >= line_endings.size()) { 120 | return line_endings.get(line_endings.size() - 1); 121 | } 122 | 123 | return line_endings.get(line); 124 | } 125 | 126 | /** 127 | * Binary search for a line ending. 128 | * @param i character offset from start of document 129 | * @return 3 element array, [line_number, o] 130 | */ 131 | public LineInfo getLine(int i) { 132 | int lines = line_endings.get(line_endings.size() - 1); 133 | if(i > lines) i = lines; 134 | if(i < 0) i = 0; 135 | // Dummy entries if there are no lines 136 | if(line_endings.size() == 0) return new LineInfo(1, 0, 0); 137 | if(line_endings.size() == 1) return new LineInfo(1, 0, 0); 138 | if(line_endings.size() == 2) return new LineInfo(1, line_endings.get(0) + 1, line_endings.get(1)); 139 | 140 | int index_min = 0; 141 | int index_max = line_endings.size() - 1; 142 | int index_mid; 143 | int value; 144 | 145 | while(true) { 146 | index_mid = (int) Math.floor((index_min + index_max) / 2.0f); // Key space is pretty linear, might be able to use that to scale our next point. 147 | value = line_endings.get(index_mid); 148 | 149 | if(value < i) { 150 | if(i < line_endings.get(index_mid + 1)) return new LineInfo(index_mid + 1, value + 1, line_endings.get(index_mid + 1)); 151 | 152 | index_min = index_mid + 1; 153 | } else if(i < value) { 154 | if(line_endings.get(index_mid - 1) < i) return new LineInfo(index_mid, line_endings.get(index_mid - 1) + 1, value); 155 | 156 | index_max = index_mid - 1; 157 | } else { 158 | // character at i is actually a newline, so grab the line before it. 159 | return new LineInfo(index_mid, line_endings.get(index_mid - 1) + 1, i); 160 | } 161 | } 162 | } 163 | 164 | /** 165 | * Works out the color a token should be rendered in. 166 | * 167 | * @param element The element to get the color for 168 | * @param hl the syntax highlighter for this document 169 | * @param colorScheme the users color scheme 170 | * @return the RGB color to use for the given element 171 | */ 172 | private int getColorForElementType(IElementType element, SyntaxHighlighter hl, EditorColorsScheme colorScheme) { 173 | int color = colorScheme.getDefaultForeground().getRGB(); 174 | Color tmp; 175 | TextAttributesKey[] attributes = hl.getTokenHighlights(element); 176 | for(TextAttributesKey attribute : attributes) { 177 | TextAttributes attr = colorScheme.getAttributes(attribute); 178 | if(attr != null) { 179 | tmp = attr.getForegroundColor(); 180 | if(tmp != null) color = tmp.getRGB(); 181 | } 182 | } 183 | 184 | return color; 185 | } 186 | 187 | /** 188 | * Checks if a given position is within a folded region 189 | * @param position the offset from the start of file in chars 190 | * @param regions the array of regions to check against 191 | * @return true if the given position is folded. 192 | */ 193 | private boolean isFolded(int position, FoldRegion[] regions) { 194 | for (FoldRegion region: regions) { 195 | if (!region.isExpanded() && region.getStartOffset() < position && position < region.getEndOffset()) { 196 | return true; 197 | } 198 | } 199 | 200 | return false; 201 | } 202 | 203 | /** 204 | * Internal worker function to update the minimap image 205 | * 206 | * @param text The entire text of the document to render 207 | * @param colorScheme The users color scheme 208 | * @param hl The syntax highlighter to use for the language this document is in. 209 | */ 210 | public void update(CharSequence text, EditorColorsScheme colorScheme, SyntaxHighlighter hl, FoldRegion[] folding) { 211 | logger.debug("Updating file image."); 212 | updateDimensions(text, folding); 213 | 214 | int color; 215 | int bgcolor = colorScheme.getDefaultBackground().getRGB(); 216 | char ch; 217 | LineInfo startLine; 218 | float topWeight; 219 | float bottomWeight; 220 | Lexer lexer = hl.getHighlightingLexer(); 221 | IElementType tokenType; 222 | 223 | Graphics2D g = (Graphics2D)img.getGraphics(); 224 | g.setComposite(CLEAR); 225 | g.fillRect(0, 0, img.getWidth(), img.getHeight()); 226 | 227 | lexer.start(text); 228 | tokenType = lexer.getTokenType(); 229 | 230 | int x, y; 231 | while(tokenType != null) { 232 | int start = lexer.getTokenStart(); 233 | startLine = getLine(start); 234 | y = startLine.number * config.pixelsPerLine; 235 | 236 | color = getColorForElementType(tokenType, hl, colorScheme); 237 | 238 | // Pre-loop to count whitespace from start of line. 239 | x = 0; 240 | for (int i = startLine.begin; i < start; i++) { 241 | // Dont count lines inside of folded regions. 242 | if (isFolded(i, folding)) { 243 | continue; 244 | } 245 | 246 | if(text.charAt(i) == '\t') { 247 | x += 4; 248 | } else { 249 | x += 1; 250 | } 251 | 252 | // Abort if this line is getting to long... 253 | if(x > config.width) break; 254 | } 255 | 256 | // Render whole token, make sure multi lines are handled gracefully. 257 | for(int i = start; i < lexer.getTokenEnd(); i++) { 258 | // Don't render folds. 259 | if (isFolded(i, folding)) { 260 | continue; 261 | } 262 | 263 | ch = text.charAt(i); 264 | 265 | if(ch == '\n') { 266 | x = 0; 267 | y += config.pixelsPerLine; 268 | } else if(ch == '\t') { 269 | x += 4; 270 | } else { 271 | x += 1; 272 | } 273 | 274 | topWeight = CharacterWeight.getTopWeight(text.charAt(i)); 275 | bottomWeight = CharacterWeight.getBottomWeight(text.charAt(i)); 276 | 277 | // No point rendering non visible characters. 278 | if(topWeight == 0) continue; 279 | 280 | if(0 <= x && x < img.getWidth() && 0 <= y && y + config.pixelsPerLine < img.getHeight()) { 281 | switch(config.pixelsPerLine) { 282 | case 1: 283 | // Cant show whitespace between lines any more. This looks rather ugly... 284 | setPixel(x, y + 1, color, (float) ((topWeight + bottomWeight) / 2.0)); 285 | break; 286 | 287 | case 2: 288 | // Two lines we make the top line a little lighter to give the illusion of whitespace between lines. 289 | setPixel(x, y, color, topWeight * 0.5f); 290 | setPixel(x, y + 1, color, bottomWeight); 291 | break; 292 | case 3: 293 | // Three lines we make the top nearly empty, and fade the bottom a little too 294 | setPixel(x, y, color, topWeight * 0.3f); 295 | setPixel(x, y + 1, color, (float) ((topWeight + bottomWeight) / 2.0)); 296 | setPixel(x, y + 2, color, bottomWeight * 0.7f); 297 | break; 298 | case 4: 299 | // Empty top line, Nice blend for everything else 300 | setPixel(x, y + 1, color, topWeight); 301 | setPixel(x, y + 2, color, (float) ((topWeight + bottomWeight) / 2.0)); 302 | setPixel(x, y + 3, color, bottomWeight); 303 | } 304 | } 305 | } 306 | 307 | lexer.advance(); 308 | tokenType = lexer.getTokenType(); 309 | } 310 | } 311 | 312 | /** 313 | * mask out the alpha component and set it to the given value. 314 | * @param color Color A 315 | * @param alpha alpha percent from 0-1. 316 | * @return int color 317 | */ 318 | private void setPixel(int x, int y, int color, float alpha) { 319 | if(alpha > 1) alpha = color; 320 | if(alpha < 0) alpha = 0; 321 | 322 | // abgr is backwards? 323 | unpackedColor[3] = (int) (alpha * 255); 324 | unpackedColor[0] = (color & 0xFF0000) >> 16; 325 | unpackedColor[1] = (color & 0x00FF00) >> 8; 326 | unpackedColor[2] = (color & 0x0000FF); 327 | 328 | img.getRaster().setPixel(x, y, unpackedColor); 329 | } 330 | 331 | public class LineInfo { 332 | LineInfo(int number, int begin, int end) { 333 | this.number = number; 334 | this.begin = begin; 335 | this.end = end; 336 | } 337 | 338 | public int number; 339 | public int begin; 340 | public int end; 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /src/main/java/net/vektah/codeglance/GlancePanel.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; 27 | 28 | import com.intellij.openapi.components.ServiceManager; 29 | import com.intellij.openapi.diagnostic.Logger; 30 | import com.intellij.openapi.editor.Editor; 31 | import com.intellij.openapi.editor.FoldRegion; 32 | import com.intellij.openapi.editor.ScrollType; 33 | import com.intellij.openapi.editor.colors.ColorKey; 34 | import com.intellij.openapi.editor.event.*; 35 | import com.intellij.openapi.fileEditor.FileEditor; 36 | import com.intellij.openapi.fileEditor.TextEditor; 37 | import com.intellij.openapi.fileTypes.SyntaxHighlighter; 38 | import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory; 39 | import com.intellij.openapi.project.Project; 40 | import com.intellij.psi.PsiDocumentManager; 41 | import com.intellij.psi.PsiFile; 42 | import com.intellij.ui.JBColor; 43 | import net.vektah.codeglance.config.Config; 44 | import net.vektah.codeglance.config.ConfigChangeListener; 45 | import net.vektah.codeglance.config.ConfigService; 46 | import net.vektah.codeglance.render.CoordinateHelper; 47 | import net.vektah.codeglance.render.Minimap; 48 | import net.vektah.codeglance.render.RenderTask; 49 | import net.vektah.codeglance.render.TaskRunner; 50 | 51 | import javax.swing.*; 52 | import java.awt.*; 53 | import java.awt.event.*; 54 | import java.lang.reflect.Array; 55 | 56 | /** 57 | * This JPanel gets injected into editor windows and renders a image generated by GlanceFileRenderer 58 | */ 59 | public class GlancePanel extends JPanel implements VisibleAreaListener { 60 | private final TaskRunner runner; 61 | private Editor editor; 62 | private Minimap[] minimaps = new Minimap[2]; 63 | private Integer activeBuffer = -1; 64 | private Integer nextBuffer = 0; 65 | private JPanel container; 66 | private Logger logger = Logger.getInstance(getClass().getName()); 67 | private Project project; 68 | private Boolean updatePending = false; 69 | private boolean dirty = false; 70 | private CoordinateHelper coords = new CoordinateHelper(); 71 | private ConfigService configService = ServiceManager.getService(ConfigService.class); 72 | private Config config; 73 | private int lastFoldCount = -1; 74 | private Color viewportColor; 75 | 76 | // Anonymous Listeners that should be cleaned up. 77 | private ComponentListener componentListener; 78 | private DocumentListener documentListener; 79 | private ConfigChangeListener configChangeListener; 80 | private MouseWheelListener mouseWheelListener = new MouseWheelListener(); 81 | private MouseListener mouseListener = new MouseListener(); 82 | private SelectionListener selectionListener; 83 | 84 | public GlancePanel(Project project, FileEditor fileEditor, JPanel container, TaskRunner runner) { 85 | this.runner = runner; 86 | this.editor = ((TextEditor) fileEditor).getEditor(); 87 | this.container = container; 88 | this.project = project; 89 | 90 | container.addComponentListener(componentListener = new ComponentAdapter() { 91 | @Override public void componentResized(ComponentEvent componentEvent) { 92 | updateSize(); 93 | GlancePanel.this.revalidate(); 94 | GlancePanel.this.repaint(); 95 | } 96 | }); 97 | 98 | editor.getDocument().addDocumentListener(documentListener = new DocumentAdapter() { 99 | @Override public void documentChanged(DocumentEvent documentEvent) { 100 | updateImage(); 101 | } 102 | }); 103 | 104 | configService.add(configChangeListener = new ConfigChangeListener() { 105 | @Override public void configChanged() { 106 | readConfig(); 107 | updateImage(); 108 | updateSize(); 109 | GlancePanel.this.revalidate(); 110 | GlancePanel.this.repaint(); 111 | } 112 | }); 113 | 114 | addMouseWheelListener(mouseWheelListener); 115 | 116 | readConfig(); 117 | 118 | editor.getScrollingModel().addVisibleAreaListener(this); 119 | 120 | editor.getSelectionModel().addSelectionListener(selectionListener = new SelectionListener() { 121 | @Override public void selectionChanged(SelectionEvent selectionEvent) { 122 | repaint(); 123 | } 124 | }); 125 | 126 | addMouseListener(mouseListener); 127 | addMouseMotionListener(mouseListener); 128 | 129 | updateSize(); 130 | for(int i = 0; i < Array.getLength(minimaps); i++) { 131 | minimaps[i] = new Minimap(configService.getState()); 132 | } 133 | updateImage(); 134 | } 135 | 136 | private void readConfig() { 137 | config = configService.getState(); 138 | 139 | coords.setPixelsPerLine(config.pixelsPerLine); 140 | viewportColor = Color.decode("#" + config.viewportColor); 141 | } 142 | 143 | /** 144 | * Adjusts the panels size to be a percentage of the total window 145 | */ 146 | private void updateSize() { 147 | if (config.disabled) { 148 | setPreferredSize(new Dimension(0, 0)); 149 | } else { 150 | Dimension size = new Dimension(config.width, 0); 151 | setPreferredSize(size); 152 | } 153 | } 154 | 155 | /** 156 | * Fires off a new task to the worker thread. This should only be called from the ui thread. 157 | */ 158 | private void updateImage() { 159 | if (config.disabled) return; 160 | if (project.isDisposed()) return; 161 | 162 | PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()); 163 | if (file == null) { 164 | return; 165 | } 166 | 167 | synchronized (this) { 168 | // If we have already sent a rendering job off to get processed then first we need to wait for it to finish. 169 | // see updateComplete for dirty handling. The is that there will be fast updates plus one final update to 170 | // ensure accuracy, dropping any requests in the middle. 171 | if (updatePending) { 172 | dirty = true; 173 | return; 174 | } 175 | updatePending = true; 176 | } 177 | 178 | SyntaxHighlighter hl = SyntaxHighlighterFactory.getSyntaxHighlighter(file.getLanguage(), project, file.getVirtualFile()); 179 | 180 | nextBuffer = activeBuffer == 0 ? 1 : 0; 181 | 182 | runner.add(new RenderTask(minimaps[nextBuffer], editor.getDocument().getText(), editor.getColorsScheme(), hl, editor.getFoldingModel().getAllFoldRegions(), new Runnable() { 183 | @Override public void run() { 184 | updateComplete(); 185 | } 186 | })); 187 | } 188 | 189 | private void updateComplete() { 190 | synchronized (this) { 191 | updatePending = false; 192 | } 193 | 194 | if (dirty) { 195 | SwingUtilities.invokeLater(new Runnable() { 196 | @Override public void run() { 197 | updateImage(); 198 | dirty = false; 199 | } 200 | }); 201 | } 202 | 203 | activeBuffer = nextBuffer; 204 | 205 | repaint(); 206 | } 207 | 208 | private float getHidpiScale() { 209 | // Work around for apple going full retard with half pixel pixels. 210 | Float scale = (Float)Toolkit.getDefaultToolkit().getDesktopProperty("apple.awt.contentScaleFactor"); 211 | if (scale == null) { 212 | scale = 1.0f; 213 | } 214 | 215 | return scale; 216 | } 217 | 218 | private int getMapYFromEditorY(int y) { 219 | int offset = editor.logicalPositionToOffset(editor.xyToLogicalPosition(new Point(0, y))); 220 | 221 | return coords.offsetToScreenSpace(offset); 222 | } 223 | 224 | @Override 225 | public void paint(Graphics gfx) { 226 | Graphics2D g = (Graphics2D) gfx; 227 | g.setColor(editor.getColorsScheme().getDefaultBackground()); 228 | g.fillRect(0, 0, getWidth(), getHeight()); 229 | 230 | logger.debug(String.format("Rendering to buffer: %d", activeBuffer)); 231 | if (activeBuffer >= 0) { 232 | paintSelection(g); 233 | 234 | Minimap minimap = minimaps[activeBuffer]; 235 | 236 | Rectangle visibleArea = editor.getScrollingModel().getVisibleArea(); 237 | 238 | double documentEndY = editor.logicalPositionToXY(editor.offsetToLogicalPosition(editor.getDocument().getTextLength() - 1)).getY(); 239 | 240 | coords.setMinimap(minimap) 241 | .setPanelHeight(getHeight()) 242 | .setPanelWidth(getWidth()) 243 | .setPercentageComplete(visibleArea.getMinY() / (documentEndY - (visibleArea.getMaxY() - visibleArea.getMinY()))) 244 | .setHidpiScale(getHidpiScale()); 245 | 246 | Rectangle src = coords.getImageSource(); 247 | Rectangle dest = coords.getImageDestination(); 248 | 249 | // Draw the image and scale it to stretch vertically. 250 | g.drawImage(minimap.img, // source image 251 | dest.x, dest.y, dest.width, dest.height, 252 | src.x, src.y, src.width, src.height, 253 | null); 254 | 255 | paintVisibleWindow(g); 256 | } 257 | } 258 | 259 | private void paintVisibleWindow(Graphics2D g) { 260 | Rectangle visibleArea = editor.getScrollingModel().getVisibleArea(); 261 | int firstVisibleLine = getMapYFromEditorY((int) visibleArea.getMinY()); 262 | int height = coords.linesToPixels((int) ((visibleArea.getMaxY() - visibleArea.getMinY()) / editor.getLineHeight())); 263 | 264 | // Draw the current viewport 265 | g.setColor(viewportColor); 266 | g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.50f)); 267 | g.drawRect(0, firstVisibleLine, getWidth(), height); 268 | g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.20f)); 269 | g.fillRect(0, firstVisibleLine, getWidth(), height); 270 | } 271 | 272 | private void paintSelection(Graphics2D g) { 273 | int selectionStartOffset = editor.getSelectionModel().getSelectionStart(); 274 | int selectionEndOffset = editor.getSelectionModel().getSelectionEnd(); 275 | int firstSelectedLine = coords.offsetToScreenSpace(selectionStartOffset); 276 | int firstSelectedCharacter = coords.offsetToCharacterInLine(selectionStartOffset); 277 | int lastSelectedLine = coords.offsetToScreenSpace(selectionEndOffset); 278 | int lastSelectedCharacter = coords.offsetToCharacterInLine(selectionEndOffset); 279 | 280 | g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.90f)); 281 | g.setColor(editor.getColorsScheme().getColor(ColorKey.createColorKey("SELECTION_BACKGROUND", JBColor.BLUE))); 282 | 283 | if (firstSelectedLine == lastSelectedLine) { 284 | // Single line is easy 285 | g.fillRect(firstSelectedCharacter, firstSelectedLine, lastSelectedCharacter - firstSelectedCharacter, config.pixelsPerLine); 286 | } else { 287 | // Draw the line leading in 288 | g.fillRect(firstSelectedCharacter, firstSelectedLine, getWidth() - firstSelectedCharacter, config.pixelsPerLine); 289 | 290 | // Then the line at the end 291 | g.fillRect(0, lastSelectedLine, lastSelectedCharacter, config.pixelsPerLine); 292 | 293 | if (firstSelectedLine + 1 != lastSelectedLine) { 294 | // And if there is anything in between, fill it in 295 | g.fillRect(0, firstSelectedLine + config.pixelsPerLine, getWidth(), lastSelectedLine - firstSelectedLine - config.pixelsPerLine); 296 | } 297 | } 298 | } 299 | 300 | @Override public void visibleAreaChanged(VisibleAreaEvent visibleAreaEvent) { 301 | // TODO pending http://youtrack.jetbrains.com/issue/IDEABKL-1141 - once fixed this should be a listener 302 | int currentFoldCount = 0; 303 | for (FoldRegion fold: editor.getFoldingModel().getAllFoldRegions()) { 304 | if (!fold.isExpanded()) { 305 | currentFoldCount++; 306 | } 307 | } 308 | 309 | if (currentFoldCount != lastFoldCount) { 310 | updateImage(); 311 | } 312 | 313 | lastFoldCount = currentFoldCount; 314 | 315 | updateSize(); 316 | repaint(); 317 | } 318 | 319 | public void onClose() { 320 | container.removeComponentListener(componentListener); 321 | editor.getDocument().removeDocumentListener(documentListener); 322 | configService.remove(configChangeListener); 323 | editor.getScrollingModel().removeVisibleAreaListener(this); 324 | editor.getSelectionModel().removeSelectionListener(selectionListener); 325 | removeMouseWheelListener(mouseWheelListener); 326 | removeMouseListener(mouseListener); 327 | removeMouseMotionListener(mouseListener); 328 | 329 | componentListener = null; 330 | documentListener = null; 331 | configChangeListener = null; 332 | mouseListener = null; 333 | } 334 | 335 | private class MouseWheelListener implements java.awt.event.MouseWheelListener { 336 | @Override public void mouseWheelMoved(MouseWheelEvent mouseWheelEvent) { 337 | editor.getScrollingModel().scrollVertically(editor.getScrollingModel().getVerticalScrollOffset() + (mouseWheelEvent.getWheelRotation() * editor.getLineHeight() * 3)); 338 | } 339 | } 340 | 341 | private class MouseListener extends MouseAdapter { 342 | private int scrollStart; 343 | private int mouseStart; 344 | private Cursor defaultCursor = new Cursor(Cursor.DEFAULT_CURSOR); 345 | private Cursor resizeCursor = new Cursor(Cursor.W_RESIZE_CURSOR); 346 | private boolean resizing = false; 347 | private boolean dragging = false; 348 | private int resizeStart; 349 | private int widthStart; 350 | 351 | private boolean inResizeGutter(int x) { 352 | return x >= 0 && x < 8; 353 | } 354 | 355 | @Override public void mouseDragged(MouseEvent e) { 356 | 357 | 358 | if (resizing) { 359 | config.width = widthStart + (resizeStart - e.getXOnScreen()); 360 | if (config.width < 1) { 361 | config.width = 1; 362 | } 363 | configService.dispatch().configChanged(); 364 | } 365 | 366 | if (dragging) { 367 | // Disable animation when dragging for better experience. 368 | editor.getScrollingModel().disableAnimation(); 369 | 370 | editor.getScrollingModel().scrollVertically(scrollStart + coords.pixelsToLines(e.getY() - mouseStart) * editor.getLineHeight()); 371 | editor.getScrollingModel().enableAnimation(); 372 | } 373 | } 374 | 375 | @Override public void mousePressed(MouseEvent e) { 376 | if (!dragging && inResizeGutter(e.getX())) { 377 | resizing = true; 378 | } else if (!resizing) { 379 | dragging = true; 380 | } 381 | 382 | if (resizing) { 383 | resizeStart = e.getXOnScreen(); 384 | widthStart = config.width; 385 | } 386 | 387 | if (dragging) { 388 | Rectangle visibleArea = editor.getScrollingModel().getVisibleArea(); 389 | int firstVisibleLine = getMapYFromEditorY((int) visibleArea.getMinY()); 390 | int height = coords.linesToPixels((int) ((visibleArea.getMaxY() - visibleArea.getMinY()) / editor.getLineHeight())); 391 | 392 | int panelY = e.getY() - getY(); 393 | 394 | if (config.jumpOnMouseDown && (panelY <= firstVisibleLine || panelY >= (firstVisibleLine + height))) { 395 | editor.getScrollingModel().disableAnimation(); 396 | editor.getScrollingModel().scrollTo(editor.offsetToLogicalPosition(coords.screenSpaceToOffset(e.getY(), config.percentageBasedClick)), ScrollType.CENTER); 397 | editor.getScrollingModel().enableAnimation(); 398 | } 399 | 400 | scrollStart = editor.getScrollingModel().getVerticalScrollOffset(); 401 | mouseStart = e.getY(); 402 | } 403 | } 404 | 405 | @Override public void mouseReleased(MouseEvent e) { 406 | dragging = false; 407 | resizing = false; 408 | } 409 | 410 | @Override public void mouseClicked(MouseEvent e) { 411 | if (!config.jumpOnMouseDown) { 412 | editor.getScrollingModel().scrollTo(editor.offsetToLogicalPosition(coords.screenSpaceToOffset(e.getY(), config.percentageBasedClick)), ScrollType.CENTER); 413 | } 414 | } 415 | 416 | @Override public void mouseMoved(MouseEvent e) { 417 | if (inResizeGutter(e.getX())) { 418 | setCursor(resizeCursor); 419 | } else { 420 | setCursor(defaultCursor); 421 | } 422 | } 423 | } 424 | } 425 | --------------------------------------------------------------------------------