dumb-aware console actions (IDEADEV-41202)
[fedora-idea.git] / platform / lang-impl / src / com / intellij / execution / impl / ConsoleViewImpl.java
blobec9dcfa0b827e830a294dfa566ba66ba6ebfda3d
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.execution.impl;
19 import com.intellij.codeInsight.navigation.IncrementalSearchHandler;
20 import com.intellij.execution.ExecutionBundle;
21 import com.intellij.execution.filters.*;
22 import com.intellij.execution.process.ProcessHandler;
23 import com.intellij.execution.ui.ConsoleView;
24 import com.intellij.execution.ui.ConsoleViewContentType;
25 import com.intellij.execution.ui.ObservableConsoleView;
26 import com.intellij.ide.CommonActionsManager;
27 import com.intellij.ide.DataAccessor;
28 import com.intellij.ide.DataAccessors;
29 import com.intellij.ide.OccurenceNavigator;
30 import com.intellij.openapi.Disposable;
31 import com.intellij.openapi.actionSystem.*;
32 import com.intellij.openapi.application.ApplicationManager;
33 import com.intellij.openapi.application.ModalityState;
34 import com.intellij.openapi.command.CommandProcessor;
35 import com.intellij.openapi.diagnostic.Logger;
36 import com.intellij.openapi.diff.actions.DiffActions;
37 import com.intellij.openapi.editor.*;
38 import com.intellij.openapi.editor.actionSystem.*;
39 import com.intellij.openapi.editor.colors.CodeInsightColors;
40 import com.intellij.openapi.editor.colors.EditorColors;
41 import com.intellij.openapi.editor.colors.EditorColorsManager;
42 import com.intellij.openapi.editor.colors.EditorColorsScheme;
43 import com.intellij.openapi.editor.event.*;
44 import com.intellij.openapi.editor.ex.EditorEx;
45 import com.intellij.openapi.editor.ex.MarkupModelEx;
46 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
47 import com.intellij.openapi.editor.highlighter.HighlighterClient;
48 import com.intellij.openapi.editor.highlighter.HighlighterIterator;
49 import com.intellij.openapi.editor.impl.EditorFactoryImpl;
50 import com.intellij.openapi.editor.markup.HighlighterLayer;
51 import com.intellij.openapi.editor.markup.HighlighterTargetArea;
52 import com.intellij.openapi.editor.markup.RangeHighlighter;
53 import com.intellij.openapi.editor.markup.TextAttributes;
54 import com.intellij.openapi.extensions.Extensions;
55 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
56 import com.intellij.openapi.fileTypes.FileType;
57 import com.intellij.openapi.ide.CopyPasteManager;
58 import com.intellij.openapi.keymap.Keymap;
59 import com.intellij.openapi.keymap.KeymapManager;
60 import com.intellij.openapi.project.DumbAware;
61 import com.intellij.openapi.project.Project;
62 import com.intellij.openapi.util.Computable;
63 import com.intellij.openapi.util.Condition;
64 import com.intellij.openapi.util.Disposer;
65 import com.intellij.openapi.util.Key;
66 import com.intellij.openapi.util.text.LineTokenizer;
67 import com.intellij.openapi.util.text.StringUtil;
68 import com.intellij.pom.Navigatable;
69 import com.intellij.psi.PsiDocumentManager;
70 import com.intellij.psi.PsiFile;
71 import com.intellij.psi.PsiFileFactory;
72 import com.intellij.psi.tree.IElementType;
73 import com.intellij.util.Alarm;
74 import com.intellij.util.EditorPopupHandler;
75 import com.intellij.util.LocalTimeCounter;
76 import com.intellij.util.containers.HashMap;
77 import org.jetbrains.annotations.NotNull;
78 import org.jetbrains.annotations.TestOnly;
80 import javax.swing.*;
81 import java.awt.*;
82 import java.awt.datatransfer.DataFlavor;
83 import java.awt.datatransfer.Transferable;
84 import java.awt.event.KeyEvent;
85 import java.awt.event.KeyListener;
86 import java.awt.event.MouseEvent;
87 import java.awt.event.MouseMotionAdapter;
88 import java.io.IOException;
89 import java.util.*;
90 import java.util.List;
91 import java.util.concurrent.CopyOnWriteArraySet;
93 public final class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableConsoleView, DataProvider, OccurenceNavigator {
94 private static final Logger LOG = Logger.getInstance("#com.intellij.execution.impl.ConsoleViewImpl");
96 private static final int FLUSH_DELAY = 200; //TODO : make it an option
98 private static final Key<ConsoleViewImpl> CONSOLE_VIEW_IN_EDITOR_VIEW = Key.create("CONSOLE_VIEW_IN_EDITOR_VIEW");
99 static {
100 final EditorActionManager actionManager = EditorActionManager.getInstance();
101 final TypedAction typedAction = actionManager.getTypedAction();
102 typedAction.setupHandler(new MyTypedHandler(typedAction.getHandler()));
105 private final DisposedPsiManagerCheck myPsiDisposedCheck;
106 private ConsoleState myState = ConsoleState.NOT_STARTED;
107 private final int CYCLIC_BUFFER_SIZE = getCycleBufferSize();
108 private final boolean isViewer;
109 private Computable<ModalityState> myStateForUpdate;
111 private static int getCycleBufferSize() {
112 final String cycleBufferSizeProperty = System.getProperty("idea.cycle.buffer.size");
113 if (cycleBufferSizeProperty == null) return 1024 * 1024;
114 try {
115 return Integer.parseInt(cycleBufferSizeProperty) * 1024;
117 catch (NumberFormatException e) {
118 return 1024 * 1024;
122 private final boolean USE_CYCLIC_BUFFER = useCycleBuffer();
124 private static boolean useCycleBuffer() {
125 final String useCycleBufferProperty = System.getProperty("idea.cycle.buffer.size");
126 return useCycleBufferProperty == null || !"disabled".equalsIgnoreCase(useCycleBufferProperty);
129 private static final int HYPERLINK_LAYER = HighlighterLayer.SELECTION - 123;
130 private final Alarm mySpareTimeAlarm = new Alarm();
132 private final CopyOnWriteArraySet<ChangeListener> myListeners = new CopyOnWriteArraySet<ChangeListener>();
133 private final Set<ConsoleViewContentType> myDeferredTypes = new HashSet<ConsoleViewContentType>();
134 private final ArrayList<AnAction> customActions = new ArrayList<AnAction>();
136 @TestOnly
137 public Editor getEditor() {
138 return myEditor;
141 public void scrollToEnd() {
142 myEditor.getCaretModel().moveToOffset(myEditor.getDocument().getTextLength());
145 private static class TokenInfo{
146 private final ConsoleViewContentType contentType;
147 private int startOffset;
148 private int endOffset;
149 private final TextAttributes attributes;
151 private TokenInfo(final ConsoleViewContentType contentType, final int startOffset, final int endOffset) {
152 this.contentType = contentType;
153 this.startOffset = startOffset;
154 this.endOffset = endOffset;
155 attributes = contentType.getAttributes();
159 private final Project myProject;
161 private boolean myOutputPaused;
163 private Editor myEditor;
165 private final Object LOCK = new Object();
167 private int myContentSize;
168 private StringBuffer myDeferredOutput = new StringBuffer();
169 private StringBuffer myDeferredUserInput = new StringBuffer();
171 private ArrayList<TokenInfo> myTokens = new ArrayList<TokenInfo>();
172 private final Hyperlinks myHyperlinks = new Hyperlinks();
174 private String myHelpId;
176 private final Alarm myFlushAlarm = new Alarm();
178 private final Runnable myFlushDeferredRunnable = new Runnable() {
179 public void run() {
180 flushDeferredText();
184 private final CompositeFilter myPredefinedMessageFilter;
185 private final CompositeFilter myCustomFilter;
187 private ArrayList<String> myHistory = new ArrayList<String>();
188 private int myHistorySize = 20;
190 private ArrayList<ConsoleInputListener> myConsoleInputListeners = new ArrayList<ConsoleInputListener>();
192 public void addConsoleUserInputLestener(ConsoleInputListener consoleInputListener) {
193 myConsoleInputListeners.add(consoleInputListener);
197 * By default history works for one session. If
198 * you want to import previous session, set it up here.
199 * @param history where you can save history
201 public void importHistory(Collection<String> history) {
202 this.myHistory.clear();
203 this.myHistory.addAll(history);
204 while (this.myHistory.size() > myHistorySize) {
205 this.myHistory.remove(0);
209 public List<String> getHistory() {
210 return Collections.unmodifiableList(myHistory);
213 public void setHistorySize(int historySize) {
214 this.myHistorySize = historySize;
217 public int getHistorySize() {
218 return myHistorySize;
221 private FileType myFileType;
224 * Use it for custom highlighting for user text.
225 * This will be highlighted as appropriate file to this file type.
226 * @param fileType according to which use highlighting
228 public void setFileType(FileType fileType) {
229 myFileType = fileType;
232 public ConsoleViewImpl(final Project project, boolean viewer) {
233 this(project, viewer, null);
236 public ConsoleViewImpl(final Project project, boolean viewer, FileType fileType) {
237 super(new BorderLayout());
238 isViewer = viewer;
239 myPsiDisposedCheck = new DisposedPsiManagerCheck(project);
240 myProject = project;
241 myFileType = fileType;
243 myCustomFilter = new CompositeFilter(project);
244 myPredefinedMessageFilter = new CompositeFilter(project);
245 for (ConsoleFilterProvider filterProvider : Extensions.getExtensions(ConsoleFilterProvider.FILTER_PROVIDERS)) {
246 for (Filter filter : filterProvider.getDefaultFilters(project)) {
247 myPredefinedMessageFilter.addFilter(filter);
251 Disposer.register(project, this);
254 public void attachToProcess(final ProcessHandler processHandler){
255 myState = myState.attachTo(this, processHandler);
258 public void clear() {
259 assertIsDispatchThread();
261 final Document document;
262 synchronized(LOCK){
263 myContentSize = 0;
264 if (USE_CYCLIC_BUFFER) {
265 myDeferredOutput = new StringBuffer(Math.min(myDeferredOutput.length(), CYCLIC_BUFFER_SIZE));
267 else {
268 myDeferredOutput = new StringBuffer();
270 myDeferredTypes.clear();
271 myDeferredUserInput = new StringBuffer();
272 myHyperlinks.clear();
273 myTokens.clear();
274 if (myEditor == null) return;
275 myEditor.getMarkupModel().removeAllHighlighters();
276 document = myEditor.getDocument();
278 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
279 public void run() {
280 document.deleteString(0, document.getTextLength());
282 }, null, DocCommandGroupId.noneGroupId(document));
285 public void scrollTo(final int offset) {
286 assertIsDispatchThread();
287 flushDeferredText();
288 if (myEditor == null) return;
289 int moveOffset = offset;
290 if (USE_CYCLIC_BUFFER && moveOffset >= myEditor.getDocument().getTextLength()) {
291 moveOffset = 0;
293 myEditor.getCaretModel().moveToOffset(moveOffset);
294 myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
297 private static void assertIsDispatchThread() {
298 ApplicationManager.getApplication().assertIsDispatchThread();
301 public void setOutputPaused(final boolean value) {
302 myOutputPaused = value;
303 if (!value){
304 requestFlushImmediately();
308 public boolean isOutputPaused() {
309 return myOutputPaused;
312 public boolean hasDeferredOutput() {
313 synchronized(LOCK){
314 return myDeferredOutput.length() > 0;
318 public void performWhenNoDeferredOutput(final Runnable runnable) {
319 //Q: implement in another way without timer?
320 if (!hasDeferredOutput()){
321 runnable.run();
323 else{
324 mySpareTimeAlarm.addRequest(
325 new Runnable() {
326 public void run() {
327 performWhenNoDeferredOutput(runnable);
335 public JComponent getComponent() {
336 if (myEditor == null){
337 myEditor = createEditor();
338 requestFlushImmediately();
339 add(myEditor.getComponent(), BorderLayout.CENTER);
341 myEditor.getDocument().addDocumentListener(new DocumentAdapter() {
342 public void documentChanged(DocumentEvent e) {
343 if (e.getNewLength() == 0 && e.getOffset() == 0) {
344 // string has beeen removed from the beginning, move tokens down
345 synchronized (LOCK) {
346 int toRemoveLen = e.getOldLength();
347 int tIndex = findTokenInfoIndexByOffset(toRemoveLen);
348 ArrayList<TokenInfo> newTokens = new ArrayList<TokenInfo>(myTokens.subList(tIndex, myTokens.size()));
349 for (TokenInfo token : newTokens) {
350 token.startOffset -= toRemoveLen;
351 token.endOffset -= toRemoveLen;
353 if (!newTokens.isEmpty()) {
354 newTokens.get(0).startOffset = 0;
356 myContentSize -= Math.min(myContentSize, toRemoveLen);
357 myTokens = newTokens;
363 return this;
366 public void setModalityStateForUpdate(Computable<ModalityState> stateComputable) {
367 myStateForUpdate = stateComputable;
372 public void dispose(){
373 myState = myState.dispose();
374 if (myEditor != null){
375 myFlushAlarm.cancelAllRequests();
376 mySpareTimeAlarm.cancelAllRequests();
377 if (!myEditor.isDisposed()) {
378 EditorFactory.getInstance().releaseEditor(myEditor);
380 synchronized (LOCK) {
381 myDeferredOutput = new StringBuffer();
383 myEditor = null;
387 public void print(String s, final ConsoleViewContentType contentType) {
388 synchronized(LOCK){
389 myDeferredTypes.add(contentType);
391 s = StringUtil.convertLineSeparators(s);
392 myContentSize += s.length();
393 myDeferredOutput.append(s);
394 if (contentType == ConsoleViewContentType.USER_INPUT){
395 myDeferredUserInput.append(s);
398 boolean needNew = true;
399 if (!myTokens.isEmpty()){
400 final TokenInfo lastToken = myTokens.get(myTokens.size() - 1);
401 if (lastToken.contentType == contentType){
402 lastToken.endOffset = myContentSize; // optimization
403 needNew = false;
406 if (needNew){
407 myTokens.add(new TokenInfo(contentType, myContentSize - s.length(), myContentSize));
410 if (s.indexOf('\n') >= 0 || s.indexOf('\r') >= 0) {
411 if (contentType == ConsoleViewContentType.USER_INPUT) {
412 flushDeferredUserInput();
415 if (myFlushAlarm.getActiveRequestCount() == 0 && myEditor != null) {
416 final boolean shouldFlushNow = USE_CYCLIC_BUFFER && myDeferredOutput.length() > CYCLIC_BUFFER_SIZE;
417 myFlushAlarm.addRequest(myFlushDeferredRunnable, shouldFlushNow? 0 : FLUSH_DELAY, getStateForUpdate());
422 private ModalityState getStateForUpdate() {
423 return myStateForUpdate != null ? myStateForUpdate.compute() : ModalityState.stateForComponent(myEditor.getComponent());
426 private void requestFlushImmediately() {
427 if (myEditor != null) {
428 myFlushAlarm.addRequest(myFlushDeferredRunnable, 0, getStateForUpdate());
432 public int getContentSize() { return myContentSize; }
434 public boolean canPause() {
435 return true;
438 private void flushDeferredText() {
439 ApplicationManager.getApplication().assertIsDispatchThread();
440 if (myProject.isDisposed()) {
441 return;
444 final String text;
445 synchronized (LOCK) {
446 if (myOutputPaused) return;
447 if (myDeferredOutput.length() == 0) return;
448 if (myEditor == null) return;
450 text = myDeferredOutput.substring(0, myDeferredOutput.length());
451 if (USE_CYCLIC_BUFFER) {
452 myDeferredOutput = new StringBuffer(Math.min(myDeferredOutput.length(), CYCLIC_BUFFER_SIZE));
454 else {
455 myDeferredOutput.setLength(0);
458 final Document document = myEditor.getDocument();
459 final int oldLineCount = document.getLineCount();
460 final boolean isAtEndOfDocument = myEditor.getCaretModel().getOffset() == document.getTextLength();
461 boolean cycleUsed = USE_CYCLIC_BUFFER && document.getTextLength() + text.length() > CYCLIC_BUFFER_SIZE;
462 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
463 public void run() {
464 document.insertString(document.getTextLength(), text);
465 synchronized (LOCK) {
466 fireChange();
469 }, null, DocCommandGroupId.noneGroupId(document));
470 myPsiDisposedCheck.performCheck();
471 final int newLineCount = document.getLineCount();
472 if (cycleUsed) {
473 final int lineCount = LineTokenizer.calcLineCount(text, true);
474 for (Iterator<RangeHighlighter> it = myHyperlinks.getRanges().keySet().iterator(); it.hasNext();) {
475 if (!it.next().isValid()) {
476 it.remove();
479 highlightHyperlinks(newLineCount >= lineCount + 1 ? newLineCount - lineCount - 1 : 0, newLineCount - 1);
481 else if (oldLineCount < newLineCount) {
482 highlightHyperlinks(oldLineCount - 1, newLineCount - 2);
485 if (isAtEndOfDocument) {
486 myEditor.getCaretModel().moveToOffset(myEditor.getDocument().getTextLength());
487 myEditor.getSelectionModel().removeSelection();
488 myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
492 private void flushDeferredUserInput() {
493 if (myState.isRunning()){
494 final String text = myDeferredUserInput.substring(0, myDeferredUserInput.length());
495 final int index = Math.max(text.lastIndexOf('\n'), text.lastIndexOf('\r'));
496 if (index < 0) return;
497 try{
498 myState.sendUserInput(text.substring(0, index + 1));
500 catch(IOException e){
501 return;
503 myDeferredUserInput.setLength(0);
504 myDeferredUserInput.append(text.substring(index + 1));
508 public Object getData(final String dataId) {
509 if (DataConstants.NAVIGATABLE.equals(dataId)){
510 if (myEditor == null) {
511 return null;
513 final LogicalPosition pos = myEditor.getCaretModel().getLogicalPosition();
514 final HyperlinkInfo info = getHyperlinkInfoByLineAndCol(pos.line, pos.column);
515 final OpenFileDescriptor openFileDescriptor = info instanceof FileHyperlinkInfo ? ((FileHyperlinkInfo)info).getDescriptor() : null;
516 if (openFileDescriptor == null || !openFileDescriptor.getFile().isValid()) {
517 return null;
519 return openFileDescriptor;
522 if (DataConstants.EDITOR.equals(dataId)) {
523 return myEditor;
525 if (DataConstants.HELP_ID.equals(dataId)) {
526 return myHelpId;
528 return null;
531 public void setHelpId(final String helpId) {
532 myHelpId = helpId;
535 public void addMessageFilter(final Filter filter) {
536 myCustomFilter.addFilter(filter);
539 public void printHyperlink(final String hyperlinkText, final HyperlinkInfo info) {
540 if (myEditor == null) return;
541 print(hyperlinkText, ConsoleViewContentType.NORMAL_OUTPUT);
542 flushDeferredText();
543 final int textLength = myEditor.getDocument().getTextLength();
544 addHyperlink(textLength - hyperlinkText.length(), textLength, null, info, getHyperlinkAttributes());
547 private static TextAttributes getHyperlinkAttributes() {
548 return EditorColorsManager.getInstance().getGlobalScheme().getAttributes(CodeInsightColors.HYPERLINK_ATTRIBUTES);
551 private static TextAttributes getFollowedHyperlinkAttributes() {
552 return EditorColorsManager.getInstance().getGlobalScheme().getAttributes(CodeInsightColors.FOLLOWED_HYPERLINK_ATTRIBUTES);
555 private Editor createEditor() {
556 return ApplicationManager.getApplication().runReadAction(new Computable<Editor>() {
557 public Editor compute() {
558 return doCreateEditor();
563 private Editor doCreateEditor() {
564 final EditorFactoryImpl editorFactory = (EditorFactoryImpl) EditorFactory.getInstance();
565 final Document editorDocument = editorFactory.createDocument(true);
566 editorDocument.addDocumentListener(new DocumentListener() {
567 public void beforeDocumentChange(DocumentEvent event) {
570 public void documentChanged(DocumentEvent event) {
571 if (myFileType != null) {
572 highlightUserTokens();
577 final int bufferSize = USE_CYCLIC_BUFFER ? CYCLIC_BUFFER_SIZE : 0;
578 editorDocument.setCyclicBufferSize(bufferSize);
580 final EditorEx editor = (EditorEx) editorFactory.createViewer(editorDocument,myProject);
581 final EditorHighlighter highlighter = new MyHighlighter();
582 editor.setHighlighter(highlighter);
583 editor.putUserData(CONSOLE_VIEW_IN_EDITOR_VIEW, this);
585 final EditorSettings editorSettings = editor.getSettings();
586 editorSettings.setLineMarkerAreaShown(false);
587 editorSettings.setLineNumbersShown(false);
588 editorSettings.setFoldingOutlineShown(false);
589 editorSettings.setAdditionalPageAtBottom(false);
590 editorSettings.setAdditionalColumnsCount(0);
591 editorSettings.setAdditionalLinesCount(0);
593 final EditorColorsScheme scheme = editor.getColorsScheme();
594 editor.setBackgroundColor(scheme.getColor(ConsoleViewContentType.CONSOLE_BACKGROUND_KEY));
595 scheme.setColor(EditorColors.CARET_ROW_COLOR, null);
596 scheme.setColor(EditorColors.RIGHT_MARGIN_COLOR, null);
598 editor.addEditorMouseListener(new EditorPopupHandler(){
599 public void invokePopup(final EditorMouseEvent event) {
600 final MouseEvent mouseEvent = event.getMouseEvent();
601 popupInvoked(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
605 editor.addEditorMouseListener(
606 new EditorMouseAdapter(){
607 public void mouseReleased(final EditorMouseEvent e){
608 final MouseEvent mouseEvent = e.getMouseEvent();
609 if (!mouseEvent.isPopupTrigger()){
610 navigate(e);
616 editor.getContentComponent().addMouseMotionListener(
617 new MouseMotionAdapter(){
618 public void mouseMoved(final MouseEvent e){
619 final HyperlinkInfo info = getHyperlinkInfoByPoint(e.getPoint());
620 if (info != null){
621 editor.getContentComponent().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
623 else{
624 editor.getContentComponent().setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
630 final ConsoleViewImpl consoleView = this;
631 editor.getContentComponent().addKeyListener(new KeyListener() {
632 private int historyPosition = myHistory.size();
634 public void keyTyped(KeyEvent e) {
638 public void keyPressed(KeyEvent e) {
641 public void keyReleased(KeyEvent e) {
642 if (e.isAltDown() && !e.isControlDown() && !e.isMetaDown() && !e.isShiftDown()) {
643 if (e.getKeyCode() == KeyEvent.VK_UP) {
644 historyPosition--;
645 if (historyPosition < 0) historyPosition = 0;
646 replaceString();
647 e.consume();
648 } else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
649 historyPosition++;
650 if (historyPosition > myHistory.size()) historyPosition = myHistory.size();
651 replaceString();
652 e.consume();
654 } else {
655 historyPosition = myHistory.size();
659 private void replaceString() {
660 final String str;
662 if (myHistory.size() == historyPosition) str = "";
663 else str = myHistory.get(historyPosition);
664 synchronized (LOCK) {
665 if (myTokens.isEmpty()) return;
666 final TokenInfo info = myTokens.get(myTokens.size() - 1);
667 if (info.contentType != ConsoleViewContentType.USER_INPUT) {
668 consoleView.insertUserText(str, 0);
669 } else {
670 consoleView.replaceUserText(str, info.startOffset, info.endOffset);
676 setEditorUpActions(editor);
678 return editor;
681 private void highlightUserTokens() {
682 if (myTokens.isEmpty()) return;
683 final TokenInfo token = myTokens.get(myTokens.size() - 1);
684 if (token.contentType == ConsoleViewContentType.USER_INPUT) {
685 String text = myEditor.getDocument().getText().substring(token.startOffset, token.endOffset);
686 PsiFile file = PsiFileFactory.getInstance(myProject).
687 createFileFromText("dummy", myFileType, text, LocalTimeCounter.currentTime(), true);
688 Document document = PsiDocumentManager.getInstance(myProject).getDocument(file);
689 assert document != null;
690 Editor editor = EditorFactory.getInstance().createEditor(document, myProject, myFileType, false);
691 try {
692 RangeHighlighter[] allHighlighters = myEditor.getMarkupModel().getAllHighlighters();
693 for (RangeHighlighter highlighter : allHighlighters) {
694 if (highlighter.getStartOffset() >= token.startOffset) {
695 myEditor.getMarkupModel().removeHighlighter(highlighter);
698 HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(0);
699 while (!iterator.atEnd()) {
700 myEditor.getMarkupModel().addRangeHighlighter(iterator.getStart() + token.startOffset, iterator.getEnd() + token.startOffset, HighlighterLayer.SYNTAX,
701 iterator.getTextAttributes(),
702 HighlighterTargetArea.EXACT_RANGE);
703 iterator.advance();
706 finally {
707 EditorFactory.getInstance().releaseEditor(editor);
712 private static void setEditorUpActions(final Editor editor) {
713 new EnterHandler().registerCustomShortcutSet(CommonShortcuts.ENTER, editor.getContentComponent());
714 registerActionHandler(editor, IdeActions.ACTION_EDITOR_PASTE, new PasteHandler());
715 registerActionHandler(editor, IdeActions.ACTION_EDITOR_BACKSPACE, new BackSpaceHandler());
716 registerActionHandler(editor, IdeActions.ACTION_EDITOR_DELETE, new DeleteHandler());
719 private static void registerActionHandler(final Editor editor, final String actionId, final AnAction action) {
720 final Keymap keymap=KeymapManager.getInstance().getActiveKeymap();
721 final Shortcut[] shortcuts = keymap.getShortcuts(actionId);
722 action.registerCustomShortcutSet(new CustomShortcutSet(shortcuts), editor.getContentComponent());
725 private void popupInvoked(final Component component, final int x, final int y){
726 final DefaultActionGroup group = new DefaultActionGroup();
727 group.add(new ClearAllAction());
728 group.add(new CopyAction());
729 group.addSeparator();
730 final ActionManager actionManager = ActionManager.getInstance();
731 group.add(actionManager.getAction(DiffActions.COMPARE_WITH_CLIPBOARD));
732 final ActionPopupMenu menu = actionManager.createActionPopupMenu(ActionPlaces.UNKNOWN, group);
733 menu.getComponent().show(component, x, y);
736 private void navigate(final EditorMouseEvent event){
737 if (event.getMouseEvent().isPopupTrigger()) return;
738 final Point p = event.getMouseEvent().getPoint();
739 final HyperlinkInfo info = getHyperlinkInfoByPoint(p);
740 if (info != null){
741 info.navigate(myProject);
742 linkFollowed(info);
746 private static final Key<TextAttributes> OLD_HYPERLINK_TEXT_ATTRIBUTES = Key.create("OLD_HYPERLINK_TEXT_ATTRIBUTES");
747 private void linkFollowed(final HyperlinkInfo info) {
748 MarkupModelEx markupModel = (MarkupModelEx)myEditor.getMarkupModel();
749 for (Map.Entry<RangeHighlighter,HyperlinkInfo> entry : myHyperlinks.getRanges().entrySet()) {
750 RangeHighlighter range = entry.getKey();
751 TextAttributes oldAttr = range.getUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES);
752 if (oldAttr != null) {
753 markupModel.setRangeHighlighterAttributes(range, oldAttr);
754 range.putUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES, null);
756 if (entry.getValue() == info) {
757 TextAttributes oldAttributes = range.getTextAttributes();
758 range.putUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES, oldAttributes);
759 TextAttributes attributes = getFollowedHyperlinkAttributes().clone();
760 assert oldAttributes != null;
761 attributes.setFontType(oldAttributes.getFontType());
762 attributes.setEffectType(oldAttributes.getEffectType());
763 attributes.setEffectColor(oldAttributes.getEffectColor());
764 attributes.setForegroundColor(oldAttributes.getForegroundColor());
765 markupModel.setRangeHighlighterAttributes(range, attributes);
768 //refresh highlighter text attributes
769 RangeHighlighter dummy = markupModel.addRangeHighlighter(0, 0, HYPERLINK_LAYER, getHyperlinkAttributes(), HighlighterTargetArea.EXACT_RANGE);
770 markupModel.removeHighlighter(dummy);
773 private HyperlinkInfo getHyperlinkInfoByPoint(final Point p){
774 if (myEditor == null) return null;
775 final LogicalPosition pos = myEditor.xyToLogicalPosition(new Point(p.x, p.y));
776 return getHyperlinkInfoByLineAndCol(pos.line, pos.column);
779 private HyperlinkInfo getHyperlinkInfoByLineAndCol(final int line, final int col) {
780 final int offset = myEditor.logicalPositionToOffset(new LogicalPosition(line, col));
781 return myHyperlinks.getHyperlinkAt(offset);
784 private void highlightHyperlinks(final int line1, final int line2){
785 ApplicationManager.getApplication().assertIsDispatchThread();
786 PsiDocumentManager.getInstance(myProject).commitAllDocuments();
787 final Document document = myEditor.getDocument();
788 final CharSequence chars = document.getCharsSequence();
789 final TextAttributes hyperlinkAttributes = getHyperlinkAttributes();
791 for(int line = line1; line <= line2; line++) {
792 if (line < 0) continue;
793 final int startOffset = document.getLineStartOffset(line);
794 int endOffset = document.getLineEndOffset(line);
795 if (endOffset < document.getTextLength()){
796 endOffset++; // add '\n'
798 final String text = chars.subSequence(startOffset, endOffset).toString();
799 Filter.Result result = myCustomFilter.applyFilter(text, endOffset);
800 if (result == null) {
801 result = myPredefinedMessageFilter.applyFilter(text, endOffset);
803 if (result != null){
804 final int highlightStartOffset = result.highlightStartOffset;
805 final int highlightEndOffset = result.highlightEndOffset;
806 final HyperlinkInfo hyperlinkInfo = result.hyperlinkInfo;
807 addHyperlink(highlightStartOffset, highlightEndOffset, result.highlightAttributes, hyperlinkInfo, hyperlinkAttributes);
812 private void addHyperlink(final int highlightStartOffset,
813 final int highlightEndOffset,
814 final TextAttributes highlightAttributes,
815 final HyperlinkInfo hyperlinkInfo,
816 final TextAttributes hyperlinkAttributes) {
817 TextAttributes textAttributes = highlightAttributes != null ? highlightAttributes : hyperlinkAttributes;
818 final RangeHighlighter highlighter = myEditor.getMarkupModel().addRangeHighlighter(highlightStartOffset,
819 highlightEndOffset,
820 HYPERLINK_LAYER,
821 textAttributes,
822 HighlighterTargetArea.EXACT_RANGE);
823 myHyperlinks.add(highlighter, hyperlinkInfo);
826 private class ClearAllAction extends AnAction implements DumbAware {
827 private ClearAllAction(){
828 super(ExecutionBundle.message("clear.all.from.console.action.name"));
831 public void actionPerformed(final AnActionEvent e){
832 clear();
836 private class CopyAction extends AnAction implements DumbAware {
837 private CopyAction(){
838 super(myEditor != null && myEditor.getSelectionModel().hasSelection() ? ExecutionBundle.message("copy.selected.content.action.name") : ExecutionBundle.message("copy.content.action.name"));
841 public void actionPerformed(final AnActionEvent e){
842 if (myEditor == null) return;
843 if (myEditor.getSelectionModel().hasSelection()){
844 myEditor.getSelectionModel().copySelectionToClipboard();
846 else{
847 myEditor.getSelectionModel().setSelection(0, myEditor.getDocument().getTextLength());
848 myEditor.getSelectionModel().copySelectionToClipboard();
849 myEditor.getSelectionModel().removeSelection();
854 private class MyHighlighter extends DocumentAdapter implements EditorHighlighter {
855 private boolean myHasEditor;
857 public HighlighterIterator createIterator(final int startOffset) {
858 final int startIndex = findTokenInfoIndexByOffset(startOffset);
860 return new HighlighterIterator(){
861 private int myIndex = startIndex;
863 public TextAttributes getTextAttributes() {
864 if (myFileType != null && getTokenInfo().contentType == ConsoleViewContentType.USER_INPUT) {
865 return ConsoleViewContentType.NORMAL_OUTPUT.getAttributes();
867 return getTokenInfo() == null ? null : getTokenInfo().attributes;
870 public int getStart() {
871 return getTokenInfo() == null ? 0 : getTokenInfo().startOffset;
874 public int getEnd() {
875 return getTokenInfo() == null ? 0 : getTokenInfo().endOffset;
878 public IElementType getTokenType() {
879 return null;
882 public void advance() {
883 myIndex++;
886 public void retreat() {
887 myIndex--;
890 public boolean atEnd() {
891 return myIndex < 0 || myIndex >= myTokens.size();
894 private TokenInfo getTokenInfo() {
895 return myTokens.get(myIndex);
900 public void setText(final CharSequence text) {
903 public void setEditor(final HighlighterClient editor) {
904 LOG.assertTrue(!myHasEditor, "Highlighters cannot be reused with different editors");
905 myHasEditor = true;
908 public void setColorScheme(EditorColorsScheme scheme) {
912 private int findTokenInfoIndexByOffset(final int offset) {
913 int low = 0;
914 int high = myTokens.size() - 1;
916 while(low <= high){
917 final int mid = (low + high) / 2;
918 final TokenInfo midVal = myTokens.get(mid);
919 if (offset < midVal.startOffset){
920 high = mid - 1;
922 else if (offset >= midVal.endOffset){
923 low = mid + 1;
925 else{
926 return mid;
929 return myTokens.size();
932 private static class MyTypedHandler implements TypedActionHandler {
933 private final TypedActionHandler myOriginalHandler;
935 private MyTypedHandler(final TypedActionHandler originalAction) {
936 myOriginalHandler = originalAction;
939 public void execute(@NotNull final Editor editor, final char charTyped, @NotNull final DataContext dataContext) {
940 final ConsoleViewImpl consoleView = editor.getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW);
941 if (consoleView == null || !consoleView.myState.isRunning() || consoleView.isViewer){
942 myOriginalHandler.execute(editor, charTyped, dataContext);
944 else{
945 final String s = String.valueOf(charTyped);
946 SelectionModel selectionModel = editor.getSelectionModel();
947 if (selectionModel.hasSelection()) {
948 consoleView.replaceUserText(s, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
949 } else {
950 consoleView.insertUserText(s, editor.getCaretModel().getOffset());
956 private static final DataAccessor<ConsoleViewImpl> CONSOLE = new DataAccessor<ConsoleViewImpl>() {
957 public ConsoleViewImpl getImpl(final DataContext dataContext) throws NoDataException {
958 return DataAccessors.EDITOR.getNotNull(dataContext).getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW);
962 private static final Condition<ConsoleViewImpl> CONSOLE_IS_RUNNING = new Condition<ConsoleViewImpl>() {
963 public boolean value(final ConsoleViewImpl consoleView) {
964 return consoleView.myState.isRunning();
968 private static final DataAccessor<ConsoleViewImpl> RUNNINT_CONSOLE =DataAccessor.createConditionalAccessor(CONSOLE, CONSOLE_IS_RUNNING);
970 private abstract static class ConsoleAction extends AnAction implements DumbAware {
971 public void actionPerformed(final AnActionEvent e) {
972 final DataContext context = e.getDataContext();
973 final ConsoleViewImpl console = RUNNINT_CONSOLE.from(context);
974 execute(console, context);
977 protected abstract void execute(ConsoleViewImpl console, final DataContext context);
979 public void update(final AnActionEvent e) {
980 final ConsoleViewImpl console = RUNNINT_CONSOLE.from(e.getDataContext());
981 e.getPresentation().setEnabled(console != null);
985 private static class EnterHandler extends ConsoleAction {
986 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
987 synchronized (consoleView.LOCK) {
988 String str = consoleView.myDeferredUserInput.toString();
989 if (StringUtil.isNotEmpty(str)) {
990 consoleView.myHistory.remove(str);
991 consoleView.myHistory.add(str);
992 if (consoleView.myHistory.size() > consoleView.myHistorySize) consoleView.myHistory.remove(0);
994 for (ConsoleInputListener listener : consoleView.myConsoleInputListeners) {
995 listener.textEntered(str);
998 consoleView.print("\n", ConsoleViewContentType.USER_INPUT);
999 consoleView.flushDeferredText();
1000 final Editor editor = consoleView.myEditor;
1001 editor.getCaretModel().moveToOffset(editor.getDocument().getTextLength());
1002 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1006 private static class PasteHandler extends ConsoleAction {
1007 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1008 final Transferable content = CopyPasteManager.getInstance().getContents();
1009 if (content == null) return;
1010 String s = null;
1011 try {
1012 s = (String)content.getTransferData(DataFlavor.stringFlavor);
1014 catch(Exception e) {
1015 consoleView.myEditor.getComponent().getToolkit().beep();
1017 if (s == null) return;
1018 Editor editor = consoleView.myEditor;
1019 SelectionModel selectionModel = editor.getSelectionModel();
1020 if (selectionModel.hasSelection()) {
1021 consoleView.replaceUserText(s, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
1022 } else {
1023 consoleView.insertUserText(s, editor.getCaretModel().getOffset());
1028 private static class BackSpaceHandler extends ConsoleAction {
1029 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1030 final Editor editor = consoleView.myEditor;
1032 if (IncrementalSearchHandler.isHintVisible(editor)) {
1033 getDefaultActionHandler().execute(editor, context);
1034 return;
1037 final Document document = editor.getDocument();
1038 final int length = document.getTextLength();
1039 if (length == 0) {
1040 return;
1043 SelectionModel selectionModel = editor.getSelectionModel();
1044 if (selectionModel.hasSelection()) {
1045 consoleView.deleteUserText(selectionModel.getSelectionStart(),
1046 selectionModel.getSelectionEnd() - selectionModel.getSelectionStart());
1047 } else if (editor.getCaretModel().getOffset() > 0) {
1048 consoleView.deleteUserText(editor.getCaretModel().getOffset() - 1, 1);
1052 private static EditorActionHandler getDefaultActionHandler() {
1053 return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE);
1057 private static class DeleteHandler extends ConsoleAction {
1058 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1059 final Editor editor = consoleView.myEditor;
1061 if (IncrementalSearchHandler.isHintVisible(editor)) {
1062 getDefaultActionHandler().execute(editor, context);
1063 return;
1066 final Document document = editor.getDocument();
1067 final int length = document.getTextLength();
1068 if (length == 0) {
1069 return;
1072 SelectionModel selectionModel = editor.getSelectionModel();
1073 if (selectionModel.hasSelection()) {
1074 consoleView.deleteUserText(selectionModel.getSelectionStart(),
1075 selectionModel.getSelectionEnd() - selectionModel.getSelectionStart());
1076 } else {
1077 consoleView.deleteUserText(editor.getCaretModel().getOffset(), 1);
1081 private static EditorActionHandler getDefaultActionHandler() {
1082 return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE);
1086 private static class Hyperlinks {
1087 private static final int NO_INDEX = Integer.MIN_VALUE;
1088 private final Map<RangeHighlighter,HyperlinkInfo> myHighlighterToMessageInfoMap = new HashMap<RangeHighlighter, HyperlinkInfo>();
1089 private int myLastIndex = NO_INDEX;
1091 public void clear() {
1092 myHighlighterToMessageInfoMap.clear();
1093 myLastIndex = NO_INDEX;
1096 public HyperlinkInfo getHyperlinkAt(final int offset) {
1097 for (final RangeHighlighter highlighter : myHighlighterToMessageInfoMap.keySet()) {
1098 if (highlighter.isValid() && containsOffset(offset, highlighter)) {
1099 return myHighlighterToMessageInfoMap.get(highlighter);
1102 return null;
1105 private static boolean containsOffset(final int offset, final RangeHighlighter highlighter) {
1106 return highlighter.getStartOffset() <= offset && offset <= highlighter.getEndOffset();
1109 public void add(final RangeHighlighter highlighter, final HyperlinkInfo hyperlinkInfo) {
1110 myHighlighterToMessageInfoMap.put(highlighter, hyperlinkInfo);
1111 if (myLastIndex != NO_INDEX && containsOffset(myLastIndex, highlighter)) myLastIndex = NO_INDEX;
1114 private Map<RangeHighlighter,HyperlinkInfo> getRanges() {
1115 return myHighlighterToMessageInfoMap;
1119 public JComponent getPreferredFocusableComponent() {
1120 //ensure editor created
1121 getComponent();
1122 return myEditor.getContentComponent();
1126 // navigate up/down in stack trace
1127 public boolean hasNextOccurence() {
1128 return next(1, false) != null;
1131 public boolean hasPreviousOccurence() {
1132 return next(-1, false) != null;
1135 public OccurenceInfo goNextOccurence() {
1136 return next(1, true);
1139 private OccurenceInfo next(final int delta, boolean doMove) {
1140 List<RangeHighlighter> ranges = new ArrayList<RangeHighlighter>(myHyperlinks.getRanges().keySet());
1141 Collections.sort(ranges, new Comparator<RangeHighlighter>() {
1142 public int compare(final RangeHighlighter o1, final RangeHighlighter o2) {
1143 return o1.getStartOffset() - o2.getStartOffset();
1146 int i;
1147 for (i = 0; i<ranges.size(); i++) {
1148 RangeHighlighter range = ranges.get(i);
1149 if (range.getUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES) != null) {
1150 break;
1153 int newIndex = ranges.isEmpty() ? -1 : i == ranges.size() ? 0 : (i + delta + ranges.size()) % ranges.size();
1154 RangeHighlighter next = newIndex < ranges.size() && newIndex >= 0 ? ranges.get(newIndex) : null;
1155 if (next == null) return null;
1156 if (doMove) {
1157 scrollTo(next.getStartOffset());
1159 final HyperlinkInfo hyperlinkInfo = myHyperlinks.getRanges().get(next);
1160 return new OccurenceInfo(new Navigatable() {
1161 public void navigate(final boolean requestFocus) {
1162 hyperlinkInfo.navigate(myProject);
1163 linkFollowed(hyperlinkInfo);
1166 public boolean canNavigate() {
1167 return true;
1170 public boolean canNavigateToSource() {
1171 return true;
1173 }, i, ranges.size());
1176 public OccurenceInfo goPreviousOccurence() {
1177 return next(-1, true);
1180 public String getNextOccurenceActionName() {
1181 return ExecutionBundle.message("down.the.stack.trace");
1184 public String getPreviousOccurenceActionName() {
1185 return ExecutionBundle.message("up.the.stack.trace");
1188 public void addCustomConsoleAction(@NotNull AnAction action) {
1189 customActions.add(action);
1192 @NotNull
1193 public AnAction[] createConsoleActions() {
1194 //Initializing prev and next occurrences actions
1195 CommonActionsManager actionsManager = CommonActionsManager.getInstance();
1196 AnAction prevAction = actionsManager.createPrevOccurenceAction(this);
1197 prevAction.getTemplatePresentation().setText(getPreviousOccurenceActionName());
1198 AnAction nextAction = actionsManager.createNextOccurenceAction(this);
1199 nextAction.getTemplatePresentation().setText(getNextOccurenceActionName());
1200 //Initializing custom actions
1201 AnAction[] consoleActions = new AnAction[2 + customActions.size()];
1202 consoleActions[0] = prevAction;
1203 consoleActions[1] = nextAction;
1204 for (int i = 0; i < customActions.size(); ++i) {
1205 consoleActions[i + 2] = customActions.get(i);
1207 return consoleActions;
1210 public void setEditorEnabled(boolean enabled) {
1211 myEditor.getContentComponent().setEnabled(enabled);
1214 private void fireChange() {
1215 if (myDeferredTypes.isEmpty()) return;
1216 Collection<ConsoleViewContentType> types = Collections.unmodifiableCollection(myDeferredTypes);
1218 for (ChangeListener each : myListeners) {
1219 each.contentAdded(types);
1222 myDeferredTypes.clear();
1225 public void addChangeListener(final ChangeListener listener, final Disposable parent) {
1226 myListeners.add(listener);
1227 Disposer.register(parent, new Disposable() {
1228 public void dispose() {
1229 myListeners.remove(listener);
1235 * insert text to document
1236 * @param s inserted text
1237 * @param offset relativly to all document text
1239 private void insertUserText(final String s, int offset) {
1240 final ConsoleViewImpl consoleView = this;
1241 final Editor editor = consoleView.myEditor;
1242 final Document document = editor.getDocument();
1243 final int startOffset;
1245 synchronized (consoleView.LOCK) {
1246 if (consoleView.myTokens.isEmpty()) return;
1247 final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1248 if (info.contentType != ConsoleViewContentType.USER_INPUT && !s.contains("\n")) {
1249 consoleView.print(s, ConsoleViewContentType.USER_INPUT);
1250 consoleView.flushDeferredText();
1251 editor.getCaretModel().moveToOffset(document.getTextLength());
1252 editor.getSelectionModel().removeSelection();
1253 return;
1254 } else if (info.contentType != ConsoleViewContentType.USER_INPUT) {
1255 insertUserText("temp", offset);
1256 final TokenInfo newInfo = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1257 replaceUserText(s, newInfo.startOffset, newInfo.endOffset);
1258 return;
1260 int charCountToAdd;
1263 if (offset > info.endOffset) {
1264 startOffset = info.endOffset;
1266 else if (offset < info.startOffset) {
1267 startOffset = info.startOffset;
1268 } else {
1269 startOffset = offset;
1271 charCountToAdd = s.length();
1273 if (consoleView.myDeferredUserInput.length() < info.endOffset - info.startOffset) return; //user was quick
1275 consoleView.myDeferredUserInput.insert(startOffset - info.startOffset, s);
1277 info.endOffset += charCountToAdd;
1278 consoleView.myContentSize += charCountToAdd;
1281 document.insertString(startOffset, s);
1282 editor.getCaretModel().moveToOffset(startOffset + s.length());
1283 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1287 * replace text
1288 * @param s text for replace
1289 * @param start relativly to all document text
1290 * @param end relativly to all document text
1292 private void replaceUserText(final String s, int start, int end) {
1293 if (start == end) {
1294 insertUserText(s, start);
1295 return;
1297 final ConsoleViewImpl consoleView = this;
1298 final Editor editor = consoleView.myEditor;
1299 final Document document = editor.getDocument();
1300 final int startOffset;
1301 final int endOffset;
1303 synchronized (consoleView.LOCK) {
1304 if (consoleView.myTokens.isEmpty()) return;
1305 final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1306 if (info.contentType != ConsoleViewContentType.USER_INPUT) {
1307 consoleView.print(s, ConsoleViewContentType.USER_INPUT);
1308 consoleView.flushDeferredText();
1309 editor.getCaretModel().moveToOffset(document.getTextLength());
1310 editor.getSelectionModel().removeSelection();
1311 return;
1313 if (consoleView.myDeferredUserInput.length() == 0) return;
1314 int charCountToReplace;
1316 startOffset = getStartOffset(start, info);
1317 endOffset = getEndOffset(end, info);
1319 if (startOffset == -1 ||
1320 endOffset == -1 ||
1321 endOffset <= startOffset) {
1322 editor.getSelectionModel().removeSelection();
1323 editor.getCaretModel().moveToOffset(start);
1324 return;
1326 charCountToReplace = s.length() - endOffset + startOffset;
1328 if (consoleView.myDeferredUserInput.length() < info.endOffset - info.startOffset) return; //user was quick
1330 consoleView.myDeferredUserInput.replace(startOffset - info.startOffset, endOffset - info.startOffset, s);
1332 info.endOffset += charCountToReplace;
1333 if (info.startOffset == info.endOffset) {
1334 consoleView.myTokens.remove(consoleView.myTokens.size() - 1);
1336 consoleView.myContentSize += charCountToReplace;
1339 document.replaceString(startOffset, endOffset, s);
1340 editor.getCaretModel().moveToOffset(startOffset + s.length());
1341 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1342 editor.getSelectionModel().removeSelection();
1346 * delete text
1347 * @param offset relativly to all document text
1348 * @param length lenght of deleted text
1350 private void deleteUserText(int offset, int length) {
1351 ConsoleViewImpl consoleView = this;
1352 final Editor editor = consoleView.myEditor;
1353 final Document document = editor.getDocument();
1354 final int startOffset;
1355 final int endOffset;
1357 synchronized (consoleView.LOCK) {
1358 if (consoleView.myTokens.isEmpty()) return;
1359 final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1360 if (info.contentType != ConsoleViewContentType.USER_INPUT) return;
1361 if (consoleView.myDeferredUserInput.length() == 0) return;
1362 int charCountToDelete;
1364 startOffset = getStartOffset(offset, info);
1365 endOffset = getEndOffset(offset + length, info);
1366 if (startOffset == -1 ||
1367 endOffset == -1 ||
1368 endOffset <= startOffset) {
1369 editor.getSelectionModel().removeSelection();
1370 editor.getCaretModel().moveToOffset(offset);
1371 return;
1374 consoleView.myDeferredUserInput.delete(startOffset - info.startOffset, endOffset - info.startOffset);
1375 charCountToDelete = endOffset - startOffset;
1377 info.endOffset -= charCountToDelete;
1378 if (info.startOffset == info.endOffset) {
1379 consoleView.myTokens.remove(consoleView.myTokens.size() - 1);
1381 consoleView.myContentSize -= charCountToDelete;
1384 document.deleteString(startOffset, endOffset);
1385 editor.getCaretModel().moveToOffset(startOffset);
1386 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1387 editor.getSelectionModel().removeSelection();
1390 //util methods for add, replace, delete methods
1391 private static int getStartOffset(int offset, TokenInfo info) {
1392 int startOffset;
1393 if (offset >= info.startOffset && offset < info.endOffset) {
1394 startOffset = offset;
1395 } else if (offset < info.startOffset) {
1396 startOffset = info.startOffset;
1397 } else {
1398 startOffset = -1;
1400 return startOffset;
1403 private static int getEndOffset(int offset, TokenInfo info) {
1404 int endOffset;
1405 if (offset > info.endOffset) {
1406 endOffset = info.endOffset;
1407 } else if (offset <= info.startOffset) {
1408 endOffset = -1;
1409 } else {
1410 endOffset = offset;
1412 return endOffset;