/**
* Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package org.python.pydev.shared_interactive_console.console.ui.internal;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.ui.console.IConsoleLineTracker;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.python.pydev.shared_core.callbacks.ICallback;
import org.python.pydev.shared_core.log.Log;
import org.python.pydev.shared_core.string.FastStringBuffer;
import org.python.pydev.shared_core.string.StringUtils;
import org.python.pydev.shared_core.string.TextSelectionUtils;
import org.python.pydev.shared_core.structure.Tuple;
import org.python.pydev.shared_core.utils.DocCmd;
import org.python.pydev.shared_interactive_console.console.InterpreterResponse;
import org.python.pydev.shared_interactive_console.console.ScriptConsoleHistory;
import org.python.pydev.shared_interactive_console.console.ScriptConsolePrompt;
import org.python.pydev.shared_interactive_console.console.ui.IConsoleStyleProvider;
import org.python.pydev.shared_interactive_console.console.ui.IScriptConsoleSession;
import org.python.pydev.shared_interactive_console.console.ui.ScriptConsolePartitioner;
import org.python.pydev.shared_interactive_console.console.ui.ScriptStyleRange;
import org.python.pydev.shared_ui.utils.RunInUiThread;
/**
* This class will listen to the document and will:
*
* - pass the commands to the handler
* - add the results from the handler
* - show the prompt
* - set the color of the console regions
*/
public class ScriptConsoleDocumentListener implements IDocumentListener {
private ICommandHandler handler;
private ScriptConsolePrompt prompt;
private ScriptConsoleHistory history;
private int readOnlyColumnsInCurrentBeforePrompt;
private int historyFullLine;
/**
* Document to which this listener is attached.
*/
private IDocument doc;
private int disconnectionLevel = 0;
/**
* The time for the last change in the document that was listened in this console.
*/
private long lastChangeMillis;
/**
* The commands that should be initially set in the console
*/
private String initialCommands;
private volatile boolean promptReady;
/**
* @return the last time the document that this console was listening to was changed.
*/
public long getLastChangeMillis() {
return lastChangeMillis;
}
/**
* Viewer for the document contained in this listener.
*/
private IScriptConsoleViewer2ForDocumentListener viewer;
/**
* Additional viewers for the same document.
*/
private List<WeakReference<IScriptConsoleViewer2ForDocumentListener>> otherViewers = new ArrayList<WeakReference<IScriptConsoleViewer2ForDocumentListener>>();
/**
* Strategy used for indenting / tabs
*/
private IHandleScriptAutoEditStrategy strategy;
/**
* Console line trackers (for hyperlinking)
*/
private List<IConsoleLineTracker> consoleLineTrackers;
public IHandleScriptAutoEditStrategy getIndentStrategy() {
return strategy;
}
/**
* Stops listening changes in one document and starts listening another one.
*
* @param oldDoc may be null (if not null, this class will stop listening changes in it).
* @param newDoc the document that should be listened from now on.
*/
protected synchronized void reconnect(IDocument oldDoc, IDocument newDoc) {
Assert.isTrue(disconnectionLevel == 0);
if (oldDoc != null) {
oldDoc.removeDocumentListener(this);
}
newDoc.addDocumentListener(this);
this.doc = newDoc;
}
/**
* Stop listening to changes (so that we're able to change the document in this class without having
* any loops back into the function that will change it)
*/
protected synchronized void startDisconnected() {
if (disconnectionLevel == 0) {
doc.removeDocumentListener(this);
}
disconnectionLevel += 1;
}
/**
* Start listening to changes again.
*/
protected synchronized void stopDisconnected() {
disconnectionLevel -= 1;
if (disconnectionLevel == 0) {
doc.addDocumentListener(this);
}
}
/**
* Clear the document and show the initial prompt.
* @param addInitialCommands indicates if the initial commands should be appended to the document.
*/
public void clear(boolean addInitialCommands) {
startDisconnected();
try {
doc.set(""); //$NON-NLS-1$
appendInvitation(true);
} finally {
stopDisconnected();
}
if (addInitialCommands) {
try {
doc.replace(doc.getLength(), 0, this.initialCommands + "\n");
} catch (BadLocationException e) {
Log.log(e);
}
}
}
/**
* Adds some other viewer for the same document.
*
* @param scriptConsoleViewer this is the viewer that should be added as a second viewer for the same
* document.
*/
public void addViewer(IScriptConsoleViewer2ForDocumentListener scriptConsoleViewer) {
this.otherViewers.add(new WeakReference<IScriptConsoleViewer2ForDocumentListener>(scriptConsoleViewer));
}
/**
* Constructor
*
* @param viewer this is the viewer to which this listener is attached. It's the main viewer. Other viewers
* may be added later through addViewer() for sharing the same listener and being properly updated.
*
* @param handler this is the object that'll handle the commands
* @param prompt shows the prompt to the user
* @param history keeps track of the commands added by the user.
* @param initialCommands the commands that should be initially added
*/
public ScriptConsoleDocumentListener(IScriptConsoleViewer2ForDocumentListener viewer, ICommandHandler handler,
ScriptConsolePrompt prompt, ScriptConsoleHistory history, List<IConsoleLineTracker> consoleLineTrackers,
String initialCommands, IHandleScriptAutoEditStrategy strategy) {
this.lastChangeMillis = System.currentTimeMillis();
this.strategy = strategy;
this.prompt = prompt;
this.handler = handler;
this.history = history;
this.viewer = viewer;
this.readOnlyColumnsInCurrentBeforePrompt = 0;
this.historyFullLine = 0;
this.doc = null;
this.consoleLineTrackers = consoleLineTrackers;
this.initialCommands = initialCommands;
final ICallback<Object, Tuple<String, String>> onContentsReceived = new ICallback<Object, Tuple<String, String>>() {
@Override
public Object call(final Tuple<String, String> result) {
if (result.o1.length() > 0 || result.o2.length() > 0) {
Runnable runnable = new Runnable() {
@Override
public void run() {
startDisconnected();
PromptContext pc;
try {
pc = removeUserInput();
IScriptConsoleSession consoleSession = ScriptConsoleDocumentListener.this.viewer
.getConsoleSession();
if (result.o1.length() > 0) {
if (consoleSession != null) {
consoleSession.onStdoutContentsReceived(result.o1);
}
addToConsoleView(result.o1, true, true);
}
if (result.o2.length() > 0) {
if (consoleSession != null) {
consoleSession.onStderrContentsReceived(result.o2);
}
addToConsoleView(result.o2, false, true);
}
if (pc.removedPrompt) {
appendInvitation(false);
}
} finally {
stopDisconnected();
}
if (pc.removedPrompt) {
appendText(pc.userInput);
ScriptConsoleDocumentListener.this.viewer.setCaretOffset(doc.getLength()
- pc.cursorOffset, false);
}
}
};
RunInUiThread.async(runnable);
}
return null;
}
};
handler.setOnContentsReceivedCallback(onContentsReceived);
}
private class PromptContext {
public boolean removedPrompt;
// offset from the end of the document.
public int cursorOffset;
public String userInput;
public PromptContext(boolean removedPrompt, int cursorOffset, String userInput) {
this.removedPrompt = removedPrompt;
this.cursorOffset = cursorOffset;
this.userInput = userInput;
}
}
protected PromptContext removeUserInput() {
if (!promptReady) {
return new PromptContext(false, -1, "");
}
PromptContext pc = new PromptContext(true, -1, "");
try {
int lastLine = doc.getNumberOfLines() - 1;
int lastLineLength = doc.getLineLength(lastLine);
int end = doc.getLength();
int start = end - lastLineLength;
// There may be read-only content before the current input. so last line
// may look like:
// Out[10]: >>> some_user_command
// The content before the prompt should be treated as read-only.
int promptOffset = doc.get(start, lastLineLength).indexOf(prompt.toString());
start += promptOffset;
lastLineLength -= promptOffset;
pc.userInput = doc.get(start, lastLineLength);
pc.cursorOffset = end - viewer.getCaretOffset();
doc.replace(start, lastLineLength, "");
pc.userInput = pc.userInput.replace(prompt.toString(), "");
} catch (BadLocationException e) {
e.printStackTrace();
}
return pc;
}
/**
* Set the document that this class should listen.
*
* @param doc the document that should be used in the console.
*/
public void setDocument(IDocument doc) {
reconnect(this.doc, doc);
}
/**
* Ignore
*/
@Override
public void documentAboutToBeChanged(DocumentEvent event) {
}
/**
* Process the result that came from pushing some text to the interpreter.
*
* @param result the response from the interpreter after sending some command for it to process.
*/
protected void processResult(final InterpreterResponse result) {
if (result != null) {
history.commit();
try {
readOnlyColumnsInCurrentBeforePrompt = getLastLineLength();
} catch (BadLocationException e) {
Log.log(e);
}
if (!result.more) {
historyFullLine = history.getAsList().size();
}
}
appendInvitation(false);
}
/**
* Adds some text that came as an output to stdout or stderr to the console.
*
* @param out the text that should be added
* @param stdout true if it came from stdout and also if it came from stderr
*/
private void addToConsoleView(String out, boolean stdout, boolean textAddedIsReadOnly) {
if (out.length() == 0) {
return; //nothing to add!
}
int start = doc.getLength();
IConsoleStyleProvider styleProvider = viewer.getStyleProvider();
Tuple<List<ScriptStyleRange>, String> style = null;
if (styleProvider != null) {
if (stdout) {
style = styleProvider.createInterpreterOutputStyle(out, start);
} else { //stderr
style = styleProvider.createInterpreterErrorStyle(out, start);
}
if (style != null) {
for (ScriptStyleRange s : style.o1) {
addToPartitioner(s);
}
}
}
if (style != null) {
appendText(style.o2);
if (textAddedIsReadOnly) {
try {
// The text we just appended can't be changed!
int lastLine = doc.getNumberOfLines() - 1;
int len = doc.getLineLength(lastLine);
this.readOnlyColumnsInCurrentBeforePrompt = len;
} catch (BadLocationException e) {
Log.log(e);
}
}
}
TextSelectionUtils ps = new TextSelectionUtils(doc, start);
int cursorLine = ps.getCursorLine();
int numberOfLines = doc.getNumberOfLines();
//right after appending the text, let's notify line trackers
for (int i = cursorLine; i < numberOfLines; i++) {
try {
int offset = ps.getLineOffset(i);
int endOffset = ps.getEndLineOffset(i);
Region region = new Region(offset, endOffset - offset);
for (IConsoleLineTracker lineTracker : this.consoleLineTrackers) {
lineTracker.lineAppended(region);
}
} catch (Exception e) {
Log.log(e);
}
}
revealEndOfDocument();
}
/**
* Adds a given style range to the partitioner.
*
* Note that the style must be added before the actual text is added! (because as
* soon as it's added, the style is asked for).
*
* @param style the style to be added.
*/
private void addToPartitioner(ScriptStyleRange style) {
IDocumentPartitioner partitioner = this.doc.getDocumentPartitioner();
if (partitioner instanceof ScriptConsolePartitioner) {
ScriptConsolePartitioner scriptConsolePartitioner = (ScriptConsolePartitioner) partitioner;
scriptConsolePartitioner.addRange(style);
}
}
/**
* Should be called right after adding some text to the console (it'll actually go on,
* remove the text just added and add it line-by-line in the document so that it can be
* correctly treated in the console).
*
* @param offset the offset where the addition took place
* @param text the text that should be adedd
*/
protected void proccessAddition(int offset, String text) {
//we have to do some gymnastics here to add line-by-line the contents that the user entered.
//(mostly because it may have been a copy/paste with multi-lines)
String indentString = "";
boolean addedNewLine = false;
boolean addedParen = false;
boolean addedCloseParen = false;
int addedLen = text.length();
if (addedLen == 1) {
if (text.equals("\r") || text.equals("\n")) {
addedNewLine = true;
} else if (text.equals("(")) {
addedParen = true;
} else if (text.equals(")")) {
addedCloseParen = true;
}
} else if (addedLen == 2) {
if (text.equals("\r\n")) {
addedNewLine = true;
}
}
String delim = getDelimeter();
int newDeltaCaretPosition = doc.getLength() - (offset + text.length());
//1st, remove the text the user just entered (and enter it line-by-line later)
try {
// Remove the just entered text
doc.replace(offset, text.length(), ""); //$NON-NLS-1$
// Is the current offset in the command line
// NB we do this after the above as the pasted text may have new lines in it
boolean offset_in_command_line = offset >= getCommandLineOffset();
// If the offset isn't in the command line, then just append to the existing
// command line text
if (!offset_in_command_line) {
offset = newDeltaCaretPosition = getCommandLineOffset();
// Remove any existing command line text and prepend it to the text
// we're inserting
text = doc.get(getCommandLineOffset(), getCommandLineLength()) + text;
doc.replace(getCommandLineOffset(), getCommandLineLength(), "");
} else {
// paste is within the command line
text = text + doc.get(offset, doc.getLength() - offset);
doc.replace(offset, doc.getLength() - offset, "");
}
} catch (BadLocationException e) {
text = "";
Log.log(e);
}
text = StringUtils.replaceNewLines(text, delim);
//now, add it line-by-line (it won't even get into the loop if there's no
//new line in the text added).
int start = 0;
int index = -1;
List<String> commands = new ArrayList<String>();
while ((index = text.indexOf(delim, start)) != -1) {
String cmd = text.substring(start, index);
cmd = convertTabs(cmd);
commands.add(cmd);
start = index + delim.length();
}
final String[] finalIndentString = new String[] { indentString };
if (commands.size() > 0) {
//Note that we'll disconnect from the document here and reconnect when the last line is executed.
startDisconnected();
String cmd = commands.get(0);
execCommand(addedNewLine, delim, finalIndentString, cmd, commands, 0, text, addedParen, start,
addedCloseParen, newDeltaCaretPosition);
} else {
onAfterAllLinesHandled(text, addedParen, start, offset, addedCloseParen, finalIndentString[0],
newDeltaCaretPosition);
}
}
/**
* Here is where we run things not using the UI thread. It's a recursive function. In summary, it'll
* run each line in the commands received in a new thread, and as each finishes, it calls itself again
* for the next command. The last command will reconnect to the document.
*
* Exceptions had to be locally handled, because they're not well tolerated under this scenario
* (if on of the callbacks fail, the others won't be executed and we'd get into a situation
* where the shell becomes unusable).
*/
private void execCommand(final boolean addedNewLine, final String delim, final String[] finalIndentString,
final String cmd, final List<String> commands, final int currentCommand, final String text,
final boolean addedParen, final int start, final boolean addedCloseParen, final int newDeltaCaretPosition) {
applyStyleToUserAddedText(cmd, doc.getLength());
//the cmd could be something as '\n'
appendText(cmd);
//and the command line the actual contents to be executed at this time
final String commandLine = getCommandLine();
if (handler.isOnStateWhereCommandHandlingShouldStop(commandLine)) {
return;
}
history.update(commandLine);
// handle the command line:
// When the user presses a return and goes to a new line, the contents of the current line are sent to
// the interpreter (and its results properly handled).
appendText(getDelimeter());
final boolean finalAddedNewLine = addedNewLine;
final String finalDelim = delim;
final ICallback<Object, InterpreterResponse> onResponseReceived = new ICallback<Object, InterpreterResponse>() {
@Override
public Object call(final InterpreterResponse arg) {
//When we receive the response, we must handle it in the UI thread.
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
processResult(arg);
if (finalAddedNewLine) {
List<String> historyList = history.getAsList();
IDocument historyDoc = new Document(StringUtils.join("\n",
historyList.subList(historyFullLine, historyList.size())) + "\n");
int currHistoryLen = historyDoc.getLength();
if (currHistoryLen > 0) {
DocCmd docCmd = new DocCmd(currHistoryLen - 1, 0, finalDelim);
strategy.customizeNewLine(historyDoc, docCmd);
finalIndentString[0] = docCmd.text.replaceAll("\\r\\n|\\n|\\r", ""); //remove any new line added!
if (currHistoryLen != historyDoc.getLength()) {
Log.log("Error: the document passed to the customizeNewLine should not be changed!");
}
}
}
} catch (Throwable e) {
//Yeap, it can never fail!
Log.log(e);
}
if (currentCommand + 1 < commands.size()) {
execCommand(finalAddedNewLine, finalDelim, finalIndentString,
commands.get(currentCommand + 1), commands, currentCommand + 1, text, addedParen,
start, addedCloseParen, newDeltaCaretPosition);
} else {
//last one
try {
onAfterAllLinesHandled(text, addedParen, start, readOnlyColumnsInCurrentBeforePrompt,
addedCloseParen,
finalIndentString[0], newDeltaCaretPosition);
} finally {
//We must disconnect
stopDisconnected(); //reconnect with the document
}
}
}
};
RunInUiThread.async(runnable);
return null;
}
};
handler.beforeHandleCommand(commandLine, onResponseReceived);
//Handle the command in a thread that doesn't block the U/I.
Job j = new Job("PyDev Console Hander") {
@Override
protected IStatus run(IProgressMonitor monitor) {
promptReady = false;
handler.handleCommand(commandLine, onResponseReceived);
return Status.OK_STATUS;
};
};
j.setSystem(true);
j.schedule();
}
private static class TabCompletionSingletonRule implements ISchedulingRule {
@Override
public boolean contains(ISchedulingRule rule) {
return rule == this;
}
@Override
public boolean isConflicting(ISchedulingRule rule) {
return rule instanceof TabCompletionSingletonRule;
}
}
/**
* Attempts to query the console backend (ipython) for completions
* and update the console's cursor as appropriate.
*/
public void handleConsoleTabCompletions() {
final String commandLine = getCommandLine();
final int commandLineOffset = viewer.getCommandLineOffset();
final int caretOffset = viewer.getCaretOffset();
// Don't block the UI when talking to the console
Job j = new Job("Async Fetch completions") {
@Override
protected IStatus run(IProgressMonitor monitor) {
ICompletionProposal[] completions = handler
.getTabCompletions(commandLine, caretOffset - commandLineOffset);
if (completions.length == 0) {
return Status.OK_STATUS;
}
// Evaluate all the completions
final List<String> compList = new ArrayList<String>();
//%cd is a special case already handled when converting it in
//org.python.pydev.debug.newconsole.PydevConsoleCommunication.convertToICompletions(String, String, int, Object, List<ICompletionProposal>, boolean)
//So, don't consider it 'magic' in this case.
boolean magicCommand = commandLine.startsWith("%") && !commandLine.startsWith("%cd ");
for (ICompletionProposal completion : completions) {
boolean magicCompletion = completion.getDisplayString().startsWith("%");
Document doc = new Document(commandLine.substring((magicCommand && magicCompletion) ? 1 : 0));
completion.apply(doc);
String out = doc.get().substring((magicCommand && !magicCompletion) ? 1 : 0);
if (out.startsWith("_", out.lastIndexOf('.') + 1)
&& !commandLine.startsWith("_", commandLine.lastIndexOf('.') + 1)) {
continue;
}
if (out.indexOf('(', commandLine.length()) != -1) {
out = out.substring(0, out.indexOf('(', commandLine.length()));
}
compList.add(out);
}
// Discover the longest possible completion so we can zip up to it
String longestCommonPrefix = null;
for (String completion : compList) {
if (!completion.startsWith(commandLine)) {
continue;
}
// Calculate the longest common prefix so we can auto-complete at least up to there.
if (longestCommonPrefix == null) {
longestCommonPrefix = completion;
} else {
for (int i = 0; i < longestCommonPrefix.length() && i < completion.length(); i++) {
if (longestCommonPrefix.charAt(i) != completion.charAt(i)) {
longestCommonPrefix = longestCommonPrefix.substring(0, i);
break;
}
}
// Handle mismatched lengths: dir and dirs
if (longestCommonPrefix.length() > completion.length()) {
longestCommonPrefix = completion;
}
}
}
if (longestCommonPrefix == null) {
longestCommonPrefix = commandLine;
}
// Calculate the maximum length of the completions for string formatting
int length = 0;
for (String completion : compList) {
length = Math.max(length, completion.length());
}
final String fLongestCommonPrefix = longestCommonPrefix;
final int maxLength = length;
Runnable r = new Runnable() {
@Override
public void run() {
// Get the viewer width + format the auto-completion output appropriately
int consoleWidth = viewer.getConsoleWidthInCharacters();
int formatLength = maxLength + 4;
int completionsPerLine = consoleWidth / formatLength;
if (completionsPerLine <= 0) {
completionsPerLine = 1;
}
String formatString = "%-" + formatLength + "s";
StringBuilder sb = new StringBuilder("\n");
int i = 0;
for (String completion : compList) {
sb.append(String.format(formatString, completion));
if (++i % completionsPerLine == 0) {
sb.append("\n");
}
}
sb.append("\n");
String currentCommand = getCommandLine();
try {
// disconnect the console so we can write content into it
startDisconnected();
// Add our completions to the console
addToConsoleView(sb.toString(), true, true);
// Re-add >>>
appendInvitation(false);
} finally {
stopDisconnected();
}
// Auto-complete the command up to the longest common prefix (if it hasn't changed since we were last here)
if (!currentCommand.equals(commandLine) || fLongestCommonPrefix.isEmpty()) {
addToConsoleView(currentCommand, true, false);
} else {
addToConsoleView(fLongestCommonPrefix, true, false);
}
}
};
RunInUiThread.async(r);
return Status.OK_STATUS;
}
};
j.setPriority(Job.INTERACTIVE);
j.setRule(new TabCompletionSingletonRule());
j.setSystem(true);
j.schedule();
}
/**
* This method should be called after all the lines received were processed.
*/
private void onAfterAllLinesHandled(final String finalText, final boolean finalAddedParen, final int finalStart,
final int finalOffset, final boolean finalAddedCloseParen, final String finalIndentString,
final int finalNewDeltaCaretPosition) {
boolean shiftsCaret = true;
String newText = finalText.substring(finalStart, finalText.length());
if (finalAddedParen) {
String cmdLine = getCommandLine();
Document parenDoc = new Document(cmdLine + newText);
int currentOffset = cmdLine.length() + 1;
DocCmd docCmd = new DocCmd(currentOffset, 0, "(");
docCmd.shiftsCaret = true;
try {
strategy.customizeParenthesis(parenDoc, docCmd);
} catch (BadLocationException e) {
Log.log(e);
}
newText = docCmd.text + newText.substring(1);
if (!docCmd.shiftsCaret) {
shiftsCaret = false;
setCaretOffset(finalOffset + (docCmd.caretOffset - currentOffset));
}
} else if (finalAddedCloseParen) {
String cmdLine = getCommandLine();
String existingDoc = cmdLine + finalText.substring(1);
int cmdLineOffset = cmdLine.length();
if (existingDoc.length() > cmdLineOffset) {
Document parenDoc = new Document(existingDoc);
DocCmd docCmd = new DocCmd(cmdLineOffset, 0, ")");
docCmd.shiftsCaret = true;
boolean canSkipOpenParenthesis;
try {
canSkipOpenParenthesis = strategy.canSkipCloseParenthesis(parenDoc, docCmd);
} catch (BadLocationException e) {
canSkipOpenParenthesis = false;
Log.log(e);
}
if (canSkipOpenParenthesis) {
shiftsCaret = false;
setCaretOffset(finalOffset + 1);
newText = newText.substring(1);
}
}
}
//and now add the last line (without actually handling it).
String cmd = finalIndentString + newText;
cmd = convertTabs(cmd);
applyStyleToUserAddedText(cmd, doc.getLength());
appendText(cmd);
if (shiftsCaret) {
setCaretOffset(doc.getLength() - finalNewDeltaCaretPosition);
}
history.update(getCommandLine());
}
private String convertTabs(String cmd) {
return strategy.convertTabs(cmd);
}
/**
* Applies the style in the text for the contents that've been just added.
*
* @param cmd
* @param offset2
*/
private void applyStyleToUserAddedText(String cmd, int offset2) {
IConsoleStyleProvider styleProvider = viewer.getStyleProvider();
if (styleProvider != null) {
ScriptStyleRange style = styleProvider.createUserInputStyle(cmd, offset2);
if (style != null) {
addToPartitioner(style);
}
}
}
/**
* Whenever the document changes, we stop listening to change the document from
* within this listener (passing commands to the handler if needed, getting results, etc).
*/
@Override
public void documentChanged(DocumentEvent event) {
lastChangeMillis = System.currentTimeMillis();
startDisconnected();
try {
int eventOffset = event.getOffset();
String eventText = event.getText();
proccessAddition(eventOffset, eventText);
} finally {
stopDisconnected();
}
}
/**
* Appends some text at the end of the document.
*
* @param text the text to be added.
*/
protected void appendText(String text) {
int initialOffset = doc.getLength();
try {
doc.replace(initialOffset, 0, text);
} catch (BadLocationException e) {
Log.log(e);
}
}
/**
* Shows the prompt for the user (e.g.: >>>)
*/
protected void appendInvitation(boolean async) {
int start = doc.getLength();
String promptStr = prompt.toString();
IConsoleStyleProvider styleProvider = viewer.getStyleProvider();
if (styleProvider != null) {
ScriptStyleRange style = styleProvider.createPromptStyle(promptStr, start);
if (style != null) {
addToPartitioner(style);
}
}
appendText(promptStr); //caret already updated
setCaretOffset(doc.getLength(), async);
revealEndOfDocument();
promptReady = true;
}
/**
* Shows the end of the document for the main viewer and all the related viewer for the same document.
*/
private void revealEndOfDocument() {
viewer.revealEndOfDocument();
for (Iterator<WeakReference<IScriptConsoleViewer2ForDocumentListener>> it = otherViewers.iterator(); it
.hasNext();) {
WeakReference<IScriptConsoleViewer2ForDocumentListener> ref = it.next();
IScriptConsoleViewer2ForDocumentListener v = ref.get();
if (v == null) {
it.remove();
} else {
v.revealEndOfDocument();
}
}
}
private void setCaretOffset(int offset) {
setCaretOffset(offset, false);
}
/**
* Sets the caret offset to the passed offset for the main viewer and all the related viewer for the same document.
* @param offset the offset to which the caret should be moved
*/
private void setCaretOffset(int offset, boolean async) {
viewer.setCaretOffset(offset, async);
for (Iterator<WeakReference<IScriptConsoleViewer2ForDocumentListener>> it = otherViewers.iterator(); it
.hasNext();) {
WeakReference<IScriptConsoleViewer2ForDocumentListener> ref = it.next();
IScriptConsoleViewer2ForDocumentListener v = ref.get();
if (v == null) {
it.remove();
} else {
v.setCaretOffset(offset, async);
}
}
}
/**
* @return the delimiter to be used to add new lines to the console.
*/
public String getDelimeter() {
return TextUtilities.getDefaultLineDelimiter(doc);
}
/**
* @return the length of the last line
*/
public int getLastLineLength() throws BadLocationException {
int lastLine = doc.getNumberOfLines() - 1;
return doc.getLineLength(lastLine);
}
/**
* @return the offset where the last line starts
* @throws BadLocationException
*/
public int getLastLineOffset() throws BadLocationException {
int lastLine = doc.getNumberOfLines() - 1;
return doc.getLineOffset(lastLine);
}
public int getLastLineReadOnlySize() {
return readOnlyColumnsInCurrentBeforePrompt + prompt.toString().length();
}
public int getCommandLineOffset() throws BadLocationException {
int lastLine = doc.getNumberOfLines() - 1;
int commandLineOffset = doc.getLineOffset(lastLine) + getLastLineReadOnlySize();
if (commandLineOffset > doc.getLength()) {
return doc.getLength();
}
return commandLineOffset;
}
/**
* @return the length of the current command line (all the currently
* editable area)
*
* @throws BadLocationException
*/
public int getCommandLineLength() throws BadLocationException {
int lastLine = doc.getNumberOfLines() - 1;
int len = doc.getLineLength(lastLine) - getLastLineReadOnlySize();
if (len <= 0) {
return 0;
}
return len;
}
/**
* @return the command line that the user entered.
* @throws BadLocationException
*/
public String getCommandLine() {
int commandLineOffset;
int commandLineLength;
try {
commandLineOffset = getCommandLineOffset();
commandLineLength = getCommandLineLength();
} catch (BadLocationException e1) {
Log.log(e1);
return "";
}
if (commandLineLength < 0) {
return "";
}
try {
return doc.get(commandLineOffset, commandLineLength);
} catch (BadLocationException e) {
String msg = new FastStringBuffer(60).append("Error: bad location: offset:").append(commandLineOffset)
.append(" text:").append(commandLineLength).toString();
Log.log(msg);
return "";
}
}
/**
* Sets the current command line to be executed (but without executing it).
* Used by the up/down arrow to set a previous/next command.
*
* @param command this is the command that should be in the command line.
*
* @throws BadLocationException
*/
public void setCommandLine(String command) throws BadLocationException {
doc.replace(getCommandLineOffset(), getCommandLineLength(), command);
}
public void discardCommandLine() {
if (!prompt.getNeedInput()) {
final String commandLine = getCommandLine();
if (!commandLine.isEmpty()) {
history.commit();
} else if (!prompt.getNeedMore()) {
return; // no command line; nothing to do
}
}
startDisconnected();
try {
try {
doc.replace(doc.getLength(), 0, "\n");
} catch (BadLocationException e) {
Log.log(e);
}
readOnlyColumnsInCurrentBeforePrompt = 0;
prompt.setMode(true);
prompt.setNeedInput(false);
appendInvitation(false);
viewer.setCaretOffset(doc.getLength(), false);
} finally {
stopDisconnected();
}
}
}