Permalink
Browse files

Improved multiple tab completions, other fixes

It's now possible to fully edit the current line, even when SBT outputs
multiple completions; autocomplete should be smoother in general.

Improved BufferedTransferThread usage and comments.

Moved the methods in the .sbtconsole package object to a
standard object (ThreadUtils) in the .shellconsole package.
  • Loading branch information...
1 parent 307a42d commit 12b78bfd54e5da2eeb3e6d47fc71f5b97ce079e9 @SandroGrzicic committed Aug 19, 2012
@@ -39,7 +39,7 @@ class SbtConsolePlugin extends AbstractUIPlugin with IStartup with HasLogger {
}
} else {
if (Preferences.sbtAutostart) {
- SWTUtils asyncExec {
+ SWTUtils.asyncExec {
for (window <- Workbench.getInstance.getWorkbenchWindows()) {
val action = new ShowSbtConsoleAction()
action.init(window)
@@ -13,6 +13,7 @@ import java.io.FilterInputStream
import java.io.Closeable
import org.eclipse.jdt.launching.JavaRuntime
import org.eclipse.jdt.core.IJavaProject
+import scala.tools.eclipse.shellconsole.ThreadUtils
object SbtRunner {
sealed trait SbtRunnerMessage
@@ -150,7 +151,7 @@ class SbtRunner extends Actor with HasLogger {
case Some(sbt) =>
stop()
// wait until it takes its time to exit cleanly
- sleepUntil(SBT_EXIT_CLEANUP_TIME) {
+ ThreadUtils.sleepWhile(SBT_EXIT_CLEANUP_TIME) {
sbtProcess.isDefined
}
if (sbtProcess.isDefined) {
@@ -1,46 +0,0 @@
-package scala.tools.eclipse
-
-import java.util.concurrent.Executors
-import java.util.concurrent.TimeUnit
-package object sbtconsole {
-
- object SWTUtils2 {
- import org.eclipse.swt.widgets.Display
-
- implicit def function2runnable0(f: => Unit) = new Runnable {
- def run() { f }
- }
- implicit def function2runnable1(f: () => Unit) = new Runnable {
- def run() { f }
- }
-
- /** Run `f` on the UI thread after `after` milliseconds. */
- def asyncExec(after: Long)(f: => Unit) {
- Executors.newSingleThreadScheduledExecutor().schedule({
- Display.getDefault.asyncExec(f)
- }, after, TimeUnit.MILLISECONDS)
- }
- }
-
- /**
- * Sleeps the current thread until the condition becomes true,
- * or the `timeout` (in ms) elapses.
- * `sleepSegments` controls how often will the condition be checked
- * (more segments = more checks).
- */
- def sleepUntil(timeout: Long, sleepSegments: Int = 20)(condition: => Boolean) {
- if (timeout < 0) {
- throw new IllegalArgumentException("Timeout must be nonnegative!")
- } else if (sleepSegments <= 0) {
- throw new IllegalArgumentException("SleepSegments must be positive!")
- }
- var steps = 0
- val sleepTime = timeout / sleepSegments
- val endTime = System.currentTimeMillis + timeout
- while (condition && steps < sleepSegments && System.currentTimeMillis < endTime) {
- Thread.sleep(sleepTime)
- steps += 1
- }
- }
-
-}
@@ -15,11 +15,17 @@ import java.io.IOException
* to `Output`.
*
* The currently buffered output can be copied to the output stream by using
- * `copyToOutput`; the copied length can be limited using `contentBufferLimit`.
+ * `copyToOutput`; the copied length can be limited using `contentBufferLimit`.
*
+ * The transfer terminates when the input stream has no more input, or the
+ * output stream is not accepting any more input (throws an IOException).
*
*/
-class BufferedTransferThread(in: InputStream, out: IOConsoleOutputStream, charset: String = "UTF-8") extends Thread {
+class BufferedTransferThread(
+ in: InputStream,
+ out: IOConsoleOutputStream,
+ charset: String = "UTF-8"
+) extends Thread {
import scala.sys.process.BasicIO
import BufferedTransferThread._
@@ -33,12 +39,17 @@ class BufferedTransferThread(in: InputStream, out: IOConsoleOutputStream, charse
@volatile var writeTarget: Target = Output
/**
- * Copy the currently buffered input characters to the output stream,
- * optionally up to `contentBufferLimit`.
+ * Copies the currently buffered input characters to the output stream,
+ * optionally up to `contentBufferLimit`.
+ * Clears the buffer when it's done unless otherwise specified.
*/
- def copyToOutput(contentBufferLimit: Int = contentBuffer.length) {
+ def copyToOutput(contentBufferLimit: Int = contentBuffer.length, clearBuffer: Boolean = true) {
out.write(contentBuffer.toString.substring(0, contentBufferLimit))
try { out.flush() } catch { case _: IOException => }
+
+ if (clearBuffer) {
+ contentBuffer.setLength(0)
+ }
}
override def run() {
@@ -49,10 +60,15 @@ class BufferedTransferThread(in: InputStream, out: IOConsoleOutputStream, charse
byteCount = in.read(streamBuffer)
if (byteCount > 0) {
if (writeTarget == Output) {
- out.write(streamBuffer, 0, byteCount)
- // flush() will throw an exception once the process has terminated
- val available = try { out.flush(); true } catch { case _: IOException => false }
- if (available) loop()
+ // an exception will be thrown once the process has terminated
+ val outputStreamOpen = try {
+ out.write(streamBuffer, 0, byteCount)
+ out.flush()
+ true
+ } catch {
+ case _: IOException => false
+ }
+ if (outputStreamOpen) loop()
} else {
contentBuffer.append(new String(streamBuffer, 0, byteCount, charset))
loop()
@@ -7,7 +7,6 @@ import org.eclipse.ui.console.IOConsole
import scala.tools.eclipse.logging.HasLogger
import scala.tools.eclipse.util.SWTUtils
import org.eclipse.swt.widgets.Display
-import scala.tools.eclipse.sbtconsole.SWTUtils2
import java.io.PipedInputStream
import java.io.PipedOutputStream
import java.io.OutputStreamWriter
@@ -12,7 +12,6 @@ import org.eclipse.jface.text.BadLocationException
import org.eclipse.jface.text.TextEvent
import org.eclipse.jface.text.ITextListener
import org.eclipse.jface.text.IRegion
-import scala.tools.eclipse.sbtconsole.SWTUtils2
import org.eclipse.swt.SWTException
import scala.sys.process.BasicIO
@@ -27,13 +26,15 @@ class ShellConsoleKeyListener(console: ShellConsole, page: ShellConsolePage)
with KeyListener
with HasLogger {
+ /** The symbol which represents a new line in the process output. */
+ var newlineSymbol = '\r'
+
/** Holds user input (history). */
var history = IndexedSeq[String]()
/** Current line (position) in history. */
var historyLine = 0
private val currentLine = StringBuilder.newBuilder
- private var currentLineProcess: Option[String] = None
private def getCurrentLineInfo: IRegion =
document.getLineInformation(document.getNumberOfLines - 1)
@@ -43,9 +44,6 @@ class ShellConsoleKeyListener(console: ShellConsole, page: ShellConsolePage)
private lazy val document = page.getViewer.getDocument
private lazy val textWidget = page.getViewer.getTextWidget
- /** Writes to the process. */
- private val processWriter = console.processWriter
-
/** Move the caret to the end of the console. */
def moveCaretToEnd() {
if (textWidget != null && !textWidget.isDisposed()) {
@@ -55,7 +53,7 @@ class ShellConsoleKeyListener(console: ShellConsole, page: ShellConsolePage)
/** Move the caret to the end of the console after `after` milliseconds. */
def moveCaretToEndAsync(after: Long = 200) {
- SWTUtils2.asyncExec(after) {
+ ThreadUtils.asyncExec(after) {
if (page != null) {
moveCaretToEnd()
}
@@ -110,8 +108,8 @@ class ShellConsoleKeyListener(console: ShellConsole, page: ShellConsolePage)
val line = currentLine.mkString
- processWriter.write(line + "\n")
- processWriter.flush()
+ console.processWriter.write(line + "\n")
+ console.processWriter.flush()
replaceCurrentLineWith("")
@@ -129,43 +127,63 @@ class ShellConsoleKeyListener(console: ShellConsole, page: ShellConsolePage)
// start buffering process output
transferThread.writeTarget = BufferedTransferThread.Buffer
- processWriter.write(line + "\t")
- processWriter.flush()
+ // send the current line to the process for autocompletion
+ console.processWriter.write(line + "\t")
+ console.processWriter.flush()
- // wait for the process to print out the completion (TODO: optimize somehow?)
+ // wait for the process to print out the completion
// because it's not possible to find out the end of the completion
Thread.sleep(100)
-
- val contentBuffer = transferThread.contentBuffer.toString()
- if (contentBuffer.count('\r' ==) == 1) {
+ // make sure we actually get some output from the process
+ ThreadUtils.sleepWhile(timeout = 1000, sleepSegments = 10) {
+ transferThread.contentBuffer.length == 0
+ }
+
+ val contentBuffer = transferThread.contentBuffer.toString
+
+ if (contentBuffer.count(newlineSymbol ==) == 1) {
// single completion - complete current line
- val completion = contentBuffer.dropWhile('\r' !=).drop(3)
+ val completion = contentBuffer.dropWhile(newlineSymbol !=).drop(3)
// erase completion from process shell
val backspaces = Array.fill(completion.length)('\b')
- processWriter.write(backspaces)
- processWriter.flush()
-
-// eclipseLog.info("Completion: " + completion + " - currentLine: " + currentLine.toString)
+ console.processWriter.write(backspaces)
+ console.processWriter.flush()
replaceCurrentLineWith(completion)
-
+
+ // clear content buffer and resume outputting process output to console
+ transferThread.contentBuffer.setLength(0)
+ transferThread.writeTarget = BufferedTransferThread.Output
+
+ moveCaretToEnd()
+
} else {
// multiple completions - transfer them to the console
- // TODO: remove last line from output using transferThread.contentBufferLimit
+ // erase completion from process shell
+ val backspaces = Array.fill(line.length)('\b')
+ console.processWriter.write(backspaces)
+ console.processWriter.flush()
replaceCurrentLineWith("")
- transferThread.copyToOutput()
+
+ // remove last line from output by using the copyToOutput limit
+ val contentBufferCopyLimit = contentBuffer.lastIndexOf(newlineSymbol) + 4
+ // copy the current content buffer to the console and clear it
+ transferThread.copyToOutput(contentBufferCopyLimit, clearBuffer = true)
+ // resume outputting process output to console
+ transferThread.writeTarget = BufferedTransferThread.Output
+
+ // wait until the console has been properly filled with the output
+ ThreadUtils.asyncExec(200) {
+ // set the current line to be the previously typed line
+ replaceCurrentLineWith(line)
+ moveCaretToEnd()
+ }
}
-
- // clear content buffer and resume outputting process output to console
- transferThread.contentBuffer.setLength(0)
- transferThread.writeTarget = BufferedTransferThread.Output
-
} catch {
case e =>
- eclipseLog.warn("Exception while attempting autocomplete.", e)
+ logger.warn("Exception while attempting autocomplete of " + line + ": e.getMessage", e)
}
- moveCaretToEnd()
}
def addLineToHistory(currentLine: String) {
@@ -198,7 +216,7 @@ class ShellConsoleKeyListener(console: ShellConsole, page: ShellConsolePage)
document.replace(lineInfo.getOffset + 2, lineInfo.getLength - 2, contents)
} catch {
case e =>
- eclipseLog.info("Exception while replacing current line with " + contents + " - " + e.getMessage, e)
+ logger.warn("Exception while replacing current line with " + contents + " - " + e.getMessage, e)
}
currentLine.replace(0, currentLine.length, contents)
moveCaretToEnd()
@@ -0,0 +1,48 @@
+package scala.tools.eclipse.shellconsole
+
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+import org.eclipse.swt.widgets.Display
+
+/** Useful utility methods. */
+object ThreadUtils {
+ import org.eclipse.swt.widgets.Display
+
+ implicit def function2runnable0(f: => Unit) = new Runnable {
+ def run() { f }
+ }
+ implicit def function2runnable1(f: () => Unit) = new Runnable {
+ def run() { f }
+ }
+
+ /** Executor used with asyncExec. */
+ lazy val executor = Executors.newSingleThreadScheduledExecutor()
+
+ /** Run `f` on the UI thread after `after` milliseconds. */
+ def asyncExec(after: Long)(f: => Unit) {
+ executor.schedule({
+ Display.getDefault.asyncExec(f)
+ }, after, TimeUnit.MILLISECONDS)
+ }
+
+ /**
+ * Sleeps the current thread while the condition is true
+ * or the `timeout` (in ms) elapses.
+ * `sleepSegments` controls how often will the condition be checked
+ * (more segments = more checks).
+ */
+ def sleepWhile(timeout: Long, sleepSegments: Int = 20)(condition: => Boolean) {
+ if (timeout < 0) {
+ throw new IllegalArgumentException("Timeout must be nonnegative!")
+ } else if (sleepSegments <= 0) {
+ throw new IllegalArgumentException("SleepSegments must be positive!")
+ }
+ var steps = 0
+ val sleepTime = timeout / sleepSegments
+ val endTime = System.currentTimeMillis + timeout
+ while (condition && steps < sleepSegments && System.currentTimeMillis < endTime) {
+ Thread.sleep(sleepTime)
+ steps += 1
+ }
+ }
+}

0 comments on commit 12b78bf

Please sign in to comment.