├── .idea ├── .gitignore ├── dictionaries │ └── ORFJackal.xml ├── ant.xml ├── encodings.xml ├── vcs.xml ├── copyright │ ├── profiles_settings.xml │ └── Apache_2_0.xml ├── inspectionProfiles │ ├── profiles_settings.xml │ └── Project_Default.xml ├── modules.xml ├── libraries │ ├── Maven__junit_junit_4_8_1.xml │ ├── Maven__commons_lang_commons_lang_2_5.xml │ └── Maven__com_miglayout_miglayout_3_7_3_1.xml ├── runConfigurations │ ├── plugin.xml │ ├── SbtRunnerTester.xml │ ├── All_tests_with_coverage.xml │ └── SbtSettingsForm.xml ├── compiler.xml ├── misc.xml └── uiDesigner.xml ├── .gitignore ├── sbt-plugin ├── src │ ├── main │ │ ├── resources │ │ │ ├── sbt-launch.jar │ │ │ ├── MessageBundle.properties │ │ │ └── META-INF │ │ │ │ └── plugin.xml │ │ └── java │ │ │ └── net │ │ │ └── orfjackal │ │ │ └── sbt │ │ │ ├── plugin │ │ │ ├── IO.java │ │ │ ├── sbtlang │ │ │ │ ├── SbtLanguage.java │ │ │ │ ├── SbtFileType.java │ │ │ │ ├── SbtFileViewProviderFactory.java │ │ │ │ └── SbtCompletionContributor.java │ │ │ ├── CompletionSignal.java │ │ │ ├── MessageBundle.java │ │ │ ├── settings │ │ │ │ ├── SbtApplicationSettingsComponent.java │ │ │ │ ├── SbtProjectSettings.java │ │ │ │ ├── SbtConfigurable.java │ │ │ │ ├── SbtApplicationSettings.java │ │ │ │ ├── SbtProjectSettingsComponent.java │ │ │ │ └── SbtSettingsForm.java │ │ │ ├── SbtBeforeRunTask.java │ │ │ ├── SbtColorizerFilter.java │ │ │ ├── SbtProcessHandler.java │ │ │ ├── SelectSbtActionDialog.java │ │ │ ├── SbtBeforeRunTaskProvider.java │ │ │ ├── SbtConsole.java │ │ │ └── SbtRunnerComponent.java │ │ │ └── runner │ │ │ ├── ReaderToWriterCopier.java │ │ │ ├── MulticastPipe.java │ │ │ ├── CyclicCharBuffer.java │ │ │ ├── OutputReader.java │ │ │ ├── ProcessRunner.java │ │ │ └── SbtRunner.java │ └── test │ │ └── java │ │ └── net │ │ └── orfjackal │ │ └── sbt │ │ └── runner │ │ ├── MulticastPipeTest.java │ │ ├── SbtRunnerTester.java │ │ ├── CyclicCharBufferTest.java │ │ └── ProcessRunnerTest.java ├── sbt-plugin.iml └── pom.xml ├── BUILDING.txt ├── src └── main │ └── assembly │ └── src.xml ├── sbt.iml ├── sbt-dist ├── src │ └── main │ │ └── assembly │ │ └── dist.xml └── pom.xml ├── README.md ├── pom.xml └── LICENSE.txt /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | /workspace.xml 2 | scopes/* 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /*/target/ 3 | classes 4 | *.iws 5 | profiles.xml -------------------------------------------------------------------------------- /.idea/dictionaries/ORFJackal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/resources/sbt-launch.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luontola/idea-sbt-plugin/HEAD/sbt-plugin/src/main/resources/sbt-launch.jar -------------------------------------------------------------------------------- /.idea/ant.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /BUILDING.txt: -------------------------------------------------------------------------------- 1 | 2 | You will need to tell Maven that into which directory you have 3 | installed IDEA. Copy profiles.xml.TEMPLATE to profiles.xml and 4 | add the path to your IDEA installation. 5 | 6 | And to build the project within IDEA, you will need to add idea.jar 7 | into the IDEA Plugin SDK. 8 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__junit_junit_4_8_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/resources/MessageBundle.properties: -------------------------------------------------------------------------------- 1 | sbt.tasks.action=SBT Action 2 | 3 | sbt.tasks.before.run.empty=Run SBT Action 4 | sbt.tasks.before.run=Run SBT Action ''{0}'' 5 | 6 | sbt.tasks.select.action.title=Select SBT Action 7 | sbt.tasks.select.action.run.current=Run in current module (requires SBT 0.7.7 or 0.10.x) 8 | sbt.tasks.select.action.run.current.tooltip=IDEA modules must be named the same as the SBT projects 9 | 10 | sbt.tasks.executing=Executing SBT Action 11 | 12 | sbt.config.title=SBT 13 | 14 | sbt.console.id=SBT Console -------------------------------------------------------------------------------- /.idea/copyright/Apache_2_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__commons_lang_commons_lang_2_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_miglayout_miglayout_3_7_3_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/assembly/src.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | src 5 | 6 | zip 7 | 8 | 9 | 10 | 11 | .git/** 12 | 13 | target/** 14 | */target/** 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /sbt.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/plugin/IO.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010-2011, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.plugin; 6 | 7 | import com.intellij.util.PathUtil; 8 | import org.apache.commons.lang.StringUtils; 9 | 10 | import java.io.File; 11 | 12 | public class IO { 13 | public static String canonicalPathTo(File file) { 14 | return PathUtil.getCanonicalPath(file.getAbsolutePath()); 15 | } 16 | 17 | public static String absolutePath(String path) { 18 | if (StringUtils.isEmpty(path)) { 19 | return ""; 20 | } 21 | return new File(path).getAbsolutePath(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/plugin/sbtlang/SbtLanguage.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010-2011, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.plugin.sbtlang; 6 | 7 | 8 | import com.intellij.lang.Language; 9 | import com.intellij.lang.StdLanguages; 10 | import com.intellij.openapi.fileTypes.LanguageFileType; 11 | import org.jetbrains.annotations.NonNls; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | /** A language for input in the SBT console */ 16 | public class SbtLanguage extends Language { 17 | public SbtLanguage() { 18 | super(StdLanguages.TEXT, "SBT"); 19 | } 20 | 21 | public static final Language INSTANCE = new SbtLanguage(); 22 | } 23 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/plugin/CompletionSignal.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.plugin; 6 | 7 | import com.intellij.util.concurrency.Semaphore; 8 | 9 | import java.util.concurrent.atomic.AtomicBoolean; 10 | 11 | public class CompletionSignal { 12 | private final Semaphore done = new Semaphore(); 13 | private final AtomicBoolean result = new AtomicBoolean(false); 14 | 15 | public void begin() { 16 | done.down(); 17 | } 18 | 19 | public void success() { 20 | result.set(true); 21 | } 22 | 23 | public void finished() { 24 | done.up(); 25 | } 26 | 27 | public boolean waitForResult() { 28 | done.waitFor(); 29 | return result.get(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/plugin/sbtlang/SbtFileType.java: -------------------------------------------------------------------------------- 1 | package net.orfjackal.sbt.plugin.sbtlang; 2 | 3 | import com.intellij.openapi.fileTypes.LanguageFileType; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import javax.swing.*; 8 | 9 | public class SbtFileType extends LanguageFileType { 10 | public static final LanguageFileType INSTANCE = new SbtFileType(); 11 | 12 | private SbtFileType() { 13 | super(SbtLanguage.INSTANCE); 14 | } 15 | 16 | @NotNull 17 | public String getName() { 18 | return "_SBT"; 19 | } 20 | 21 | @NotNull 22 | public String getDescription() { 23 | return "Internal file type for idea-sbt-plugin"; 24 | } 25 | 26 | @NotNull 27 | public String getDefaultExtension() { 28 | return "__sbt"; 29 | } 30 | 31 | @Nullable 32 | public Icon getIcon() { 33 | return null; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/plugin/sbtlang/SbtFileViewProviderFactory.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010-2011, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.plugin.sbtlang; 6 | 7 | import com.intellij.lang.Language; 8 | import com.intellij.openapi.vfs.VirtualFile; 9 | import com.intellij.psi.FileViewProvider; 10 | import com.intellij.psi.FileViewProviderFactory; 11 | import com.intellij.psi.PsiManager; 12 | import com.intellij.psi.SingleRootFileViewProvider; 13 | 14 | public class SbtFileViewProviderFactory implements FileViewProviderFactory { 15 | 16 | public FileViewProvider createFileViewProvider(VirtualFile file, Language language, PsiManager manager, boolean physical) { 17 | return new SingleRootFileViewProvider(manager, file, physical, language.getBaseLanguage()) { 18 | 19 | 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sbt-dist/src/main/assembly/dist.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | dist 5 | 6 | zip 7 | 8 | true 9 | ${plugin.id} 10 | 11 | 12 | 13 | 14 | 15 | lib 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | / 25 | .. 26 | 27 | *.txt 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /.idea/runConfigurations/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/runner/ReaderToWriterCopier.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.runner; 6 | 7 | import java.io.*; 8 | 9 | public class ReaderToWriterCopier implements Runnable { 10 | 11 | private final Reader source; 12 | private final Writer target; 13 | 14 | public ReaderToWriterCopier(Reader source, Writer target) { 15 | this.source = source; 16 | this.target = target; 17 | } 18 | 19 | public void run() { 20 | try { 21 | char[] buf = new char[1024]; 22 | int len; 23 | while ((len = source.read(buf)) != -1) { 24 | target.write(buf, 0, len); 25 | } 26 | } catch (IOException e) { 27 | e.printStackTrace(); 28 | } finally { 29 | try { 30 | target.close(); 31 | } catch (IOException e) { 32 | e.printStackTrace(); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/plugin/MessageBundle.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.plugin; 6 | 7 | import com.intellij.CommonBundle; 8 | 9 | import java.lang.ref.*; 10 | import java.util.ResourceBundle; 11 | 12 | public class MessageBundle { 13 | 14 | private static final String BUNDLE = "MessageBundle"; 15 | private static Reference ourBundle; 16 | 17 | private MessageBundle() { 18 | } 19 | 20 | public static String message(String key, Object... params) { 21 | return CommonBundle.message(getBundle(), key, params); 22 | } 23 | 24 | private static ResourceBundle getBundle() { 25 | ResourceBundle bundle = null; 26 | if (ourBundle != null) { 27 | bundle = ourBundle.get(); 28 | } 29 | if (bundle == null) { 30 | bundle = ResourceBundle.getBundle(BUNDLE); 31 | ourBundle = new SoftReference(bundle); 32 | } 33 | return bundle; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 28 | 29 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/plugin/settings/SbtApplicationSettingsComponent.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.plugin.settings; 6 | 7 | import com.intellij.openapi.components.*; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | @State(name = "SbtSettings", storages = {@Storage(id = "default", file = "$APP_CONFIG$/other.xml")}) 11 | public class SbtApplicationSettingsComponent implements PersistentStateComponent, ApplicationComponent { 12 | 13 | private SbtApplicationSettings applicationSettings = new SbtApplicationSettings(); 14 | 15 | public SbtApplicationSettings getState() { 16 | return applicationSettings; 17 | } 18 | 19 | public void loadState(SbtApplicationSettings state) { 20 | applicationSettings = state; 21 | } 22 | 23 | public void initComponent() { 24 | } 25 | 26 | public void disposeComponent() { 27 | } 28 | 29 | @NotNull 30 | public String getComponentName() { 31 | return "SbtSettings"; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.idea/runConfigurations/SbtRunnerTester.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 24 | -------------------------------------------------------------------------------- /sbt-plugin/sbt-plugin.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/runConfigurations/All_tests_with_coverage.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/plugin/settings/SbtProjectSettings.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.plugin.settings; 6 | 7 | import org.apache.commons.lang.builder.EqualsBuilder; 8 | 9 | public class SbtProjectSettings { 10 | private String sbtLauncherJarPath = ""; 11 | private String sbtLauncherVmParameters = ""; 12 | private boolean useApplicationSettings = true; 13 | 14 | public boolean isUseApplicationSettings() { 15 | return useApplicationSettings; 16 | } 17 | 18 | public void setUseApplicationSettings(boolean useApplicationSettings) { 19 | this.useApplicationSettings = useApplicationSettings; 20 | } 21 | 22 | public String getSbtLauncherJarPath() { 23 | return sbtLauncherJarPath; 24 | } 25 | 26 | public void setSbtLauncherJarPath(String sbtLauncherJarPath) { 27 | this.sbtLauncherJarPath = sbtLauncherJarPath; 28 | } 29 | 30 | public String getSbtLauncherVmParameters() { 31 | return sbtLauncherVmParameters; 32 | } 33 | 34 | public void setSbtLauncherVmParameters(String sbtLauncherVmParameters) { 35 | this.sbtLauncherVmParameters = sbtLauncherVmParameters; 36 | } 37 | 38 | public boolean equals(Object obj) { 39 | return EqualsBuilder.reflectionEquals(this, obj); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.idea/runConfigurations/SbtSettingsForm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## idea-sbt-plugin 2 | 3 | Integrates Intellij IDEA with [SBT](scala-sbt.org) to build Scala projects easily and quickly. 4 | 5 | ### DEPRECATION ### 6 | 7 | __JetBrains have recently [added support](https://blog.jetbrains.com/scala/2017/03/23/scala-plugin-for-intellij-idea-2017-1-cleaner-ui-sbt-shell-repl-worksheet-akka-support-and-more/) for an SBT console to the IntellIJ Scala Plugin. This is now the recommended way to use SBT within IntellIJ. No new releases of this plugin are planned.__ 8 | 9 | 10 | ### Documentation 11 | 12 | [User Guide](https://github.com/orfjackal/idea-sbt-plugin/wiki) 13 | 14 | ### Obtaining 15 | 16 | The plugin is distributed through the Plugin Manager in IntelliJ. 17 | 18 | ### Problem? 19 | 20 | Please reports issues to the GitHub issue tracker 21 | 22 | ### Related Projects 23 | 24 | - [sbt-idea](https://github.com/mpeltonen/sbt-idea) An SBT plugin for generating IntelliJ projects from your SBT build 25 | - [intellij-structure](https://github.com/JetBrains/sbt-structure) Developed by JetBrains, a plugin that might eventually take over from sbt-idea AND idea-sbt-plugin. 26 | 27 | ### Building 28 | 29 | To build the project: 30 | 31 | % mvn -Didea.home="/Applications/IntelliJ Idea 15.app/Contents" install 32 | % ls -la sbt-dist/target/idea-sbt-plugin-*.zip # File, Settings, Plugins, Install from Disk 33 | 34 | You can also open this project in IntelliJ. Point the Project SDK to an IntelliJ Plugin SDK, 35 | which you can setup easily by pointing at your IntelliJ installation. 36 | 37 | You can the use the Run Configuration "plugin" to spawn a child IntelliJ process 38 | with your modified version of the plugin. 39 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/runner/MulticastPipe.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.runner; 6 | 7 | import java.io.*; 8 | import java.util.List; 9 | import java.util.concurrent.CopyOnWriteArrayList; 10 | 11 | public class MulticastPipe extends Writer { 12 | 13 | private final List subscribers = new CopyOnWriteArrayList(); 14 | 15 | public Reader subscribe() { 16 | try { 17 | PipedReader r = new PipedReader(); 18 | subscribers.add(new PipedWriter(r)); 19 | return r; 20 | } catch (IOException e) { 21 | throw new RuntimeException(e); 22 | } 23 | } 24 | 25 | private void unsubscribe(PipedWriter w) { 26 | subscribers.remove(w); 27 | } 28 | 29 | public void write(char[] cbuf, int off, int len) throws IOException { 30 | for (PipedWriter w : subscribers) { 31 | try { 32 | w.write(cbuf, off, len); 33 | w.flush(); 34 | } catch (IOException e) { 35 | unsubscribe(w); 36 | } 37 | } 38 | } 39 | 40 | public void flush() throws IOException { 41 | for (PipedWriter w : subscribers) { 42 | try { 43 | w.flush(); 44 | } catch (IOException e) { 45 | unsubscribe(w); 46 | } 47 | } 48 | } 49 | 50 | public void close() throws IOException { 51 | for (PipedWriter w : subscribers) { 52 | try { 53 | w.close(); 54 | } catch (IOException e) { 55 | unsubscribe(w); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /sbt-plugin/src/test/java/net/orfjackal/sbt/runner/MulticastPipeTest.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.runner; 6 | 7 | import org.junit.Test; 8 | 9 | import java.io.*; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | public class MulticastPipeTest { 14 | private final MulticastPipe pipe = new MulticastPipe(); 15 | 16 | @Test 17 | public void readers_see_what_is_written_to_the_pipe() throws IOException { 18 | Reader reader = pipe.subscribe(); 19 | 20 | pipe.write('A'); 21 | assertEquals('A', reader.read()); 22 | } 23 | 24 | @Test 25 | public void multiple_concurrent_readers_will_all_see_the_same_data() throws IOException { 26 | Reader reader1 = pipe.subscribe(); 27 | Reader reader2 = pipe.subscribe(); 28 | 29 | pipe.write('A'); 30 | assertEquals('A', reader1.read()); 31 | assertEquals('A', reader2.read()); 32 | } 33 | 34 | @Test 35 | public void readers_cannot_see_what_was_written_before_they_subscribed() throws IOException { 36 | pipe.write('A'); 37 | Reader reader = pipe.subscribe(); 38 | assertFalse("should not see data, but did", reader.ready()); 39 | } 40 | 41 | @Test 42 | public void closed_readers_cannot_see_any_new_data_but_others_are_not_affected() throws IOException { 43 | Reader closedReader = pipe.subscribe(); 44 | Reader openReader = pipe.subscribe(); 45 | closedReader.close(); 46 | 47 | pipe.write('A'); 48 | 49 | try { 50 | closedReader.read(); 51 | fail("reader not closed"); 52 | } catch (IOException e) { 53 | } 54 | assertEquals('A', openReader.read()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /sbt-dist/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | net.orfjackal.idea-sbt-plugin 7 | sbt 8 | 1.8.1-SNAPSHOT 9 | 10 | 11 | sbt-dist 12 | pom 13 | sbt-dist 14 | 15 | 16 | 17 | 18 | net.orfjackal.idea-sbt-plugin 19 | sbt-plugin 20 | ${project.version} 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | maven-assembly-plugin 30 | 2.2-beta-5 31 | false 32 | 33 | idea-sbt-plugin-${project.version} 34 | false 35 | false 36 | 37 | src/main/assembly/dist.xml 38 | 39 | 40 | 41 | 42 | make-assembly 43 | package 44 | 45 | attached 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/plugin/settings/SbtConfigurable.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.plugin.settings; 6 | 7 | import com.intellij.openapi.options.*; 8 | import net.orfjackal.sbt.plugin.MessageBundle; 9 | 10 | import javax.swing.*; 11 | 12 | public class SbtConfigurable implements Configurable { 13 | // org.jetbrains.idea.maven.utils.MavenSettings 14 | // org.jetbrains.idea.maven.project.MavenImportingConfigurable 15 | 16 | private final SbtProjectSettingsComponent projectSettings; 17 | private final SbtApplicationSettingsComponent applicationSettings; 18 | private final SbtSettingsForm settingsForm = new SbtSettingsForm(); 19 | 20 | public SbtConfigurable(SbtProjectSettingsComponent projectSettings, SbtApplicationSettingsComponent applicationSettings) { 21 | this.projectSettings = projectSettings; 22 | this.applicationSettings = applicationSettings; 23 | } 24 | 25 | public String getDisplayName() { 26 | return MessageBundle.message("sbt.config.title"); 27 | } 28 | 29 | public Icon getIcon() { 30 | return null; 31 | } 32 | 33 | public String getHelpTopic() { 34 | return null; 35 | } 36 | 37 | public JComponent createComponent() { 38 | return settingsForm.createComponent(); 39 | } 40 | 41 | public boolean isModified() { 42 | return settingsForm.isModified(projectSettings.getState(), applicationSettings.getState()); 43 | } 44 | 45 | public void apply() throws ConfigurationException { 46 | settingsForm.copyTo(projectSettings.getState(), applicationSettings.getState()); 47 | } 48 | 49 | public void reset() { 50 | settingsForm.copyFrom(projectSettings.getState(), applicationSettings.getState()); 51 | } 52 | 53 | public void disposeUIResources() { 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/plugin/settings/SbtApplicationSettings.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.plugin.settings; 6 | 7 | import net.orfjackal.sbt.plugin.IO; 8 | import org.apache.commons.lang.builder.EqualsBuilder; 9 | 10 | import java.io.File; 11 | 12 | public class SbtApplicationSettings { 13 | private static final String DEFAULT_SBT_VM_PARAMETERS = "-Xmx512M -XX:MaxPermSize=256M"; 14 | 15 | private String sbtLauncherJarPath = ""; // Will use the bundled launcher; 16 | private String sbtLauncherVmParameters = DEFAULT_SBT_VM_PARAMETERS; 17 | private boolean useCustomJdk = false; 18 | private String jdkHome = null; 19 | 20 | public String getSbtLauncherJarPath() { 21 | return sbtLauncherJarPath; 22 | } 23 | 24 | public void setSbtLauncherJarPath(String sbtLauncherJarPath) { 25 | if (sbtLauncherJarPath.length() == 0) { 26 | this.sbtLauncherJarPath = ""; 27 | } else { 28 | this.sbtLauncherJarPath = IO.canonicalPathTo(new File(sbtLauncherJarPath)); 29 | } 30 | } 31 | 32 | public String getSbtLauncherVmParameters() { 33 | return sbtLauncherVmParameters; 34 | } 35 | 36 | public void setSbtLauncherVmParameters(String sbtLauncherVmParameters) { 37 | this.sbtLauncherVmParameters = sbtLauncherVmParameters; 38 | } 39 | 40 | public boolean isUseCustomJdk() { 41 | return useCustomJdk; 42 | } 43 | 44 | public void setUseCustomJdk(boolean useCustomJdk) { 45 | this.useCustomJdk = useCustomJdk; 46 | } 47 | 48 | public String getJdkHome() { 49 | return jdkHome; 50 | } 51 | 52 | public void setJdkHome(String jdkHome) { 53 | this.jdkHome = jdkHome; 54 | } 55 | 56 | public boolean equals(Object obj) { 57 | return EqualsBuilder.reflectionEquals(this, obj); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/runner/CyclicCharBuffer.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.runner; 6 | 7 | public class CyclicCharBuffer { 8 | 9 | private final char[] buffer; 10 | private int start = 0; 11 | private int length = 0; 12 | 13 | public CyclicCharBuffer(int capacity) { 14 | buffer = new char[capacity]; 15 | } 16 | 17 | public int length() { 18 | return length; 19 | } 20 | 21 | public char charAt(int i) { 22 | return buffer[index(i)]; 23 | } 24 | 25 | private int index(int i) { 26 | return (start + i) % buffer.length; 27 | } 28 | 29 | public void append(char c) { 30 | if (isFull()) { 31 | removeFirst(); 32 | } 33 | insertLast(c); 34 | } 35 | 36 | private boolean isFull() { 37 | return length == buffer.length; 38 | } 39 | 40 | private void removeFirst() { 41 | start++; 42 | length--; 43 | } 44 | 45 | private void insertLast(char c) { 46 | buffer[index(length)] = c; 47 | length++; 48 | } 49 | 50 | public boolean contentEquals(String that) { 51 | return this.length() == that.length() && contentEndsWith(that); 52 | } 53 | 54 | public boolean contentEndsWith(String that) { 55 | if (this.length() < that.length()) { 56 | return false; 57 | } 58 | for (int i = 0; i < that.length(); i++) { 59 | int j = this.length() - that.length() + i; 60 | if (this.charAt(j) != that.charAt(i)) { 61 | return false; 62 | } 63 | } 64 | return true; 65 | } 66 | 67 | public String toString() { 68 | StringBuilder s = new StringBuilder(buffer.length); 69 | for (int i = 0; i < length(); i++) { 70 | s.append(charAt(i)); 71 | } 72 | return s.toString(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 24 | 25 | 30 | 36 | 37 | 38 | http://www.w3.org/1999/xhtml 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /sbt-plugin/src/test/java/net/orfjackal/sbt/runner/SbtRunnerTester.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.runner; 6 | 7 | import java.io.*; 8 | import java.util.Scanner; 9 | 10 | public class SbtRunnerTester { 11 | 12 | private static final File LAUNCHER_JAR = new File(System.getProperty("user.home"), "bin/sbt-launch.jar"); 13 | private static final String[] VM_PARAMS = new String[] {"-Xmx512M"}; 14 | private static final File WORKING_DIR = new File("/tmp"); 15 | 16 | private static SbtRunner sbt; 17 | 18 | public static void main(String[] args) throws Exception { 19 | sbt = new SbtRunner("java", WORKING_DIR, LAUNCHER_JAR, VM_PARAMS); 20 | OutputReader output = sbt.subscribeToOutput(); 21 | sbt.start(true); 22 | 23 | Thread t = new Thread(new Runnable() { 24 | public void run() { 25 | inputLoop(System.in); 26 | } 27 | }); 28 | t.setDaemon(true); 29 | t.start(); 30 | 31 | printLoop(output, System.out); 32 | } 33 | 34 | private static void inputLoop(InputStream source) { 35 | Scanner in = new Scanner(source); 36 | while (true) { 37 | String action = in.nextLine(); 38 | if (action.equals("force-exit")) { 39 | System.exit(1); 40 | } 41 | executeAsynchronously(action); 42 | } 43 | } 44 | 45 | private static void executeAsynchronously(final String action) { 46 | Thread t = new Thread(new Runnable() { 47 | public void run() { 48 | try { 49 | sbt.execute(action); 50 | } catch (IOException e) { 51 | e.printStackTrace(); 52 | } 53 | } 54 | }); 55 | t.setDaemon(true); 56 | t.start(); 57 | } 58 | 59 | private static void printLoop(Reader source, PrintStream target) throws IOException { 60 | int ch; 61 | while ((ch = source.read()) != -1) { 62 | target.print((char) ch); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/runner/OutputReader.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.runner; 6 | 7 | import java.io.*; 8 | import java.util.*; 9 | 10 | public class OutputReader extends FilterReader { 11 | 12 | public static final boolean FOUND = true; 13 | public static final boolean END_OF_OUTPUT = false; 14 | private static final int BUFFER_SIZE = 1024; 15 | private CyclicCharBuffer buffer = new CyclicCharBuffer(BUFFER_SIZE); 16 | 17 | public OutputReader(Reader output) { 18 | super(output); 19 | } 20 | 21 | public boolean waitForOutput(String expected) throws IOException { 22 | return waitForOutput(Arrays.asList(expected), Arrays.asList()); 23 | } 24 | 25 | public boolean waitForOutput(Collection expected, Collection expectedExact) throws IOException { 26 | int max = 0; 27 | for (String s : expected) { 28 | checkExpectedLength(s); 29 | max = Math.max(max, s.length()); 30 | } 31 | int ch; 32 | while ((ch = read()) != -1) { 33 | buffer.append((char) ch); 34 | for (String s : expected) { 35 | if (buffer.contentEndsWith(s)) { 36 | return FOUND; 37 | } 38 | } 39 | for (String s : expectedExact) { 40 | if (buffer.contentEquals(s)) { 41 | return FOUND; 42 | } 43 | } 44 | } 45 | return END_OF_OUTPUT; 46 | } 47 | 48 | public void skipBufferedOutput() throws IOException { 49 | while (ready()) { 50 | skip(1); 51 | } 52 | } 53 | 54 | public boolean endOfOutputContains(String expected) { 55 | checkExpectedLength(expected); 56 | return buffer.toString().contains(expected); 57 | } 58 | 59 | private void checkExpectedLength(String expected) { 60 | if (expected.length() > BUFFER_SIZE) { 61 | throw new IllegalArgumentException("expected string is too long."); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/plugin/settings/SbtProjectSettingsComponent.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.plugin.settings; 6 | 7 | import com.intellij.openapi.components.*; 8 | import com.intellij.openapi.project.Project; 9 | import com.intellij.openapi.util.io.FileUtil; 10 | 11 | import java.io.File; 12 | 13 | @State(name = "SbtSettings", storages = { 14 | @Storage(id = "default", file = "$PROJECT_FILE$", scheme = StorageScheme.DEFAULT), 15 | @Storage(id = "dir", file = "$PROJECT_CONFIG_DIR$/compiler.xml", scheme = StorageScheme.DIRECTORY_BASED)}) 16 | public class SbtProjectSettingsComponent extends AbstractProjectComponent implements PersistentStateComponent { 17 | 18 | private SbtProjectSettings projectSettings = new SbtProjectSettings(); 19 | 20 | public SbtProjectSettingsComponent(Project project) { 21 | super(project); 22 | } 23 | 24 | public SbtProjectSettings getState() { 25 | return projectSettings; 26 | } 27 | 28 | public void loadState(SbtProjectSettings state) { 29 | this.projectSettings = state; 30 | } 31 | 32 | public String effectiveSbtLauncherVmParameters(SbtApplicationSettingsComponent applicationSettings) { 33 | if (projectSettings.isUseApplicationSettings()) { 34 | return applicationSettings.getState().getSbtLauncherVmParameters(); 35 | } else { 36 | return getState().getSbtLauncherVmParameters(); 37 | } 38 | } 39 | 40 | public String effectiveSbtLauncherJarPath(SbtApplicationSettingsComponent applicationSettings) { 41 | if (projectSettings.isUseApplicationSettings()) { 42 | return applicationSettings.getState().getSbtLauncherJarPath(); 43 | } else { 44 | return getState().getSbtLauncherJarPath(); 45 | } 46 | } 47 | 48 | public String getJavaCommand(SbtApplicationSettingsComponent applicationSettings) { 49 | if (applicationSettings.getState().isUseCustomJdk()) { 50 | String systemDependentJdkHome = FileUtil.toSystemDependentName(applicationSettings.getState().getJdkHome()); 51 | return systemDependentJdkHome + File.separator + "bin" + File.separator + "java"; 52 | } else { 53 | return "java"; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/runner/ProcessRunner.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.runner; 6 | 7 | import java.io.*; 8 | 9 | public class ProcessRunner { 10 | 11 | private final ProcessBuilder builder; 12 | 13 | private Process process; 14 | private Thread shutdownHook; 15 | private final MulticastPipe outputMulticast = new MulticastPipe(); 16 | private Writer input; 17 | 18 | public ProcessRunner(File workingDir, String... command) { 19 | builder = new ProcessBuilder(command); 20 | builder.directory(workingDir); 21 | builder.redirectErrorStream(true); 22 | } 23 | 24 | public OutputReader subscribeToOutput() { 25 | return new OutputReader(outputMulticast.subscribe()); 26 | } 27 | 28 | public void start() throws IOException { 29 | process = builder.start(); 30 | shutdownHook = new Thread(new DestroyProcessRunner(process)); 31 | 32 | InputStreamReader output = new InputStreamReader(new BufferedInputStream(process.getInputStream())); 33 | Thread t = new Thread(new ReaderToWriterCopier(output, outputMulticast)); 34 | t.setDaemon(true); 35 | t.start(); 36 | 37 | input = new OutputStreamWriter(new BufferedOutputStream(process.getOutputStream())); 38 | } 39 | 40 | public void destroyOnShutdown() { 41 | Runtime.getRuntime().addShutdownHook(shutdownHook); 42 | } 43 | 44 | public void destroy() { 45 | process.destroy(); 46 | try { 47 | process.waitFor(); 48 | } catch (InterruptedException e) { 49 | e.printStackTrace(); 50 | } 51 | Runtime.getRuntime().removeShutdownHook(shutdownHook); 52 | } 53 | 54 | public boolean isAlive() { 55 | if (process == null) { 56 | return false; 57 | } 58 | try { 59 | process.exitValue(); 60 | return false; 61 | } catch (IllegalThreadStateException e) { 62 | return true; 63 | } 64 | } 65 | 66 | public void writeInput(String s) throws IOException { 67 | input.write(s); 68 | input.flush(); 69 | } 70 | 71 | 72 | private static class DestroyProcessRunner implements Runnable { 73 | private final Process process; 74 | 75 | public DestroyProcessRunner(Process process) { 76 | this.process = process; 77 | } 78 | 79 | public void run() { 80 | process.destroy(); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/plugin/SbtBeforeRunTask.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.plugin; 6 | 7 | import com.intellij.execution.BeforeRunTask; 8 | import com.intellij.openapi.util.Key; 9 | import org.jdom.Element; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | public class SbtBeforeRunTask extends BeforeRunTask { 13 | 14 | public SbtBeforeRunTask(@NotNull Key providerId) { 15 | super(providerId); 16 | runInCurrentModule = true; 17 | action = "test:products"; 18 | } 19 | 20 | private String action; 21 | private boolean runInCurrentModule; 22 | 23 | public String getAction() { 24 | return action; 25 | } 26 | 27 | public void setAction(String action) { 28 | this.action = action; 29 | } 30 | 31 | public boolean isRunInCurrentModule() { 32 | return runInCurrentModule; 33 | } 34 | 35 | public void setRunInCurrentModule(boolean runInCurrentModule) { 36 | this.runInCurrentModule = runInCurrentModule; 37 | } 38 | 39 | @Override 40 | public void writeExternal(Element element) { 41 | super.writeExternal(element); 42 | if (action != null) { 43 | element.setAttribute("action", action); 44 | } 45 | element.setAttribute("runInCurrentModule", Boolean.toString(runInCurrentModule)); 46 | } 47 | 48 | @Override 49 | public void readExternal(Element element) { 50 | super.readExternal(element); 51 | action = element.getAttributeValue("action"); 52 | String runInCurrentModuleText = element.getAttributeValue("runInCurrentModule"); 53 | try { 54 | runInCurrentModule = Boolean.parseBoolean(runInCurrentModuleText); 55 | } catch (Exception e) { 56 | // ignore 57 | } 58 | } 59 | 60 | @Override 61 | public boolean equals(Object o) { 62 | if (this == o) return true; 63 | if (o == null || getClass() != o.getClass()) return false; 64 | if (!super.equals(o)) return false; 65 | 66 | SbtBeforeRunTask that = (SbtBeforeRunTask) o; 67 | 68 | if (runInCurrentModule != that.runInCurrentModule) return false; 69 | if (action != null ? !action.equals(that.action) : that.action != null) return false; 70 | 71 | return true; 72 | } 73 | 74 | @Override 75 | public int hashCode() { 76 | int result = super.hashCode(); 77 | result = 31 * result + (action != null ? action.hashCode() : 0); 78 | result = 31 * result + (runInCurrentModule ? 1 : 0); 79 | return result; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /sbt-plugin/src/test/java/net/orfjackal/sbt/runner/CyclicCharBufferTest.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.runner; 6 | 7 | import org.junit.Test; 8 | 9 | import static org.junit.Assert.*; 10 | 11 | public class CyclicCharBufferTest { 12 | 13 | private static final int UNUSED_CAPACITY = 100; 14 | 15 | @Test 16 | public void initially_the_buffer_is_empty() { 17 | CyclicCharBuffer buffer = new CyclicCharBuffer(UNUSED_CAPACITY); 18 | 19 | assertHasString("", buffer); 20 | } 21 | 22 | @Test 23 | public void the_buffer_contains_what_is_written_there() { 24 | CyclicCharBuffer buffer = new CyclicCharBuffer(UNUSED_CAPACITY); 25 | buffer.append('a'); 26 | buffer.append('b'); 27 | 28 | assertHasString("ab", buffer); 29 | } 30 | 31 | @Test 32 | public void the_first_chars_are_dropped_when_the_buffer_is_filled() { 33 | CyclicCharBuffer buffer = new CyclicCharBuffer(3); 34 | buffer.append('a'); 35 | buffer.append('b'); 36 | buffer.append('c'); 37 | 38 | assertHasString("abc", buffer); 39 | 40 | buffer.append('d'); 41 | 42 | assertHasString("bcd", buffer); 43 | } 44 | 45 | @Test 46 | public void can_test_whether_the_buffer_content_equals_a_string() { 47 | CyclicCharBuffer buffer = new CyclicCharBuffer(3); 48 | buffer.append('a'); 49 | buffer.append('b'); 50 | 51 | assertTrue("should equal ", buffer.contentEquals("ab")); 52 | 53 | for (String s : new String[]{ 54 | "a", // shorter than length 55 | "ax", // different content 56 | "abc", // longer than length 57 | "abcd", // longer than capacity 58 | }) { 59 | assertFalse("should not equal <" + s + ">", buffer.contentEquals(s)); 60 | } 61 | } 62 | 63 | @Test 64 | public void can_check_if_buffer_ends_with_content() { 65 | CyclicCharBuffer buffer = new CyclicCharBuffer(3); 66 | buffer.append('a'); 67 | buffer.append('b'); 68 | 69 | assertTrue("should end with ", buffer.contentEndsWith("ab")); 70 | assertTrue("should end with ", buffer.contentEndsWith("b")); 71 | assertTrue("should end with <>", buffer.contentEndsWith("")); 72 | assertFalse("should end with ", buffer.contentEndsWith("a")); 73 | assertFalse("should end with ", buffer.contentEndsWith("abc")); 74 | } 75 | 76 | private static void assertHasString(String expected, CyclicCharBuffer actual) { 77 | assertEquals("size", expected.length(), actual.length()); 78 | assertEquals("content", expected, actual.toString()); 79 | assertTrue("contentEquals", actual.contentEquals(expected)); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /sbt-plugin/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | net.orfjackal.idea-sbt-plugin 7 | sbt 8 | 1.8.1-SNAPSHOT 9 | 10 | 11 | sbt-plugin 12 | jar 13 | sbt-plugin 14 | 15 | 16 | 17 | 18 | 19 | 20 | com.intellij 21 | openapi 22 | ${idea.version} 23 | system 24 | ${idea.home}/lib/openapi.jar 25 | 26 | 27 | 28 | com.intellij 29 | extensions 30 | ${idea.version} 31 | system 32 | ${idea.home}/lib/extensions.jar 33 | 34 | 35 | 36 | com.intellij 37 | idea 38 | ${idea.version} 39 | system 40 | ${idea.home}/lib/idea.jar 41 | 42 | 43 | 44 | com.intellij 45 | jdom 46 | ${idea.version} 47 | system 48 | ${idea.home}/lib/jdom.jar 49 | 50 | 51 | 52 | com.intellij 53 | util 54 | ${idea.version} 55 | system 56 | ${idea.home}/lib/util.jar 57 | 58 | 59 | 60 | com.intellij 61 | annotations 62 | ${idea.version} 63 | system 64 | ${idea.home}/lib/annotations.jar 65 | 66 | 67 | 68 | 69 | 70 | com.miglayout 71 | miglayout 72 | 3.7.3.1 73 | 74 | 75 | 76 | 77 | 78 | commons-lang 79 | commons-lang 80 | 2.5 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/plugin/SbtColorizerFilter.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | package net.orfjackal.sbt.plugin; 5 | 6 | import com.intellij.execution.filters.Filter; 7 | import com.intellij.ide.highlighter.custom.CustomHighlighterColors; 8 | import com.intellij.openapi.editor.colors.*; 9 | import com.intellij.openapi.editor.markup.TextAttributes; 10 | import com.intellij.openapi.project.DumbAware; 11 | 12 | /** 13 | * Rather than reading the ANSI output of SBT, we just highlight with some regexes. 14 | */ 15 | public class SbtColorizerFilter implements Filter, DumbAware { 16 | 17 | private static final String ERROR_PREFIX = "[error]"; 18 | private static final String INFO_PREFIX = "[info]"; 19 | private static final String SUCCESS_PREFIX = "[success]"; 20 | 21 | // TODO Add a custom ColorSettingsPage for the SBT console. 22 | private static final TextAttributesKey GREEN = CustomHighlighterColors.CUSTOM_STRING_ATTRIBUTES; 23 | private static final TextAttributesKey BLUE = CodeInsightColors.TODO_DEFAULT_ATTRIBUTES; 24 | private static final TextAttributesKey RED = CodeInsightColors.WRONG_REFERENCES_ATTRIBUTES; 25 | private static final TextAttributesKey YELLOW = CodeInsightColors.WARNINGS_ATTRIBUTES; 26 | 27 | public Result applyFilter(final String line, final int textEndOffset) { 28 | if (line == null) { 29 | return null; 30 | } 31 | final int textStartOffset = textEndOffset - line.length(); 32 | if (line.startsWith(ERROR_PREFIX)) { 33 | if (line.matches("\\Q" + ERROR_PREFIX + "\\E\\s+x \\S.*\\n")) { 34 | return new Result(textStartOffset, textEndOffset, null, getAttributes(RED)); 35 | } else { 36 | return new Result(textStartOffset + 1, textStartOffset + ERROR_PREFIX.length() - 1, null, getAttributes(RED)); 37 | } 38 | } else if (line.startsWith(SUCCESS_PREFIX)) { 39 | return new Result(textStartOffset + 1, textStartOffset + SUCCESS_PREFIX.length() - 1, null, getAttributes(GREEN)); 40 | } else if (line.startsWith(INFO_PREFIX)) { 41 | if (line.matches("\\Q" + INFO_PREFIX + "\\E ==.*==.*\\n")) { 42 | return new Result(textStartOffset + INFO_PREFIX.length() + 1, textEndOffset, null, getAttributes(BLUE)); 43 | } else if (line.matches("\\Q" + INFO_PREFIX + "\\E\\s+\\+ \\S.*\\n")) { 44 | return new Result(textStartOffset + INFO_PREFIX.length() + 1, textEndOffset, null, getAttributes(GREEN)); 45 | } else if (line.matches("\\Q" + INFO_PREFIX + "\\E\\s+o \\S.*\\n")) { 46 | return new Result(textStartOffset + INFO_PREFIX.length() + 1, textEndOffset, null, getAttributes(YELLOW)); 47 | } 48 | } 49 | return null; 50 | } 51 | 52 | private TextAttributes getAttributes(TextAttributesKey green) { 53 | return EditorColorsManager.getInstance().getGlobalScheme().getAttributes(green); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/plugin/SbtProcessHandler.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.plugin; 6 | 7 | import com.intellij.execution.process.*; 8 | import com.intellij.openapi.diagnostic.Logger; 9 | import net.orfjackal.sbt.runner.OutputReader; 10 | 11 | import java.io.*; 12 | 13 | public class SbtProcessHandler extends ProcessHandler { 14 | 15 | private static final Logger logger = Logger.getInstance(SbtProcessHandler.class.getName()); 16 | 17 | private final SbtRunnerComponent sbt; 18 | private final OutputReader output; 19 | 20 | public SbtProcessHandler(SbtRunnerComponent sbt, OutputReader output) { 21 | this.sbt = sbt; 22 | this.output = output; 23 | } 24 | 25 | public void startNotify() { 26 | final NotifyWhenTextAvailable outputNotifier = new NotifyWhenTextAvailable(this, output); 27 | 28 | addProcessListener(new ProcessAdapter() { 29 | public void startNotified(ProcessEvent event) { 30 | Thread t = new Thread(outputNotifier); 31 | t.setDaemon(true); 32 | t.start(); 33 | } 34 | }); 35 | 36 | super.startNotify(); 37 | } 38 | 39 | public OutputStream getProcessInput() { 40 | return new ExecuteUserEnteredActions(sbt); 41 | } 42 | 43 | protected void destroyProcessImpl() { 44 | sbt.destroyProcess(); 45 | } 46 | 47 | protected void detachProcessImpl() { 48 | throw new UnsupportedOperationException("SBT cannot be detached"); 49 | } 50 | 51 | public boolean detachIsDefault() { 52 | return false; 53 | } 54 | 55 | 56 | private static class NotifyWhenTextAvailable implements Runnable { 57 | private final SbtProcessHandler process; 58 | private final Reader output; 59 | 60 | public NotifyWhenTextAvailable(SbtProcessHandler process, Reader output) { 61 | this.process = process; 62 | this.output = output; 63 | } 64 | 65 | public void run() { 66 | try { 67 | char[] cbuf = new char[100]; 68 | int len; 69 | while ((len = output.read(cbuf)) != -1) { 70 | String text = new String(cbuf, 0, len); 71 | String withoutCr = text.replace("\r", ""); 72 | process.notifyTextAvailable(withoutCr, ProcessOutputTypes.STDOUT); 73 | } 74 | } catch (IOException e) { 75 | logger.error(e); 76 | } finally { 77 | process.notifyProcessTerminated(0); 78 | } 79 | } 80 | } 81 | 82 | private static class ExecuteUserEnteredActions extends OutputStream { 83 | private final SbtRunnerComponent sbt; 84 | private final StringBuilder commandBuffer = new StringBuilder(); 85 | 86 | public ExecuteUserEnteredActions(SbtRunnerComponent sbt) { 87 | this.sbt = sbt; 88 | } 89 | 90 | public void write(int b) { 91 | char ch = (char) b; 92 | if (ch == '\n') { 93 | sbt.executeInBackground(buildCommand()); 94 | } else { 95 | commandBuffer.append(ch); 96 | } 97 | } 98 | 99 | private String buildCommand() { 100 | String command = commandBuffer.toString().trim(); 101 | commandBuffer.setLength(0); 102 | return command; 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/plugin/sbtlang/SbtCompletionContributor.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010-2011, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.plugin.sbtlang; 6 | 7 | import com.intellij.codeInsight.completion.*; 8 | import com.intellij.codeInsight.lookup.LookupElement; 9 | import com.intellij.codeInsight.lookup.LookupElementBuilder; 10 | import com.intellij.lang.ASTNode; 11 | import com.intellij.lang.LanguageWordCompletion; 12 | import com.intellij.openapi.project.DumbService; 13 | import com.intellij.patterns.PlatformPatterns; 14 | import com.intellij.psi.PlainTextTokenTypes; 15 | import com.intellij.psi.PsiElement; 16 | import com.intellij.psi.PsiFile; 17 | import com.intellij.psi.PsiReference; 18 | import com.intellij.psi.tree.IElementType; 19 | import com.intellij.util.ProcessingContext; 20 | import org.jetbrains.annotations.NotNull; 21 | 22 | import java.util.*; 23 | 24 | import static com.intellij.patterns.StandardPatterns.character; 25 | 26 | // TODO get completion variants from SBT over a telnet-like interface 27 | // TODO target this contributor to the SBT console in a more principled manner (rather than using the getContainingFile.getName == SbtLanguage.INSTACNCE.getID) 28 | 29 | // Adapted from WordCompletionContributor in IDEA core. 30 | public class SbtCompletionContributor extends CompletionContributor { 31 | 32 | @Override 33 | public void fillCompletionVariants(final CompletionParameters parameters, final CompletionResultSet result) { 34 | if (parameters.getCompletionType() == CompletionType.BASIC && shouldPerformWordCompletion(parameters)) { 35 | addWordCompletionVariants(result, parameters, Collections.emptySet()); 36 | } 37 | } 38 | 39 | public static void addWordCompletionVariants(CompletionResultSet result, CompletionParameters parameters, Set excludes) { 40 | int startOffset = parameters.getOffset(); 41 | PsiElement insertedElement = parameters.getPosition(); 42 | // "test-only *A -- opta" gives a prefix of "test-only *A" 43 | String prefix = insertedElement.getText().substring(0, startOffset); 44 | 45 | final CompletionResultSet plainResultSet = result.withPrefixMatcher(prefix); 46 | List strings = Arrays.asList("compile", "run", "test:compile", "test", "project", "projects", "inspect", "show", "last", "last-grep"); 47 | for (String string : strings) { 48 | final LookupElement item = LookupElementBuilder.create(string); 49 | plainResultSet.addElement(item); 50 | } 51 | } 52 | 53 | private static boolean shouldPerformWordCompletion(CompletionParameters parameters) { 54 | final PsiElement insertedElement = parameters.getPosition(); 55 | final boolean dumb = DumbService.getInstance(insertedElement.getProject()).isDumb(); 56 | if (dumb) { 57 | return true; 58 | } 59 | // TODO Hackedy-hack. 60 | if (!insertedElement.getContainingFile().getName().equals("SBT")) { 61 | return false; 62 | } 63 | 64 | if (parameters.getInvocationCount() == 0) { 65 | return false; 66 | } 67 | 68 | final PsiFile file = insertedElement.getContainingFile(); 69 | 70 | final int startOffset = parameters.getOffset(); 71 | 72 | final PsiElement element = file.findElementAt(startOffset - 1); 73 | 74 | ASTNode textContainer = element != null ? element.getNode() : null; 75 | while (textContainer != null) { 76 | final IElementType elementType = textContainer.getElementType(); 77 | if (LanguageWordCompletion.INSTANCE.isEnabledIn(elementType) || elementType == PlainTextTokenTypes.PLAIN_TEXT) { 78 | return true; 79 | } 80 | textContainer = textContainer.getTreeParent(); 81 | } 82 | return false; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /sbt-plugin/src/test/java/net/orfjackal/sbt/runner/ProcessRunnerTest.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.runner; 6 | 7 | import org.junit.*; 8 | 9 | import java.io.*; 10 | import java.util.concurrent.*; 11 | 12 | import static org.junit.Assert.*; 13 | 14 | public class ProcessRunnerTest { 15 | 16 | private ProcessRunner process; 17 | 18 | @Before 19 | public void newProcess() throws IOException { 20 | process = new ProcessRunner(new File("."), "java", "-version"); 21 | } 22 | 23 | @After 24 | public void killProcess() { 25 | if (process != null) { 26 | process.destroy(); 27 | } 28 | } 29 | 30 | @Test 31 | public void is_alive_until_it_is_destroyed() throws IOException { 32 | process.start(); 33 | assertTrue("alive", process.isAlive()); 34 | process.destroy(); 35 | assertFalse("alive", process.isAlive()); 36 | } 37 | 38 | @Test 39 | public void waits_until_the_expected_output_is_printed() throws IOException { 40 | OutputReader output = process.subscribeToOutput(); 41 | process.start(); 42 | 43 | assertTrue(output.waitForOutput("Runtime Environment")); 44 | } 45 | 46 | @Test 47 | public void returns_false_if_the_expected_output_is_not_printed() throws IOException { 48 | OutputReader output = process.subscribeToOutput(); 49 | process.start(); 50 | 51 | assertFalse(output.waitForOutput("will not be found")); 52 | } 53 | 54 | @Test 55 | public void continues_parsing_the_output_from_where_it_was_left() throws IOException { 56 | OutputReader output = process.subscribeToOutput(); 57 | process.start(); 58 | 59 | assertTrue(output.waitForOutput("Runtime")); 60 | assertTrue(output.waitForOutput(" Environment")); 61 | } 62 | 63 | @Test 64 | public void can_skip_buffered_output_without_processing_it() throws IOException { 65 | OutputReader output = process.subscribeToOutput(); 66 | process.start(); 67 | 68 | assertTrue(output.waitForOutput("Runtime")); 69 | output.skipBufferedOutput(); 70 | assertFalse(output.waitForOutput("Environment")); 71 | } 72 | 73 | @Test 74 | public void an_observer_can_see_what_the_process_prints_while_there_are_other_readers() throws IOException { 75 | OutputReader observer = process.subscribeToOutput(); 76 | OutputReader otherReader = process.subscribeToOutput(); 77 | process.start(); 78 | 79 | otherReader.waitForOutput("Environ"); 80 | otherReader.skipBufferedOutput(); 81 | 82 | String output = readFullyAsString(observer); 83 | assertTrue(output, output.contains("Runtime Environment")); 84 | } 85 | 86 | @Test 87 | public void if_two_threads_wait_concurrently_then_both_of_them_will_read_the_same_output() throws Exception { 88 | ExecutorService executor = Executors.newFixedThreadPool(2); 89 | Future t1 = executor.submit(new WaitForOutput("Runtime", process.subscribeToOutput())); 90 | Future t2 = executor.submit(new WaitForOutput("Runtime", process.subscribeToOutput())); 91 | process.start(); 92 | 93 | assertTrue("Thread 1 did not read it", t1.get()); 94 | assertTrue("Thread 2 did not read it", t2.get()); 95 | } 96 | 97 | 98 | private static String readFullyAsString(Reader source) throws IOException { 99 | StringWriter result = new StringWriter(); 100 | char[] buf = new char[1024]; 101 | int len; 102 | while ((len = source.read(buf)) != -1) { 103 | result.write(buf, 0, len); 104 | } 105 | return result.toString(); 106 | } 107 | 108 | private static class WaitForOutput implements Callable { 109 | private final String expected; 110 | private final OutputReader handle; 111 | 112 | public WaitForOutput(String expected, OutputReader handle) { 113 | this.expected = expected; 114 | this.handle = handle; 115 | } 116 | 117 | public Boolean call() throws Exception { 118 | return handle.waitForOutput(expected); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/plugin/SelectSbtActionDialog.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.plugin; 6 | 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.ui.ComboBox; 9 | import com.intellij.openapi.ui.DialogWrapper; 10 | import net.miginfocom.swing.MigLayout; 11 | 12 | import javax.swing.*; 13 | import java.awt.*; 14 | import java.util.*; 15 | import java.util.List; 16 | 17 | public class SelectSbtActionDialog extends DialogWrapper { 18 | 19 | private static final String[] SBT_07_ACTIONS = new String[]{ 20 | "compile", 21 | "test-compile", 22 | ";compile;copy-resources", 23 | ";test-compile;copy-test-resources", 24 | }; 25 | private static final String[] SBT_10_ACTIONS = new String[]{ 26 | "compile", 27 | "test:compile", 28 | "products", 29 | "test:products" 30 | }; 31 | public static final String[] SBT_ACTIONS_WITH_SEPARATOR = combineActions(); 32 | private static final String SBT_07_HEADER = " SBT 0.7.x"; 33 | private static final String SBT_10_HEADER = " SBT 0.10.x"; 34 | private static final String SEPARATOR = "---"; 35 | 36 | private static String[] combineActions() { 37 | List buffer = new ArrayList(); 38 | buffer.addAll(Arrays.asList(SBT_10_ACTIONS)); 39 | return buffer.toArray(new String[buffer.size()]); 40 | } 41 | 42 | public static final List NON_SELECTABLE_ITEMS = Arrays.asList(SBT_07_HEADER, SBT_10_HEADER, SEPARATOR); 43 | 44 | private String selectedAction; 45 | private JComboBox actionField; 46 | private boolean runInCurrentModule; 47 | private JCheckBox runInCurrentModuleField; 48 | 49 | public SelectSbtActionDialog(Project project, String selectedAction, boolean runInCurrentModule) { 50 | super(project, false); 51 | this.selectedAction = selectedAction; 52 | this.runInCurrentModule = runInCurrentModule; 53 | 54 | setTitle(MessageBundle.message("sbt.tasks.select.action.title")); 55 | init(); 56 | } 57 | 58 | public boolean isRunInCurrentModule() { 59 | return runInCurrentModule; 60 | } 61 | 62 | public String getSelectedAction() { 63 | return selectedAction; 64 | } 65 | 66 | protected JComponent createCenterPanel() { 67 | runInCurrentModuleField = new JCheckBox(); 68 | runInCurrentModuleField.setSelected(runInCurrentModule); 69 | JLabel runInCurrentModuleLabel = new JLabel(MessageBundle.message("sbt.tasks.select.action.run.current")); 70 | runInCurrentModuleLabel.setToolTipText(MessageBundle.message("sbt.tasks.select.action.run.current.tooltip")); 71 | 72 | actionField = new ComboBox(SBT_ACTIONS_WITH_SEPARATOR); 73 | actionField.setEditable(true); 74 | actionField.setSelectedItem(selectedAction); 75 | actionField.setRenderer(new DefaultListCellRenderer() { 76 | public Component getListCellRendererComponent(JList jList, Object value, int index, boolean isSelected, boolean cellHasFocus) { 77 | if (value.equals(SEPARATOR)) { 78 | return new JSeparator(JSeparator.HORIZONTAL); 79 | } 80 | if (value.equals(SBT_07_HEADER) || value.equals(SBT_10_HEADER)) { 81 | setEnabled(false); 82 | setText("" + value + ""); 83 | return this; 84 | } 85 | return super.getListCellRendererComponent(jList, value, index, isSelected, cellHasFocus); 86 | } 87 | }); 88 | // TODO Make the headings and separator non-selectable. 89 | 90 | JPanel root = new JPanel(new MigLayout()); 91 | root.add(runInCurrentModuleField, ""); 92 | root.add(runInCurrentModuleLabel, "wrap"); 93 | root.add(actionField, "width 200::, spanx 2"); 94 | return root; 95 | } 96 | 97 | protected void doOKAction() { 98 | super.doOKAction(); 99 | this.selectedAction = nullIfEmpty((String) actionField.getSelectedItem()); 100 | this.runInCurrentModule = runInCurrentModuleField.isSelected(); 101 | } 102 | 103 | private static String nullIfEmpty(String s) { 104 | s = s.trim(); 105 | return s.equals("") ? null : s; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 69 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | net.orfjackal.idea-sbt-plugin 6 | sbt 7 | 1.8.1-SNAPSHOT 8 | pom 9 | 10 | 11 | sbt-plugin 12 | sbt-dist 13 | 14 | 15 | 16 | SBT 17 | SBT 18 | UTF-8 19 | UTF-8 20 | 21 | 26 | 141.177 27 | /Applications/IntelliJ IDEA 14.app/Contents 28 | 29 | 30 | SBT 31 | Intellij IDEA plugin for using SBT. 32 | http://github.com/orfjackal/idea-sbt-plugin 33 | 2010 34 | 35 | 36 | 37 | Apache License 2.0 38 | http://www.apache.org/licenses 39 | 40 | 41 | 42 | 43 | 44 | Esko Luontola 45 | http://www.orfjackal.net 46 | 47 | 48 | 49 | 50 | scm:git:git://github.com/orfjackal/idea-sbt-plugin.git 51 | http://github.com/orfjackal/idea-sbt-plugin 52 | 53 | 54 | 55 | 56 | 57 | junit 58 | junit 59 | 4.8.1 60 | test 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | true 69 | src/main/resources 70 | 71 | **/plugin.xml 72 | 73 | 74 | 75 | false 76 | src/main/resources 77 | 78 | **/plugin.xml 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | maven-compiler-plugin 87 | 2.3.2 88 | 89 | 1.5 90 | 1.5 91 | 92 | 93 | 94 | 95 | maven-surefire-plugin 96 | 2.6 97 | 98 | 99 | **/*Test.java 100 | 101 | 102 | 103 | 104 | 105 | maven-assembly-plugin 106 | 2.2-beta-5 107 | false 108 | 109 | false 110 | 111 | src/main/assembly/src.xml 112 | 113 | 114 | 115 | 116 | make-assembly 117 | package 118 | 119 | attached 120 | 121 | 122 | 123 | 124 | 125 | 126 | maven-source-plugin 127 | 2.1.2 128 | 129 | 130 | attach-sources 131 | verify 132 | 133 | jar 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/runner/SbtRunner.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.runner; 6 | 7 | import com.intellij.openapi.diagnostic.Logger; 8 | import com.intellij.openapi.util.text.StringUtil; 9 | 10 | import javax.swing.*; 11 | import java.io.*; 12 | import java.util.*; 13 | 14 | public class SbtRunner { 15 | 16 | private static final String PROMPT = "\n> "; 17 | private static final String SCALA_PROMPT = "\nscala> "; 18 | private static final String FAILED_TO_COMPILE_PROMPT = "Hit enter to retry or 'exit' to quit:"; 19 | private static final String PROMPT_AFTER_EMPTY_ACTION = "> "; 20 | private static final String ERROR_RUNNING_ACTION_PREFIX = "[error] Error running "; 21 | private static final String ERROR_SBT_010_PREFIX = "[error] Total time:"; 22 | private static final Logger LOG = Logger.getInstance("#orfjackal.sbt.runner.SbtRunner"); 23 | 24 | private final ProcessRunner sbt; 25 | private final String[] command; 26 | 27 | public SbtRunner(String javaCommand, File workingDir, File launcherJar, String[] vmParameters) { 28 | if (!workingDir.isDirectory()) { 29 | throw new IllegalArgumentException("Working directory does not exist: " + workingDir); 30 | } 31 | if (!launcherJar.isFile()) { 32 | throw new IllegalArgumentException("Launcher JAR file does not exist: " + launcherJar); 33 | } 34 | command = getCommand(javaCommand, launcherJar, vmParameters); 35 | sbt = new ProcessRunner(workingDir, command); 36 | } 37 | 38 | public final String getFormattedCommand() { 39 | StringBuilder sb = new StringBuilder(); 40 | for (String s : command) { 41 | if (s.contains(" ")) { 42 | sb.append("\"").append(s).append("\""); 43 | } else { 44 | sb.append(s); 45 | } 46 | sb.append(" "); 47 | } 48 | return sb.toString(); 49 | } 50 | 51 | private static String[] getCommand(String javaCommand, File launcherJar, String[] vmParameters) { 52 | List command = new ArrayList(); 53 | 54 | command.add(javaCommand); 55 | command.add("-Dsbt.log.noformat=true"); 56 | command.add("-Djline.terminal=jline.UnsupportedTerminal"); 57 | command.addAll(Arrays.asList(vmParameters)); 58 | command.addAll(Arrays.asList( 59 | "-jar", 60 | launcherJar.getAbsolutePath() 61 | )); 62 | 63 | LOG.info("SBT command line: " + StringUtil.join(command, " ")); 64 | return command.toArray(new String[command.size()]); 65 | } 66 | 67 | public OutputReader subscribeToOutput() { 68 | return sbt.subscribeToOutput(); 69 | } 70 | 71 | public void start(boolean wait) throws IOException { 72 | // TODO: detect if the directory does not have a project 73 | OutputReader output = sbt.subscribeToOutput(); 74 | sbt.start(); 75 | sbt.destroyOnShutdown(); 76 | if (wait) { 77 | output.waitForOutput(Arrays.asList(PROMPT, FAILED_TO_COMPILE_PROMPT), Arrays.asList()); 78 | } 79 | output.close(); 80 | } 81 | 82 | public void start(boolean wait, final Runnable onStarted) throws IOException { 83 | // TODO: detect if the directory does not have a project 84 | final OutputReader output = sbt.subscribeToOutput(); 85 | sbt.start(); 86 | sbt.destroyOnShutdown(); 87 | if (wait) { 88 | output.waitForOutput(Arrays.asList(PROMPT, FAILED_TO_COMPILE_PROMPT), Arrays.asList()); 89 | output.close(); 90 | onStarted.run(); 91 | } else { 92 | new Thread() { 93 | @Override 94 | public void run() { 95 | try { 96 | output.waitForOutput(Arrays.asList(PROMPT, FAILED_TO_COMPILE_PROMPT), Arrays.asList()); 97 | output.close(); 98 | onStarted.run(); 99 | } catch (IOException e) { 100 | // ignore 101 | } 102 | } 103 | }.start(); 104 | } 105 | } 106 | 107 | public void destroy() { 108 | sbt.destroy(); 109 | } 110 | 111 | public boolean isAlive() { 112 | return sbt.isAlive(); 113 | } 114 | 115 | /** 116 | * @param action the SBT action to run, e.g. "compile" 117 | * @return false if an error was parsed from the output, true otherwise 118 | * @throws java.io.IOException 119 | */ 120 | public boolean execute(String action) throws IOException { 121 | OutputReader output = sbt.subscribeToOutput(); 122 | try { 123 | sbt.writeInput(action + "\n"); 124 | output.waitForOutput(Arrays.asList(PROMPT, SCALA_PROMPT, FAILED_TO_COMPILE_PROMPT), Arrays.asList(PROMPT_AFTER_EMPTY_ACTION)); 125 | } finally { 126 | output.close(); 127 | } 128 | boolean error = output.endOfOutputContains(ERROR_RUNNING_ACTION_PREFIX) || output.endOfOutputContains(ERROR_SBT_010_PREFIX); 129 | LOG.debug("completed: " + action + ", error: " + error); 130 | return !error; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SBT 5 | SBT 6 | ${project.version} 7 | 8 | Build 9 | 10 | Esko Luontola 11 | 12 | 16 | 17 | Offers a console where SBT commands can be entered interactively, and a Before Run task 18 | to delegate project compilation to SBT, as an alternative to the built in IntelliJ Make. 19 |

20 | 21 | Documentation, Screenshots 22 | ]]> 23 | 25 |

1.8.0 (2015-12-03)
26 |
  • Be compatible with / require IntelliJ 15
  • 27 | 28 |
    1.7.0 (2014-03-26)
    29 |
  • Be compatible with / require IntelliJ 14.1
  • 30 |
  • Revert UI to a TextConsole rather than a LanguageConsole. We lose command history. Hopefully we can find a way to restore this feature in a subsequent release once we adapt to changes in IntelliJ
  • 31 | 32 |
    1.6.0 (2014-05-17)
    33 |
  • Require IntelliJ IDEA 13.1 (Cardea)
  • 34 | 35 |
    1.6.1 (2014-05-17)
    36 |
  • Relax version requirement to admit IDEA 13.1.0 (Cardea)
  • 37 |
    1.6.0 (2014-05-17)
    38 |
  • Require IntelliJ IDEA 13.1 (Cardea)
  • 39 | 40 |
    1.5.1 (2013-10-17)
    41 |
  • Compatibility with IntelliJ IDEA 13 (Cardea)
  • 42 | 43 |
    1.5.0 (2011-07-21)
    44 |
  • Require IntelliJ IDEA 12 (Leda)
  • 45 |
  • Cleaner UI for Before Run tasks
  • 46 | 47 |
    1.4.0
    48 |
  • Require IntelliJ 11 (Nika)
  • 49 |
  • Change UI for the console to use a 'Language Console' #42
  • 50 |
  • Don't activate the SBT tool window on project opening. #47
  • 51 |
  • Allow the JRE that runs SBT to be specified. #50
  • 52 |
  • Support console history of entered commands
  • 53 |
  • Default Before Run action to test:products
  • 54 |
  • Bundle SBT 0.12 Launcher (compatible with builds in 0.7.x and above)
  • 55 |
  • Avoid bug with empty VM options
  • 56 |
  • Add framework for autocompletion. Currently, the completions are not context sensitive, and only offer a hard coded list of common actions.
  • 57 |
  • Avoid spawning endless background tasks when using the Scala REPL inside the SBT session.
  • 58 |
  • Avoid JLine warning in the REPL. #49
  • 59 | 60 |
    1.3.1 (2011-09-22)
    61 |
  • Compability with IDEA 11
  • 62 | 63 |
    1.3.0 (2011-06-25)
    64 |
  • Workaround IDEA 10.5 Bug that corrupted text entry after a CR was output
  • 65 |
  • Allow use of the SBT Console during re-indexing
  • 66 |
  • Remove support for synchronizing the IDEA target directory with the SBT target directory after each action
  • 67 |
  • Allow per-project configuration of SBT Launcher JAR and VM Parameters.
  • 68 |
  • Add option to run the "Before Launch" tasks in the current module
  • 69 |
  • Improve notification of build failures in a "Before Launch" task
  • 70 | 71 |
    1.2.0 (2011-04-21)
    72 |
  • Fails the build if there is an error in running an action (thanks Jason Zaugg)
  • 73 |
  • More toolbar buttons in SBT Console (thanks Jason Zaugg)
  • 74 |
  • Colors in SBT console output (thanks Jason Zaugg)
  • 75 |
  • Some bug fixes (thanks Jason Zaugg)
  • 76 | 77 |
    1.1.0 (2010-12-02)
    78 |
  • Links from SBT error messages to the editor (thanks Ismael Juma)
  • 79 |
  • An always available SBT Console tool window (thanks Ismael Juma)
  • 80 |
  • VM parameters for SBT can be changed from the plugin settings (thanks Jason Zaugg)
  • 81 | 82 |
    1.0.0 (2010-06-17)
    83 |
  • Runs SBT commands as "Before Launch" tasks in run configurations
  • 84 |
  • Runs SBT commands which are entered manually to the SBT messages window
  • 85 |
  • Configures module compiler output directories automatically
  • 86 | 87 | 88 | ]]> 89 | 90 | 91 | 92 | 93 | 94 | 95 | 97 | 98 | 99 | 100 | 101 | net.orfjackal.sbt.plugin.SbtRunnerComponent 102 | 103 | 104 | 105 | net.orfjackal.sbt.plugin.settings.SbtProjectSettingsComponent 106 | 107 | 108 | 109 | 110 | 111 | 112 | net.orfjackal.sbt.plugin.settings.SbtApplicationSettingsComponent 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/plugin/SbtBeforeRunTaskProvider.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.plugin; 6 | 7 | import com.intellij.execution.BeforeRunTaskProvider; 8 | import com.intellij.execution.configurations.ModuleBasedConfiguration; 9 | import com.intellij.execution.configurations.RunConfiguration; 10 | import com.intellij.execution.runners.ExecutionEnvironment; 11 | import com.intellij.execution.ui.layout.ViewContext; 12 | import com.intellij.openapi.actionSystem.DataContext; 13 | import com.intellij.openapi.actionSystem.PlatformDataKeys; 14 | import com.intellij.openapi.application.Application; 15 | import com.intellij.openapi.application.ApplicationManager; 16 | import com.intellij.openapi.application.ModalityState; 17 | import com.intellij.openapi.compiler.CompilationStatusListener; 18 | import com.intellij.openapi.compiler.CompilerTopics; 19 | import com.intellij.openapi.compiler.DummyCompileContext; 20 | import com.intellij.openapi.diagnostic.Logger; 21 | import com.intellij.openapi.module.Module; 22 | import com.intellij.openapi.project.Project; 23 | import com.intellij.openapi.ui.MessageType; 24 | import com.intellij.openapi.util.Computable; 25 | import com.intellij.openapi.util.Key; 26 | import com.intellij.openapi.wm.StatusBar; 27 | import com.intellij.openapi.wm.ToolWindow; 28 | import com.intellij.openapi.wm.ToolWindowId; 29 | import com.intellij.openapi.wm.ToolWindowManager; 30 | import com.intellij.util.messages.MessageBus; 31 | import com.intellij.util.messages.Topic; 32 | 33 | public class SbtBeforeRunTaskProvider extends BeforeRunTaskProvider { 34 | 35 | private static final Logger logger = Logger.getInstance(SbtBeforeRunTaskProvider.class.getName()); 36 | 37 | private static final Key TASK_ID = Key.create("SBT.BeforeRunTask"); 38 | private final Project project; 39 | 40 | public SbtBeforeRunTaskProvider(Project project) { 41 | this.project = project; 42 | } 43 | 44 | public Key getId() { 45 | return TASK_ID; 46 | } 47 | 48 | // Leda 49 | public String getName() { 50 | return "SBT"; 51 | } 52 | 53 | public String getDescription(SbtBeforeRunTask task) { 54 | String desc = task.getAction(); 55 | return desc == null 56 | ? MessageBundle.message("sbt.tasks.before.run.empty") 57 | : MessageBundle.message("sbt.tasks.before.run", desc); 58 | } 59 | 60 | // Leda 61 | public boolean isConfigurable() { 62 | return true; 63 | } 64 | 65 | // Leda 66 | public boolean canExecuteTask(RunConfiguration runConfiguration, SbtBeforeRunTask sbtBeforeRunTask) { 67 | return true; 68 | } 69 | 70 | public SbtBeforeRunTask createTask(RunConfiguration runConfiguration) { 71 | return new SbtBeforeRunTask(TASK_ID); 72 | } 73 | 74 | public boolean configureTask(RunConfiguration runConfiguration, SbtBeforeRunTask task) { 75 | SelectSbtActionDialog dialog = new SelectSbtActionDialog(project, task.getAction(), task.isRunInCurrentModule()); 76 | 77 | dialog.show(); 78 | if (!dialog.isOK()) { 79 | return false; 80 | } 81 | 82 | task.setRunInCurrentModule(dialog.isRunInCurrentModule()); 83 | task.setAction(dialog.getSelectedAction()); 84 | return true; 85 | } 86 | 87 | // Nika 88 | public boolean executeTask(DataContext dataContext, RunConfiguration runConfiguration, final SbtBeforeRunTask task) { 89 | final String action = task.getAction(); 90 | if (action == null) { 91 | return false; 92 | } 93 | 94 | boolean runResult = run(runConfiguration, task, action); 95 | if (!runResult) { 96 | final Project project = PlatformDataKeys.PROJECT.getData(dataContext); 97 | if (project != null) { 98 | notifyFailureAndActivateToolWindow(action, project); 99 | } 100 | } 101 | return runResult; 102 | } 103 | 104 | // Leda 105 | public boolean executeTask(DataContext dataContext, RunConfiguration runConfiguration, ExecutionEnvironment executionEnvironment, 106 | SbtBeforeRunTask sbtBeforeRunTask) { 107 | return executeTask(dataContext, runConfiguration, sbtBeforeRunTask); 108 | } 109 | 110 | private boolean run(RunConfiguration runConfiguration, SbtBeforeRunTask task, String action) { 111 | if (task.isRunInCurrentModule() && runConfiguration instanceof ModuleBasedConfiguration) { 112 | final ModuleBasedConfiguration moduleBasedConfiguration = (ModuleBasedConfiguration) runConfiguration; 113 | Module[] modules = safeGetModules(moduleBasedConfiguration); 114 | if (modules.length == 1) { 115 | Module module = modules[0]; 116 | return runInModule(action, module.getName()); 117 | } 118 | } 119 | 120 | return runDirectly(action); 121 | } 122 | 123 | private Module[] safeGetModules(final ModuleBasedConfiguration moduleBasedConfiguration) { 124 | // Read action is a workaround for http://youtrack.jetbrains.com/issue/SCL-4716 125 | return ApplicationManager.getApplication().runReadAction(new Computable() { 126 | public Module[] compute() { 127 | return moduleBasedConfiguration.getModules(); 128 | } 129 | }); 130 | } 131 | 132 | private boolean runInModule(String action, String moduleName) { 133 | runAndWait("project " + moduleName); 134 | try { 135 | return runAndWait(action); 136 | } catch (Exception e) { 137 | logger.error(e); 138 | return false; 139 | } finally { 140 | try { 141 | runAndWait("project /"); 142 | } catch (Exception e1) { 143 | // ignore 144 | } 145 | } 146 | } 147 | 148 | private boolean runDirectly(String action) { 149 | try { 150 | return runAndWait(action); 151 | } catch (Exception e) { 152 | logger.error(e); 153 | return false; 154 | } 155 | } 156 | 157 | private boolean runAndWait(String action) { 158 | return SbtRunnerComponent.getInstance(project) 159 | .executeInBackground(action) 160 | .waitForResult(); 161 | } 162 | 163 | private void notifyFailureAndActivateToolWindow(final String action, final Project project) { 164 | ApplicationManager.getApplication().invokeAndWait(new Runnable() { 165 | public void run() { 166 | String toolWindowId = MessageBundle.message("sbt.console.id"); 167 | String message = "SBT action \"" + action + "\" failed."; 168 | ToolWindowManager.getInstance(project).notifyByBalloon(toolWindowId, MessageType.ERROR, message); 169 | final ToolWindow window = ToolWindowManager.getInstance(project).getToolWindow(toolWindowId); 170 | if (window != null && !window.isVisible()) { 171 | window.activate(null, false); 172 | } 173 | SbtRunnerComponent.getInstance(project).getConsole().scrollToEnd(); 174 | } 175 | }, ModalityState.NON_MODAL); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/plugin/settings/SbtSettingsForm.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.plugin.settings; 6 | 7 | import com.intellij.execution.ui.AlternativeJREPanel; 8 | import com.intellij.openapi.util.io.FileUtil; 9 | import net.miginfocom.swing.MigLayout; 10 | import net.orfjackal.sbt.plugin.IO; 11 | 12 | import javax.swing.*; 13 | import javax.swing.filechooser.FileFilter; 14 | import java.awt.event.*; 15 | import java.io.File; 16 | 17 | public class SbtSettingsForm { 18 | // org.jetbrains.idea.maven.project.MavenImportingSettingsForm 19 | 20 | private final JPanel root; 21 | private final JTextField applicationSbtLauncherJarPath = new JTextField(); 22 | private final JTextField applicationVmParameters = new JTextField(); 23 | private final JCheckBox useApplicationSettings = new JCheckBox(); 24 | private final JTextField projectSbtLauncherJarPath = new JTextField(); 25 | private final JTextField projectVmParameters = new JTextField(); 26 | private JLabel projectSbtLauncherLabel = new JLabel("SBT launcher JAR file (sbt-launch.jar)"); 27 | private JLabel projectVmParametersLabel = new JLabel("VM parameters"); 28 | private AlternativeJREPanel jreChooser = new AlternativeJREPanel(); 29 | 30 | public SbtSettingsForm() { 31 | 32 | JPanel projectSettings = new JPanel(new MigLayout("", "[grow]", "[nogrid]")); 33 | projectSettings.setBorder(BorderFactory.createTitledBorder("Project Settings")); 34 | { 35 | useApplicationSettings.setText("Use IDE Settings"); 36 | useApplicationSettings.setMnemonic('U'); 37 | useApplicationSettings.addActionListener(new ActionListener() { 38 | public void actionPerformed(ActionEvent actionEvent) { 39 | enableOrDisableProjectSettings(); 40 | } 41 | }); 42 | projectSettings.add(useApplicationSettings, "wrap"); 43 | } 44 | { 45 | projectSbtLauncherLabel.setDisplayedMnemonic('C'); 46 | projectSbtLauncherLabel.setLabelFor(projectSbtLauncherJarPath); 47 | 48 | JButton browse = new JButton("..."); 49 | browse.addActionListener(new ActionListener() { 50 | public void actionPerformed(ActionEvent e) { 51 | browseForSbtLauncherJar(SbtSettingsForm.this.projectSbtLauncherJarPath); 52 | } 53 | }); 54 | 55 | projectSettings.add(projectSbtLauncherLabel, "wrap"); 56 | projectSettings.add(projectSbtLauncherJarPath, "growx"); 57 | projectSettings.add(browse, "wrap"); 58 | } 59 | { 60 | projectVmParametersLabel.setDisplayedMnemonic('M'); 61 | projectVmParametersLabel.setLabelFor(projectVmParameters); 62 | projectSettings.add(projectVmParametersLabel, "wrap"); 63 | projectSettings.add(projectVmParameters, "growx"); 64 | } 65 | 66 | JPanel ideSettings = new JPanel(new MigLayout("", "[grow]", "[nogrid]")); 67 | ideSettings.setBorder(BorderFactory.createTitledBorder("IDE Settings")); 68 | { 69 | JLabel label = new JLabel("SBT launcher JAR file (sbt-launch.jar). Leave blank to use the bundled launcher."); 70 | label.setDisplayedMnemonic('L'); 71 | label.setLabelFor(applicationSbtLauncherJarPath); 72 | label.setToolTipText("When using the bundled launcher, ./project/build.properties should contain the desired SBT version, e.g. sbt.version=0.12.0"); 73 | 74 | JButton browse = new JButton("..."); 75 | browse.addActionListener(new ActionListener() { 76 | public void actionPerformed(ActionEvent e) { 77 | browseForSbtLauncherJar(SbtSettingsForm.this.applicationSbtLauncherJarPath); 78 | } 79 | }); 80 | 81 | ideSettings.add(label, "wrap"); 82 | ideSettings.add(applicationSbtLauncherJarPath, "growx"); 83 | ideSettings.add(browse, "wrap"); 84 | } 85 | { 86 | JLabel label = new JLabel("VM parameters"); 87 | label.setDisplayedMnemonic('V'); 88 | label.setLabelFor(applicationVmParameters); 89 | ideSettings.add(label, "wrap"); 90 | ideSettings.add(applicationVmParameters, "wrap, growx"); 91 | ideSettings.add(jreChooser, "growx"); 92 | } 93 | 94 | root = new JPanel(new MigLayout("wrap 1", "[grow]")); 95 | root.add(projectSettings, "grow"); 96 | root.add(ideSettings, "grow"); 97 | enableOrDisableProjectSettings(); 98 | } 99 | 100 | private void enableOrDisableProjectSettings() { 101 | boolean enable = !useApplicationSettings.isSelected(); 102 | projectSbtLauncherLabel.setEnabled(enable); 103 | projectSbtLauncherJarPath.setEnabled(enable); 104 | projectVmParameters.setEnabled(enable); 105 | projectVmParametersLabel.setEnabled(enable); 106 | } 107 | 108 | private void browseForSbtLauncherJar(JTextField launcherJarTextField) { 109 | JFileChooser chooser = new JFileChooser(); 110 | chooser.setFileFilter(new FileFilter() { 111 | public boolean accept(File file) { 112 | return file.isDirectory() || 113 | file.getName().toLowerCase().endsWith(".jar"); 114 | } 115 | 116 | public String getDescription() { 117 | return "JAR files (*.jar)"; 118 | } 119 | }); 120 | 121 | File oldValue = new File(launcherJarTextField.getText()); 122 | chooser.setCurrentDirectory(oldValue); 123 | chooser.setSelectedFile(oldValue); 124 | 125 | int result = chooser.showOpenDialog(root); 126 | if (result == JFileChooser.APPROVE_OPTION) { 127 | launcherJarTextField.setText(chooser.getSelectedFile().getAbsolutePath()); 128 | } 129 | } 130 | 131 | public JComponent createComponent() { 132 | return root; 133 | } 134 | 135 | public boolean isModified(SbtProjectSettings projectSettings, SbtApplicationSettings applicationSettings) { 136 | SbtProjectSettings currentProj = new SbtProjectSettings(); 137 | SbtApplicationSettings currentApp = new SbtApplicationSettings(); 138 | copyTo(currentProj, currentApp); 139 | return !currentProj.equals(projectSettings) || 140 | !currentApp.equals(applicationSettings); 141 | } 142 | 143 | public void copyTo(SbtProjectSettings projectSettings, SbtApplicationSettings applicationSettings) { 144 | projectSettings.setUseApplicationSettings(useApplicationSettings.isSelected()); 145 | if (projectSbtLauncherJarPath.getText().length() == 0) { 146 | projectSettings.setSbtLauncherJarPath(""); 147 | } else { 148 | projectSettings.setSbtLauncherJarPath(FileUtil.toSystemIndependentName(projectSbtLauncherJarPath.getText())); 149 | } 150 | projectSettings.setSbtLauncherVmParameters(projectVmParameters.getText()); 151 | if (applicationSbtLauncherJarPath.getText().length() == 0) { 152 | applicationSettings.setSbtLauncherJarPath(""); 153 | } else { 154 | applicationSettings.setSbtLauncherJarPath(FileUtil.toSystemIndependentName(applicationSbtLauncherJarPath.getText())); 155 | } 156 | applicationSettings.setSbtLauncherVmParameters(applicationVmParameters.getText()); 157 | applicationSettings.setUseCustomJdk(jreChooser.isPathEnabled()); 158 | applicationSettings.setJdkHome(jreChooser.getPath()); 159 | enableOrDisableProjectSettings(); 160 | } 161 | 162 | public void copyFrom(SbtProjectSettings projectSettings, SbtApplicationSettings applicationSettings) { 163 | if (projectSettings.getSbtLauncherJarPath().length() == 0) { 164 | projectSbtLauncherJarPath.setText(""); 165 | } else { 166 | projectSbtLauncherJarPath.setText(FileUtil.toSystemDependentName(IO.absolutePath(projectSettings.getSbtLauncherJarPath()))); 167 | } 168 | projectVmParameters.setText(projectSettings.getSbtLauncherVmParameters()); 169 | useApplicationSettings.setSelected(projectSettings.isUseApplicationSettings()); 170 | if (applicationSettings.getSbtLauncherJarPath().length() == 0) { 171 | applicationSbtLauncherJarPath.setText(""); 172 | } else { 173 | applicationSbtLauncherJarPath.setText(FileUtil.toSystemDependentName(IO.absolutePath(applicationSettings.getSbtLauncherJarPath()))); 174 | } 175 | applicationVmParameters.setText(applicationSettings.getSbtLauncherVmParameters()); 176 | jreChooser.init(applicationSettings.getJdkHome(), applicationSettings.isUseCustomJdk()); 177 | } 178 | 179 | public static void main(String[] args) { 180 | SbtSettingsForm form = new SbtSettingsForm(); 181 | form.copyFrom(new SbtProjectSettings(), new SbtApplicationSettings()); 182 | 183 | JFrame frame = new JFrame("Test: SbtSettingsForm"); 184 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 185 | frame.setContentPane(form.createComponent()); 186 | frame.setSize(600, 600); 187 | frame.setLocation(500, 300); 188 | frame.setVisible(true); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/plugin/SbtConsole.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.plugin; 6 | 7 | import com.intellij.execution.console.LanguageConsoleImpl; 8 | import com.intellij.execution.filters.*; 9 | import com.intellij.execution.impl.ConsoleViewImpl; 10 | import com.intellij.execution.process.ProcessAdapter; 11 | import com.intellij.execution.process.ProcessEvent; 12 | import com.intellij.execution.process.ProcessHandler; 13 | import com.intellij.execution.ui.ConsoleView; 14 | import com.intellij.execution.ui.ConsoleViewContentType; 15 | import com.intellij.openapi.actionSystem.*; 16 | import com.intellij.openapi.application.ApplicationManager; 17 | import com.intellij.openapi.diagnostic.Logger; 18 | import com.intellij.openapi.project.DumbAwareAction; 19 | import com.intellij.openapi.project.Project; 20 | import com.intellij.openapi.ui.SimpleToolWindowPanel; 21 | import com.intellij.openapi.util.IconLoader; 22 | import com.intellij.openapi.util.Key; 23 | import com.intellij.openapi.wm.ToolWindow; 24 | import com.intellij.openapi.wm.ToolWindowManager; 25 | import com.intellij.psi.search.GlobalSearchScope; 26 | import com.intellij.ui.content.Content; 27 | import com.intellij.ui.content.ContentFactory; 28 | 29 | import javax.swing.*; 30 | import java.awt.*; 31 | import java.util.Arrays; 32 | import java.util.concurrent.atomic.AtomicBoolean; 33 | 34 | public class SbtConsole { 35 | // org.jetbrains.idea.maven.embedder.MavenConsoleImpl 36 | 37 | private static final Logger logger = Logger.getInstance(SbtConsole.class.getName()); 38 | 39 | private static final Key CONSOLE_KEY = Key.create("SBT_CONSOLE_KEY"); 40 | 41 | public static final String CONSOLE_FILTER_REGEXP = 42 | "\\s" + RegexpFilter.FILE_PATH_MACROS + ":" + RegexpFilter.LINE_MACROS + ":\\s"; 43 | 44 | private final String title; 45 | private final Project project; 46 | private final ConsoleView consoleView; 47 | private final AtomicBoolean isOpen = new AtomicBoolean(false); 48 | private final SbtRunnerComponent runnerComponent; 49 | private boolean finished = false; 50 | 51 | public SbtConsole(String title, Project project, SbtRunnerComponent runnerComponent) { 52 | this.title = title; 53 | this.project = project; 54 | this.consoleView = createConsoleView(project); 55 | this.runnerComponent = runnerComponent; 56 | } 57 | 58 | private static ConsoleView createConsoleView(Project project) { 59 | // TODO can we figure out how to make this a LanguageConsole with IDEA 14.1+ 60 | // We need that for console history 61 | ConsoleView consoleView = createTextConsole(project); 62 | addFilters(project, consoleView); 63 | return consoleView; 64 | } 65 | 66 | private static ConsoleView createTextConsole(final Project project) { 67 | TextConsoleBuilder builder = TextConsoleBuilderFactory.getInstance().createBuilder(project); 68 | 69 | final SbtColorizerFilter logLevelFilter = new SbtColorizerFilter(); 70 | final ExceptionFilter exceptionFilter = new ExceptionFilter(GlobalSearchScope.allScope(project)); 71 | final RegexpFilter regexpFilter = new RegexpFilter(project, CONSOLE_FILTER_REGEXP); 72 | for (Filter filter : Arrays.asList(exceptionFilter, regexpFilter, logLevelFilter)) { 73 | builder.addFilter(filter); 74 | } 75 | return builder.getConsole(); 76 | } 77 | 78 | private static void addFilters(Project project, ConsoleView consoleView) { 79 | consoleView.addMessageFilter(new ExceptionFilter(GlobalSearchScope.allScope(project))); 80 | consoleView.addMessageFilter(new RegexpFilter(project, CONSOLE_FILTER_REGEXP)); 81 | consoleView.addMessageFilter(new SbtColorizerFilter()); 82 | } 83 | 84 | public boolean isFinished() { 85 | return finished; 86 | } 87 | 88 | public void finish() { 89 | finished = true; 90 | } 91 | 92 | public void attachToProcess(ProcessHandler processHandler, final SbtRunnerComponent runnerComponent) { 93 | consoleView.print(runnerComponent.getFormattedCommand() + "\n\n", ConsoleViewContentType.SYSTEM_OUTPUT); 94 | consoleView.attachToProcess(processHandler); 95 | processHandler.addProcessListener(new ProcessAdapter() { 96 | public void onTextAvailable(ProcessEvent event, Key outputType) { 97 | ApplicationManager.getApplication().invokeLater(new Runnable() { 98 | public void run() { 99 | ToolWindow window = ToolWindowManager.getInstance(project).getToolWindow(MessageBundle.message("sbt.console.id")); 100 | /* When we retrieve a window from ToolWindowManager before SbtToolWindowFactory is called, 101 | * we get an undesirable Content */ 102 | for (Content each : window.getContentManager().getContents()) { 103 | if (each.getUserData(CONSOLE_KEY) == null) { 104 | window.getContentManager().removeContent(each, false); 105 | } 106 | } 107 | ensureAttachedToToolWindow(window, true); 108 | } 109 | }); 110 | } 111 | 112 | public void processTerminated(ProcessEvent event) { 113 | finish(); 114 | } 115 | }); 116 | } 117 | 118 | public final void ensureAttachedToToolWindow(ToolWindow window, boolean activate) { 119 | if (!isOpen.compareAndSet(false, true)) { 120 | return; 121 | } 122 | attachToToolWindow(window); 123 | if (activate) { 124 | if (!window.isActive()) { 125 | window.activate(null, false); 126 | } 127 | } 128 | } 129 | 130 | public void attachToToolWindow(ToolWindow window) { 131 | // org.jetbrains.idea.maven.embedder.MavenConsoleImpl#ensureAttachedToToolWindow 132 | SimpleToolWindowPanel toolWindowPanel = new SimpleToolWindowPanel(false, true); 133 | JComponent consoleComponent = consoleView.getComponent(); 134 | toolWindowPanel.setContent(consoleComponent); 135 | StartSbtAction startSbtAction = new StartSbtAction(); 136 | toolWindowPanel.setToolbar(createToolbar(startSbtAction)); 137 | startSbtAction.registerCustomShortcutSet(CommonShortcuts.getRerun(), consoleComponent); 138 | 139 | Content content = ContentFactory.SERVICE.getInstance().createContent(toolWindowPanel, title, true); 140 | content.putUserData(CONSOLE_KEY, SbtConsole.this); 141 | 142 | window.getContentManager().addContent(content); 143 | window.getContentManager().setSelectedContent(content); 144 | 145 | removeUnusedTabs(window, content); 146 | } 147 | 148 | private JComponent createToolbar(AnAction startSbtAction) { 149 | JPanel toolbarPanel = new JPanel(new GridLayout()); 150 | 151 | DefaultActionGroup group = new DefaultActionGroup(); 152 | 153 | AnAction killSbtAction = new KillSbtAction(); 154 | 155 | group.add(startSbtAction); 156 | group.add(killSbtAction); 157 | 158 | // Adds "Next/Prev hyperlink", "Use Soft Wraps", and "Scroll to End" 159 | AnAction[] actions = consoleView.createConsoleActions(); 160 | for (AnAction action : actions) { 161 | group.add(action); 162 | } 163 | 164 | toolbarPanel.add(ActionManager.getInstance().createActionToolbar("SbtConsoleToolbar", group, false).getComponent()); 165 | return toolbarPanel; 166 | } 167 | 168 | private void removeUnusedTabs(ToolWindow window, Content content) { 169 | for (Content each : window.getContentManager().getContents()) { 170 | if (each.isPinned()) { 171 | continue; 172 | } 173 | if (each == content) { 174 | continue; 175 | } 176 | 177 | SbtConsole console = each.getUserData(CONSOLE_KEY); 178 | if (console == null) { 179 | continue; 180 | } 181 | 182 | if (!title.equals(console.title)) { 183 | continue; 184 | } 185 | 186 | if (console.isFinished()) { 187 | window.getContentManager().removeContent(each, false); 188 | } 189 | } 190 | } 191 | 192 | public void scrollToEnd() { 193 | (((ConsoleViewImpl) consoleView)).scrollToEnd(); 194 | } 195 | 196 | private class StartSbtAction extends DumbAwareAction { 197 | public StartSbtAction() { 198 | super("Start SBT", "Start SBT", IconLoader.getIcon("/toolwindows/toolWindowRun.png")); 199 | } 200 | 201 | @Override 202 | public void actionPerformed(AnActionEvent event) { 203 | runnerComponent.startIfNotStartedSafe(false); 204 | } 205 | 206 | @Override 207 | public void update(AnActionEvent event) { 208 | event.getPresentation().setEnabled(!runnerComponent.isSbtAlive()); 209 | } 210 | } 211 | 212 | private class KillSbtAction extends DumbAwareAction { 213 | public KillSbtAction() { 214 | super("Kill SBT", "Forcibly kill the SBT process", IconLoader.getIcon("/debugger/killProcess.png")); 215 | } 216 | 217 | @Override 218 | public void actionPerformed(AnActionEvent event) { 219 | runnerComponent.destroyProcess(); 220 | } 221 | 222 | @Override 223 | public void update(AnActionEvent event) { 224 | event.getPresentation().setEnabled(runnerComponent.isSbtAlive()); 225 | } 226 | } 227 | 228 | } 229 | -------------------------------------------------------------------------------- /sbt-plugin/src/main/java/net/orfjackal/sbt/plugin/SbtRunnerComponent.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010, Esko Luontola 2 | // This software is released under the Apache License 2.0. 3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | package net.orfjackal.sbt.plugin; 6 | 7 | import com.intellij.openapi.application.ApplicationManager; 8 | import com.intellij.openapi.application.ModalityState; 9 | import com.intellij.openapi.application.PathManager; 10 | import com.intellij.openapi.components.AbstractProjectComponent; 11 | import com.intellij.openapi.diagnostic.Logger; 12 | import com.intellij.openapi.fileEditor.FileDocumentManager; 13 | import com.intellij.openapi.progress.*; 14 | import com.intellij.openapi.project.DumbAware; 15 | import com.intellij.openapi.project.DumbAwareRunnable; 16 | import com.intellij.openapi.project.Project; 17 | import com.intellij.openapi.startup.StartupManager; 18 | import com.intellij.openapi.ui.MessageType; 19 | import com.intellij.openapi.util.io.FileUtil; 20 | import com.intellij.openapi.util.io.StreamUtil; 21 | import com.intellij.openapi.vfs.*; 22 | import com.intellij.openapi.wm.ToolWindow; 23 | import com.intellij.openapi.wm.ToolWindowAnchor; 24 | import com.intellij.openapi.wm.ToolWindowManager; 25 | import com.intellij.util.concurrency.SwingWorker; 26 | import net.orfjackal.sbt.plugin.settings.*; 27 | import net.orfjackal.sbt.runner.*; 28 | 29 | import javax.swing.*; 30 | import java.io.File; 31 | import java.io.IOException; 32 | import java.io.InputStream; 33 | import java.util.Scanner; 34 | 35 | public class SbtRunnerComponent extends AbstractProjectComponent implements DumbAware { 36 | 37 | private static final Logger logger = Logger.getInstance(SbtRunnerComponent.class.getName()); 38 | private static final boolean DEBUG = false; 39 | private static final String SBT_CONSOLE_TOOL_WINDOW_ID = "SBT Console"; 40 | 41 | private SbtRunner sbt; 42 | private SbtConsole console; 43 | private Project project; 44 | private final SbtProjectSettingsComponent projectSettings; 45 | private final SbtApplicationSettingsComponent applicationSettings; 46 | 47 | public static SbtRunnerComponent getInstance(Project project) { 48 | return project.getComponent(SbtRunnerComponent.class); 49 | } 50 | 51 | protected SbtRunnerComponent(Project project, 52 | SbtProjectSettingsComponent projectSettings, 53 | SbtApplicationSettingsComponent applicationSettings) { 54 | super(project); 55 | this.project = project; 56 | this.projectSettings = projectSettings; 57 | this.applicationSettings = applicationSettings; 58 | } 59 | 60 | public CompletionSignal executeInBackground(final String action) { 61 | final CompletionSignal signal = new CompletionSignal(); 62 | signal.begin(); 63 | 64 | queue(new Task.Backgroundable(myProject, MessageBundle.message("sbt.tasks.executing"), false) { 65 | public void run(ProgressIndicator indicator) { 66 | try { 67 | logger.debug("Begin executing: " + action); 68 | if (executeAndWait(action)) { 69 | signal.success(); 70 | logger.debug("Done executing: " + action); 71 | } else { 72 | logger.debug("Error executing: " + action); 73 | } 74 | } catch (IOException e) { 75 | logger.error("Failed to execute action \"" + action + "\". Maybe SBT failed to start?", e); 76 | } finally { 77 | signal.finished(); 78 | } 79 | } 80 | }); 81 | 82 | return signal; 83 | } 84 | 85 | @Override 86 | public void projectOpened() { 87 | final StartupManager manager = StartupManager.getInstance(myProject); 88 | manager.registerPostStartupActivity(new DumbAwareRunnable() { 89 | public void run() { 90 | console = createConsole(project); 91 | registerToolWindow(); 92 | } 93 | }); 94 | } 95 | 96 | @Override 97 | public void disposeComponent() { 98 | unregisterToolWindow(); 99 | destroyProcess(); 100 | } 101 | 102 | private SbtConsole createConsole(Project project) { 103 | return new SbtConsole(MessageBundle.message("sbt.tasks.action"), project, this); 104 | } 105 | 106 | private void registerToolWindow() { 107 | ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject); 108 | if (toolWindowManager != null) { 109 | ToolWindow toolWindow = 110 | toolWindowManager.registerToolWindow(SBT_CONSOLE_TOOL_WINDOW_ID, false, ToolWindowAnchor.BOTTOM, myProject, true); 111 | SbtRunnerComponent sbtRunnerComponent = SbtRunnerComponent.getInstance(myProject); 112 | sbtRunnerComponent.getConsole().ensureAttachedToToolWindow(toolWindow, false); 113 | } 114 | } 115 | 116 | private void unregisterToolWindow() { 117 | ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject); 118 | if (toolWindowManager != null && toolWindowManager.getToolWindow(SBT_CONSOLE_TOOL_WINDOW_ID) != null) { 119 | toolWindowManager.unregisterToolWindow(SBT_CONSOLE_TOOL_WINDOW_ID); 120 | } 121 | } 122 | 123 | private void queue(final Task.Backgroundable task) { 124 | if (ApplicationManager.getApplication().isDispatchThread()) { 125 | task.queue(); 126 | } else { 127 | ApplicationManager.getApplication().invokeAndWait(new Runnable() { 128 | public void run() { 129 | task.queue(); 130 | } 131 | }, ModalityState.NON_MODAL); 132 | } 133 | } 134 | 135 | /** 136 | * @param action the SBT action to run 137 | * @return false if an error was detected, true otherwise 138 | * @throws IOException 139 | */ 140 | public boolean executeAndWait(String action) throws IOException { 141 | saveAllDocuments(); 142 | if (!startIfNotStartedSafe(true)) { 143 | return false; 144 | } 145 | boolean success; 146 | try { 147 | success = sbt.execute(action); 148 | // TODO: update target folders (?) 149 | // org.jetbrains.idea.maven.project.MavenProjectsManager#updateProjectFolders 150 | // org.jetbrains.idea.maven.execution.MavenRunner#runBatch 151 | // org.jetbrains.idea.maven.execution.MavenRunner#updateTargetFolders 152 | } catch (IOException e) { 153 | destroyProcess(); 154 | throw e; 155 | } 156 | 157 | VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); 158 | return success; 159 | } 160 | 161 | private static void saveAllDocuments() { 162 | ApplicationManager.getApplication().invokeAndWait(new Runnable() { 163 | public void run() { 164 | FileDocumentManager.getInstance().saveAllDocuments(); 165 | } 166 | }, ModalityState.NON_MODAL); 167 | } 168 | 169 | public final SbtConsole getConsole() { 170 | return console; 171 | } 172 | 173 | public final String getFormattedCommand() { 174 | return sbt.getFormattedCommand(); 175 | } 176 | 177 | public final boolean startIfNotStartedSafe(boolean wait) { 178 | try { 179 | startIfNotStarted(wait); 180 | return true; 181 | } catch (Throwable e) { 182 | String toolWindowId = MessageBundle.message("sbt.console.id"); 183 | ToolWindowManager.getInstance(project).notifyByBalloon(toolWindowId, MessageType.ERROR, "Unable to start SBT. " + e.getMessage()); 184 | logger.info("Failed to start SBT", e); 185 | return false; 186 | } 187 | } 188 | 189 | private void startIfNotStarted(final boolean wait) throws IOException { 190 | if (!isSbtAlive()) { 191 | sbt = new SbtRunner(projectSettings.getJavaCommand(applicationSettings), projectDir(), launcherJar(), vmParameters()); 192 | printToMessageWindow(); 193 | if (DEBUG) { 194 | printToLogFile(); 195 | } 196 | sbt.start(wait, new Runnable() { 197 | public void run() { 198 | try { 199 | // See https://github.com/orfjackal/idea-sbt-plugin/issues/49 200 | sbt.execute("eval {System.setProperty(\"jline.terminal\" , \"none\"); \"\"}"); 201 | } catch (Exception e) { 202 | // ignore 203 | } 204 | } 205 | }); 206 | } 207 | } 208 | 209 | public final boolean isSbtAlive() { 210 | return sbt != null && sbt.isAlive(); 211 | } 212 | 213 | private File projectDir() { 214 | VirtualFile baseDir = myProject.getBaseDir(); 215 | assert baseDir != null; 216 | return new File(baseDir.getPath()); 217 | } 218 | 219 | private File launcherJar() { 220 | String pathname = projectSettings.effectiveSbtLauncherJarPath(applicationSettings); 221 | if (pathname != null && pathname.length() != 0) { 222 | return new File(pathname); 223 | } 224 | try { 225 | return unpackBundledLauncher(); 226 | } catch (Exception e) { 227 | // ignore 228 | } 229 | return new File("no-launcher.jar"); 230 | } 231 | 232 | private File unpackBundledLauncher() throws IOException { 233 | String launcherName = "sbt-launch.jar"; 234 | File launcherTemp = new File(new File(PathManager.getSystemPath(), "sbt"), launcherName); 235 | if (!launcherTemp.exists()) { 236 | InputStream resource = SbtRunnerComponent.class.getClassLoader().getResourceAsStream("sbt-launch.jar"); 237 | byte[] bytes = StreamUtil.loadFromStream(resource); 238 | FileUtil.writeToFile(launcherTemp, bytes); 239 | } 240 | return launcherTemp; 241 | } 242 | 243 | private String[] vmParameters() { 244 | String[] split = projectSettings.effectiveSbtLauncherVmParameters(applicationSettings).split("\\s"); 245 | if (split.length == 1 && split[0].trim().equals("")) return new String[0]; 246 | else return split; 247 | } 248 | 249 | private void printToMessageWindow() { 250 | // org.jetbrains.idea.maven.execution.MavenExecutor#myConsole 251 | SbtProcessHandler process = new SbtProcessHandler(this, sbt.subscribeToOutput()); 252 | console.attachToProcess(process, this); 253 | process.startNotify(); 254 | } 255 | 256 | private void printToLogFile() { 257 | final OutputReader output = sbt.subscribeToOutput(); 258 | Thread t = new Thread(new Runnable() { 259 | public void run() { 260 | Scanner scanner = new Scanner(output); 261 | while (scanner.hasNextLine()) { 262 | logger.info(scanner.nextLine()); 263 | } 264 | } 265 | }); 266 | t.setDaemon(true); 267 | t.start(); 268 | } 269 | 270 | public void destroyProcess() { 271 | if (sbt != null) { 272 | sbt.destroy(); 273 | sbt = null; 274 | } 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | --------------------------------------------------------------------------------