ComponentWithBrowseButton - optional remove listener on hide
[fedora-idea.git] / platform / lang-impl / src / com / intellij / execution / impl / ConsoleViewImpl.java
blob6d3a2a571f61224da9942f87d4187b76f0342d56
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.OccurenceNavigator;
28 import com.intellij.openapi.Disposable;
29 import com.intellij.openapi.actionSystem.*;
30 import com.intellij.openapi.application.ApplicationManager;
31 import com.intellij.openapi.application.ModalityState;
32 import com.intellij.openapi.command.CommandProcessor;
33 import com.intellij.openapi.diagnostic.Logger;
34 import com.intellij.openapi.diff.actions.DiffActions;
35 import com.intellij.openapi.editor.*;
36 import com.intellij.openapi.editor.actionSystem.*;
37 import com.intellij.openapi.editor.colors.CodeInsightColors;
38 import com.intellij.openapi.editor.colors.EditorColors;
39 import com.intellij.openapi.editor.colors.EditorColorsManager;
40 import com.intellij.openapi.editor.colors.EditorColorsScheme;
41 import com.intellij.openapi.editor.event.*;
42 import com.intellij.openapi.editor.ex.EditorEx;
43 import com.intellij.openapi.editor.ex.MarkupModelEx;
44 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
45 import com.intellij.openapi.editor.highlighter.HighlighterClient;
46 import com.intellij.openapi.editor.highlighter.HighlighterIterator;
47 import com.intellij.openapi.editor.impl.EditorFactoryImpl;
48 import com.intellij.openapi.editor.markup.HighlighterLayer;
49 import com.intellij.openapi.editor.markup.HighlighterTargetArea;
50 import com.intellij.openapi.editor.markup.RangeHighlighter;
51 import com.intellij.openapi.editor.markup.TextAttributes;
52 import com.intellij.openapi.extensions.Extensions;
53 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
54 import com.intellij.openapi.fileTypes.FileType;
55 import com.intellij.openapi.ide.CopyPasteManager;
56 import com.intellij.openapi.keymap.Keymap;
57 import com.intellij.openapi.keymap.KeymapManager;
58 import com.intellij.openapi.project.DumbAware;
59 import com.intellij.openapi.project.Project;
60 import com.intellij.openapi.util.Computable;
61 import com.intellij.openapi.util.Disposer;
62 import com.intellij.openapi.util.Key;
63 import com.intellij.openapi.util.text.LineTokenizer;
64 import com.intellij.openapi.util.text.StringUtil;
65 import com.intellij.pom.Navigatable;
66 import com.intellij.psi.PsiDocumentManager;
67 import com.intellij.psi.PsiFile;
68 import com.intellij.psi.PsiFileFactory;
69 import com.intellij.psi.tree.IElementType;
70 import com.intellij.util.Alarm;
71 import com.intellij.util.EditorPopupHandler;
72 import com.intellij.util.LocalTimeCounter;
73 import com.intellij.util.containers.HashMap;
74 import org.jetbrains.annotations.NotNull;
75 import org.jetbrains.annotations.Nullable;
76 import org.jetbrains.annotations.TestOnly;
78 import javax.swing.*;
79 import java.awt.*;
80 import java.awt.datatransfer.DataFlavor;
81 import java.awt.datatransfer.Transferable;
82 import java.awt.event.KeyEvent;
83 import java.awt.event.KeyListener;
84 import java.awt.event.MouseEvent;
85 import java.awt.event.MouseMotionAdapter;
86 import java.io.IOException;
87 import java.util.*;
88 import java.util.List;
89 import java.util.concurrent.CopyOnWriteArraySet;
91 public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableConsoleView, DataProvider, OccurenceNavigator {
92 private static final Logger LOG = Logger.getInstance("#com.intellij.execution.impl.ConsoleViewImpl");
94 private static final int FLUSH_DELAY = 200; //TODO : make it an option
96 private static final Key<ConsoleViewImpl> CONSOLE_VIEW_IN_EDITOR_VIEW = Key.create("CONSOLE_VIEW_IN_EDITOR_VIEW");
97 static {
98 final EditorActionManager actionManager = EditorActionManager.getInstance();
99 final TypedAction typedAction = actionManager.getTypedAction();
100 typedAction.setupHandler(new MyTypedHandler(typedAction.getHandler()));
103 private final DisposedPsiManagerCheck myPsiDisposedCheck;
104 private ConsoleState myState = ConsoleState.NOT_STARTED;
105 private final int CYCLIC_BUFFER_SIZE = getCycleBufferSize();
106 private final boolean isViewer;
107 private Computable<ModalityState> myStateForUpdate;
109 private static int getCycleBufferSize() {
110 final String cycleBufferSizeProperty = System.getProperty("idea.cycle.buffer.size");
111 if (cycleBufferSizeProperty == null) return 1024 * 1024;
112 try {
113 return Integer.parseInt(cycleBufferSizeProperty) * 1024;
115 catch (NumberFormatException e) {
116 return 1024 * 1024;
120 private final boolean USE_CYCLIC_BUFFER = useCycleBuffer();
122 private static boolean useCycleBuffer() {
123 final String useCycleBufferProperty = System.getProperty("idea.cycle.buffer.size");
124 return useCycleBufferProperty == null || !"disabled".equalsIgnoreCase(useCycleBufferProperty);
127 private static final int HYPERLINK_LAYER = HighlighterLayer.SELECTION - 123;
128 private final Alarm mySpareTimeAlarm = new Alarm();
130 private final CopyOnWriteArraySet<ChangeListener> myListeners = new CopyOnWriteArraySet<ChangeListener>();
131 private final Set<ConsoleViewContentType> myDeferredTypes = new HashSet<ConsoleViewContentType>();
132 private final ArrayList<AnAction> customActions = new ArrayList<AnAction>();
134 @TestOnly
135 public Editor getEditor() {
136 return myEditor;
139 public void scrollToEnd() {
140 myEditor.getCaretModel().moveToOffset(myEditor.getDocument().getTextLength());
143 private static class TokenInfo{
144 private final ConsoleViewContentType contentType;
145 private int startOffset;
146 private int endOffset;
147 private final TextAttributes attributes;
149 private TokenInfo(final ConsoleViewContentType contentType, final int startOffset, final int endOffset) {
150 this.contentType = contentType;
151 this.startOffset = startOffset;
152 this.endOffset = endOffset;
153 attributes = contentType.getAttributes();
157 private final Project myProject;
159 private boolean myOutputPaused;
161 private Editor myEditor;
163 private final Object LOCK = new Object();
165 private int myContentSize;
166 private StringBuffer myDeferredOutput = new StringBuffer();
167 private StringBuffer myDeferredUserInput = new StringBuffer();
169 private ArrayList<TokenInfo> myTokens = new ArrayList<TokenInfo>();
170 private final Hyperlinks myHyperlinks = new Hyperlinks();
172 private String myHelpId;
174 private final Alarm myFlushAlarm = new Alarm();
176 private final Runnable myFlushDeferredRunnable = new Runnable() {
177 public void run() {
178 flushDeferredText();
182 private final CompositeFilter myPredefinedMessageFilter;
183 private final CompositeFilter myCustomFilter;
185 private ArrayList<String> myHistory = new ArrayList<String>();
186 private int myHistorySize = 20;
188 private ArrayList<ConsoleInputListener> myConsoleInputListeners = new ArrayList<ConsoleInputListener>();
190 public void addConsoleUserInputLestener(ConsoleInputListener consoleInputListener) {
191 myConsoleInputListeners.add(consoleInputListener);
195 * By default history works for one session. If
196 * you want to import previous session, set it up here.
197 * @param history where you can save history
199 public void importHistory(Collection<String> history) {
200 this.myHistory.clear();
201 this.myHistory.addAll(history);
202 while (this.myHistory.size() > myHistorySize) {
203 this.myHistory.remove(0);
207 public List<String> getHistory() {
208 return Collections.unmodifiableList(myHistory);
211 public void setHistorySize(int historySize) {
212 this.myHistorySize = historySize;
215 public int getHistorySize() {
216 return myHistorySize;
219 private FileType myFileType;
222 * Use it for custom highlighting for user text.
223 * This will be highlighted as appropriate file to this file type.
224 * @param fileType according to which use highlighting
226 public void setFileType(FileType fileType) {
227 myFileType = fileType;
230 public ConsoleViewImpl(final Project project, boolean viewer) {
231 this(project, viewer, null);
234 public ConsoleViewImpl(final Project project, boolean viewer, FileType fileType) {
235 super(new BorderLayout());
236 isViewer = viewer;
237 myPsiDisposedCheck = new DisposedPsiManagerCheck(project);
238 myProject = project;
239 myFileType = fileType;
241 myCustomFilter = new CompositeFilter(project);
242 myPredefinedMessageFilter = new CompositeFilter(project);
243 for (ConsoleFilterProvider filterProvider : Extensions.getExtensions(ConsoleFilterProvider.FILTER_PROVIDERS)) {
244 for (Filter filter : filterProvider.getDefaultFilters(project)) {
245 myPredefinedMessageFilter.addFilter(filter);
249 Disposer.register(project, this);
252 public void attachToProcess(final ProcessHandler processHandler){
253 myState = myState.attachTo(this, processHandler);
256 public void clear() {
257 assertIsDispatchThread();
259 final Document document;
260 synchronized(LOCK){
261 myContentSize = 0;
262 if (USE_CYCLIC_BUFFER) {
263 myDeferredOutput = new StringBuffer(Math.min(myDeferredOutput.length(), CYCLIC_BUFFER_SIZE));
265 else {
266 myDeferredOutput = new StringBuffer();
268 myDeferredTypes.clear();
269 myDeferredUserInput = new StringBuffer();
270 myHyperlinks.clear();
271 myTokens.clear();
272 if (myEditor == null) return;
273 myEditor.getMarkupModel().removeAllHighlighters();
274 document = myEditor.getDocument();
276 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
277 public void run() {
278 document.deleteString(0, document.getTextLength());
280 }, null, DocCommandGroupId.noneGroupId(document));
283 public void scrollTo(final int offset) {
284 assertIsDispatchThread();
285 flushDeferredText();
286 if (myEditor == null) return;
287 int moveOffset = offset;
288 if (USE_CYCLIC_BUFFER && moveOffset >= myEditor.getDocument().getTextLength()) {
289 moveOffset = 0;
291 myEditor.getCaretModel().moveToOffset(moveOffset);
292 myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
295 private static void assertIsDispatchThread() {
296 ApplicationManager.getApplication().assertIsDispatchThread();
299 public void setOutputPaused(final boolean value) {
300 myOutputPaused = value;
301 if (!value){
302 requestFlushImmediately();
306 public boolean isOutputPaused() {
307 return myOutputPaused;
310 public boolean hasDeferredOutput() {
311 synchronized(LOCK){
312 return myDeferredOutput.length() > 0;
316 public void performWhenNoDeferredOutput(final Runnable runnable) {
317 //Q: implement in another way without timer?
318 if (!hasDeferredOutput()){
319 runnable.run();
321 else{
322 mySpareTimeAlarm.addRequest(
323 new Runnable() {
324 public void run() {
325 performWhenNoDeferredOutput(runnable);
333 public JComponent getComponent() {
334 if (myEditor == null){
335 myEditor = createEditor();
336 requestFlushImmediately();
337 add(createCenterComponent(), BorderLayout.CENTER);
339 myEditor.getDocument().addDocumentListener(new DocumentAdapter() {
340 public void documentChanged(DocumentEvent e) {
341 if (e.getNewLength() == 0 && e.getOffset() == 0) {
342 // string has beeen removed from the beginning, move tokens down
343 synchronized (LOCK) {
344 int toRemoveLen = e.getOldLength();
345 int tIndex = findTokenInfoIndexByOffset(toRemoveLen);
346 ArrayList<TokenInfo> newTokens = new ArrayList<TokenInfo>(myTokens.subList(tIndex, myTokens.size()));
347 for (TokenInfo token : newTokens) {
348 token.startOffset -= toRemoveLen;
349 token.endOffset -= toRemoveLen;
351 if (!newTokens.isEmpty()) {
352 newTokens.get(0).startOffset = 0;
354 myContentSize -= Math.min(myContentSize, toRemoveLen);
355 myTokens = newTokens;
361 return this;
364 protected JComponent createCenterComponent() {
365 return myEditor.getComponent();
368 public void setModalityStateForUpdate(Computable<ModalityState> stateComputable) {
369 myStateForUpdate = stateComputable;
374 public void dispose(){
375 myState = myState.dispose();
376 if (myEditor != null){
377 myFlushAlarm.cancelAllRequests();
378 mySpareTimeAlarm.cancelAllRequests();
379 disposeEditor();
380 synchronized (LOCK) {
381 myDeferredOutput = new StringBuffer();
383 myEditor = null;
387 protected void disposeEditor() {
388 if (!myEditor.isDisposed()) {
389 EditorFactory.getInstance().releaseEditor(myEditor);
393 public void print(String s, final ConsoleViewContentType contentType) {
394 synchronized(LOCK){
395 myDeferredTypes.add(contentType);
397 s = StringUtil.convertLineSeparators(s);
398 myContentSize += s.length();
399 myDeferredOutput.append(s);
400 if (contentType == ConsoleViewContentType.USER_INPUT){
401 myDeferredUserInput.append(s);
404 boolean needNew = true;
405 if (!myTokens.isEmpty()){
406 final TokenInfo lastToken = myTokens.get(myTokens.size() - 1);
407 if (lastToken.contentType == contentType){
408 lastToken.endOffset = myContentSize; // optimization
409 needNew = false;
412 if (needNew){
413 myTokens.add(new TokenInfo(contentType, myContentSize - s.length(), myContentSize));
416 if (s.indexOf('\n') >= 0 || s.indexOf('\r') >= 0) {
417 if (contentType == ConsoleViewContentType.USER_INPUT) {
418 flushDeferredUserInput();
421 if (myFlushAlarm.getActiveRequestCount() == 0 && myEditor != null) {
422 final boolean shouldFlushNow = USE_CYCLIC_BUFFER && myDeferredOutput.length() > CYCLIC_BUFFER_SIZE;
423 myFlushAlarm.addRequest(myFlushDeferredRunnable, shouldFlushNow? 0 : FLUSH_DELAY, getStateForUpdate());
428 private ModalityState getStateForUpdate() {
429 return myStateForUpdate != null ? myStateForUpdate.compute() : ModalityState.stateForComponent(this);
432 private void requestFlushImmediately() {
433 if (myEditor != null) {
434 myFlushAlarm.addRequest(myFlushDeferredRunnable, 0, getStateForUpdate());
438 public int getContentSize() { return myContentSize; }
440 public boolean canPause() {
441 return true;
444 private void flushDeferredText() {
445 ApplicationManager.getApplication().assertIsDispatchThread();
446 if (myProject.isDisposed()) {
447 return;
450 final String text;
451 synchronized (LOCK) {
452 if (myOutputPaused) return;
453 if (myDeferredOutput.length() == 0) return;
454 if (myEditor == null) return;
456 text = myDeferredOutput.substring(0, myDeferredOutput.length());
457 if (USE_CYCLIC_BUFFER) {
458 myDeferredOutput = new StringBuffer(Math.min(myDeferredOutput.length(), CYCLIC_BUFFER_SIZE));
460 else {
461 myDeferredOutput.setLength(0);
464 final Document document = myEditor.getDocument();
465 final int oldLineCount = document.getLineCount();
466 final boolean isAtEndOfDocument = myEditor.getCaretModel().getOffset() == document.getTextLength();
467 boolean cycleUsed = USE_CYCLIC_BUFFER && document.getTextLength() + text.length() > CYCLIC_BUFFER_SIZE;
468 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
469 public void run() {
470 document.insertString(document.getTextLength(), text);
471 synchronized (LOCK) {
472 fireChange();
475 }, null, DocCommandGroupId.noneGroupId(document));
476 myPsiDisposedCheck.performCheck();
477 final int newLineCount = document.getLineCount();
478 if (cycleUsed) {
479 final int lineCount = LineTokenizer.calcLineCount(text, true);
480 for (Iterator<RangeHighlighter> it = myHyperlinks.getRanges().keySet().iterator(); it.hasNext();) {
481 if (!it.next().isValid()) {
482 it.remove();
485 highlightHyperlinks(newLineCount >= lineCount + 1 ? newLineCount - lineCount - 1 : 0, newLineCount - 1);
487 else if (oldLineCount < newLineCount) {
488 highlightHyperlinks(oldLineCount - 1, newLineCount - 2);
491 if (isAtEndOfDocument) {
492 myEditor.getCaretModel().moveToOffset(myEditor.getDocument().getTextLength());
493 myEditor.getSelectionModel().removeSelection();
494 myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
498 private void flushDeferredUserInput() {
499 if (myState.isRunning()){
500 final String text = myDeferredUserInput.substring(0, myDeferredUserInput.length());
501 final int index = Math.max(text.lastIndexOf('\n'), text.lastIndexOf('\r'));
502 if (index < 0) return;
503 try{
504 myState.sendUserInput(text.substring(0, index + 1));
506 catch(IOException e){
507 return;
509 myDeferredUserInput.setLength(0);
510 myDeferredUserInput.append(text.substring(index + 1));
514 public Object getData(final String dataId) {
515 if (PlatformDataKeys.NAVIGATABLE.is(dataId)){
516 if (myEditor == null) {
517 return null;
519 final LogicalPosition pos = myEditor.getCaretModel().getLogicalPosition();
520 final HyperlinkInfo info = getHyperlinkInfoByLineAndCol(pos.line, pos.column);
521 final OpenFileDescriptor openFileDescriptor = info instanceof FileHyperlinkInfo ? ((FileHyperlinkInfo)info).getDescriptor() : null;
522 if (openFileDescriptor == null || !openFileDescriptor.getFile().isValid()) {
523 return null;
525 return openFileDescriptor;
528 if (PlatformDataKeys.EDITOR.is(dataId)) {
529 return myEditor;
531 if (PlatformDataKeys.HELP_ID.is(dataId)) {
532 return myHelpId;
534 return null;
537 public void setHelpId(final String helpId) {
538 myHelpId = helpId;
541 public void addMessageFilter(final Filter filter) {
542 myCustomFilter.addFilter(filter);
545 public void printHyperlink(final String hyperlinkText, final HyperlinkInfo info) {
546 if (myEditor == null) return;
547 print(hyperlinkText, ConsoleViewContentType.NORMAL_OUTPUT);
548 flushDeferredText();
549 final int textLength = myEditor.getDocument().getTextLength();
550 addHyperlink(textLength - hyperlinkText.length(), textLength, null, info, getHyperlinkAttributes());
553 private static TextAttributes getHyperlinkAttributes() {
554 return EditorColorsManager.getInstance().getGlobalScheme().getAttributes(CodeInsightColors.HYPERLINK_ATTRIBUTES);
557 private static TextAttributes getFollowedHyperlinkAttributes() {
558 return EditorColorsManager.getInstance().getGlobalScheme().getAttributes(CodeInsightColors.FOLLOWED_HYPERLINK_ATTRIBUTES);
561 private Editor createEditor() {
562 return ApplicationManager.getApplication().runReadAction(new Computable<Editor>() {
563 public Editor compute() {
564 return doCreateEditor();
569 private Editor doCreateEditor() {
570 final EditorEx editor = createRealEditor();
571 editor.addEditorMouseListener(new EditorPopupHandler() {
572 public void invokePopup(final EditorMouseEvent event) {
573 final MouseEvent mouseEvent = event.getMouseEvent();
574 popupInvoked(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
579 final int bufferSize = USE_CYCLIC_BUFFER ? CYCLIC_BUFFER_SIZE : 0;
580 editor.getDocument().setCyclicBufferSize(bufferSize);
582 editor.putUserData(CONSOLE_VIEW_IN_EDITOR_VIEW, this);
584 editor.getContentComponent().addMouseMotionListener(
585 new MouseMotionAdapter(){
586 public void mouseMoved(final MouseEvent e){
587 final HyperlinkInfo info = getHyperlinkInfoByPoint(e.getPoint());
588 if (info != null){
589 editor.getContentComponent().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
591 else{
592 editor.getContentComponent().setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
597 return editor;
600 protected EditorEx createRealEditor() {
601 final EditorFactoryImpl document = (EditorFactoryImpl) EditorFactory.getInstance();
602 final Document editorDocument = document.createDocument(true);
603 editorDocument.addDocumentListener(new DocumentListener() {
604 public void beforeDocumentChange(DocumentEvent event) {
607 public void documentChanged(DocumentEvent event) {
608 if (myFileType != null) {
609 highlightUserTokens();
613 final EditorEx editor = (EditorEx)document.createViewer(editorDocument, myProject);
614 final EditorHighlighter highlighter = new MyHighlighter();
615 editor.setHighlighter(highlighter);
617 final EditorSettings editorSettings = editor.getSettings();
618 editorSettings.setLineMarkerAreaShown(false);
619 editorSettings.setLineNumbersShown(false);
620 editorSettings.setFoldingOutlineShown(false);
621 editorSettings.setAdditionalPageAtBottom(false);
622 editorSettings.setAdditionalColumnsCount(0);
623 editorSettings.setAdditionalLinesCount(0);
625 final EditorColorsScheme scheme = editor.getColorsScheme();
626 editor.setBackgroundColor(scheme.getColor(ConsoleViewContentType.CONSOLE_BACKGROUND_KEY));
627 scheme.setColor(EditorColors.CARET_ROW_COLOR, null);
628 scheme.setColor(EditorColors.RIGHT_MARGIN_COLOR, null);
630 editor.addEditorMouseListener(new EditorMouseAdapter() {
631 public void mouseReleased(final EditorMouseEvent e) {
632 final MouseEvent mouseEvent = e.getMouseEvent();
633 if (!mouseEvent.isPopupTrigger()) {
634 navigate(e);
639 final ConsoleViewImpl consoleView = this;
640 editor.getContentComponent().addKeyListener(new KeyListener() {
641 private int historyPosition = myHistory.size();
643 public void keyTyped(KeyEvent e) {
647 public void keyPressed(KeyEvent e) {
650 public void keyReleased(KeyEvent e) {
651 if (e.isAltDown() && !e.isControlDown() && !e.isMetaDown() && !e.isShiftDown()) {
652 if (e.getKeyCode() == KeyEvent.VK_UP) {
653 historyPosition--;
654 if (historyPosition < 0) historyPosition = 0;
655 replaceString();
656 e.consume();
658 else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
659 historyPosition++;
660 if (historyPosition > myHistory.size()) historyPosition = myHistory.size();
661 replaceString();
662 e.consume();
665 else {
666 historyPosition = myHistory.size();
670 private void replaceString() {
671 final String str;
673 if (myHistory.size() == historyPosition) str = "";
674 else str = myHistory.get(historyPosition);
675 synchronized (LOCK) {
676 if (myTokens.isEmpty()) return;
677 final TokenInfo info = myTokens.get(myTokens.size() - 1);
678 if (info.contentType != ConsoleViewContentType.USER_INPUT) {
679 consoleView.insertUserText(str, 0);
681 else {
682 consoleView.replaceUserText(str, info.startOffset, info.endOffset);
688 setEditorUpActions(editor);
689 return editor;
692 private void highlightUserTokens() {
693 if (myTokens.isEmpty()) return;
694 final TokenInfo token = myTokens.get(myTokens.size() - 1);
695 if (token.contentType == ConsoleViewContentType.USER_INPUT) {
696 String text = myEditor.getDocument().getText().substring(token.startOffset, token.endOffset);
697 PsiFile file = PsiFileFactory.getInstance(myProject).
698 createFileFromText("dummy", myFileType, text, LocalTimeCounter.currentTime(), true);
699 Document document = PsiDocumentManager.getInstance(myProject).getDocument(file);
700 assert document != null;
701 Editor editor = EditorFactory.getInstance().createEditor(document, myProject, myFileType, false);
702 try {
703 RangeHighlighter[] allHighlighters = myEditor.getMarkupModel().getAllHighlighters();
704 for (RangeHighlighter highlighter : allHighlighters) {
705 if (highlighter.getStartOffset() >= token.startOffset) {
706 myEditor.getMarkupModel().removeHighlighter(highlighter);
709 HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(0);
710 while (!iterator.atEnd()) {
711 myEditor.getMarkupModel().addRangeHighlighter(iterator.getStart() + token.startOffset, iterator.getEnd() + token.startOffset, HighlighterLayer.SYNTAX,
712 iterator.getTextAttributes(),
713 HighlighterTargetArea.EXACT_RANGE);
714 iterator.advance();
717 finally {
718 EditorFactory.getInstance().releaseEditor(editor);
723 private static void setEditorUpActions(final Editor editor) {
724 new EnterHandler().registerCustomShortcutSet(CommonShortcuts.ENTER, editor.getContentComponent());
725 registerActionHandler(editor, IdeActions.ACTION_EDITOR_PASTE, new PasteHandler());
726 registerActionHandler(editor, IdeActions.ACTION_EDITOR_BACKSPACE, new BackSpaceHandler());
727 registerActionHandler(editor, IdeActions.ACTION_EDITOR_DELETE, new DeleteHandler());
730 private static void registerActionHandler(final Editor editor, final String actionId, final AnAction action) {
731 final Keymap keymap=KeymapManager.getInstance().getActiveKeymap();
732 final Shortcut[] shortcuts = keymap.getShortcuts(actionId);
733 action.registerCustomShortcutSet(new CustomShortcutSet(shortcuts), editor.getContentComponent());
736 private void popupInvoked(final Component component, final int x, final int y){
737 final DefaultActionGroup group = new DefaultActionGroup();
738 group.add(new ClearAllAction());
739 group.add(new CopyAction());
740 group.addSeparator();
741 final ActionManager actionManager = ActionManager.getInstance();
742 group.add(actionManager.getAction(DiffActions.COMPARE_WITH_CLIPBOARD));
743 final ActionPopupMenu menu = actionManager.createActionPopupMenu(ActionPlaces.UNKNOWN, group);
744 menu.getComponent().show(component, x, y);
747 private void navigate(final EditorMouseEvent event){
748 if (event.getMouseEvent().isPopupTrigger()) return;
749 final Point p = event.getMouseEvent().getPoint();
750 final HyperlinkInfo info = getHyperlinkInfoByPoint(p);
751 if (info != null){
752 info.navigate(myProject);
753 linkFollowed(info);
757 private static final Key<TextAttributes> OLD_HYPERLINK_TEXT_ATTRIBUTES = Key.create("OLD_HYPERLINK_TEXT_ATTRIBUTES");
758 private void linkFollowed(final HyperlinkInfo info) {
759 MarkupModelEx markupModel = (MarkupModelEx)myEditor.getMarkupModel();
760 for (Map.Entry<RangeHighlighter,HyperlinkInfo> entry : myHyperlinks.getRanges().entrySet()) {
761 RangeHighlighter range = entry.getKey();
762 TextAttributes oldAttr = range.getUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES);
763 if (oldAttr != null) {
764 markupModel.setRangeHighlighterAttributes(range, oldAttr);
765 range.putUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES, null);
767 if (entry.getValue() == info) {
768 TextAttributes oldAttributes = range.getTextAttributes();
769 range.putUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES, oldAttributes);
770 TextAttributes attributes = getFollowedHyperlinkAttributes().clone();
771 assert oldAttributes != null;
772 attributes.setFontType(oldAttributes.getFontType());
773 attributes.setEffectType(oldAttributes.getEffectType());
774 attributes.setEffectColor(oldAttributes.getEffectColor());
775 attributes.setForegroundColor(oldAttributes.getForegroundColor());
776 markupModel.setRangeHighlighterAttributes(range, attributes);
779 //refresh highlighter text attributes
780 RangeHighlighter dummy = markupModel.addRangeHighlighter(0, 0, HYPERLINK_LAYER, getHyperlinkAttributes(), HighlighterTargetArea.EXACT_RANGE);
781 markupModel.removeHighlighter(dummy);
784 private HyperlinkInfo getHyperlinkInfoByPoint(final Point p){
785 if (myEditor == null) return null;
786 final LogicalPosition pos = myEditor.xyToLogicalPosition(new Point(p.x, p.y));
787 return getHyperlinkInfoByLineAndCol(pos.line, pos.column);
790 private HyperlinkInfo getHyperlinkInfoByLineAndCol(final int line, final int col) {
791 final int offset = myEditor.logicalPositionToOffset(new LogicalPosition(line, col));
792 return myHyperlinks.getHyperlinkAt(offset);
795 private void highlightHyperlinks(final int line1, final int line2){
796 ApplicationManager.getApplication().assertIsDispatchThread();
797 PsiDocumentManager.getInstance(myProject).commitAllDocuments();
798 final Document document = myEditor.getDocument();
799 final CharSequence chars = document.getCharsSequence();
800 final TextAttributes hyperlinkAttributes = getHyperlinkAttributes();
802 for(int line = line1; line <= line2; line++) {
803 if (line < 0) continue;
804 final int startOffset = document.getLineStartOffset(line);
805 int endOffset = document.getLineEndOffset(line);
806 if (endOffset < document.getTextLength()){
807 endOffset++; // add '\n'
809 final String text = chars.subSequence(startOffset, endOffset).toString();
810 Filter.Result result = myCustomFilter.applyFilter(text, endOffset);
811 if (result == null) {
812 result = myPredefinedMessageFilter.applyFilter(text, endOffset);
814 if (result != null){
815 final int highlightStartOffset = result.highlightStartOffset;
816 final int highlightEndOffset = result.highlightEndOffset;
817 final HyperlinkInfo hyperlinkInfo = result.hyperlinkInfo;
818 addHyperlink(highlightStartOffset, highlightEndOffset, result.highlightAttributes, hyperlinkInfo, hyperlinkAttributes);
823 private void addHyperlink(final int highlightStartOffset,
824 final int highlightEndOffset,
825 final TextAttributes highlightAttributes,
826 final HyperlinkInfo hyperlinkInfo,
827 final TextAttributes hyperlinkAttributes) {
828 TextAttributes textAttributes = highlightAttributes != null ? highlightAttributes : hyperlinkAttributes;
829 final RangeHighlighter highlighter = myEditor.getMarkupModel().addRangeHighlighter(highlightStartOffset,
830 highlightEndOffset,
831 HYPERLINK_LAYER,
832 textAttributes,
833 HighlighterTargetArea.EXACT_RANGE);
834 myHyperlinks.add(highlighter, hyperlinkInfo);
837 private class ClearAllAction extends AnAction implements DumbAware {
838 private ClearAllAction(){
839 super(ExecutionBundle.message("clear.all.from.console.action.name"));
842 public void actionPerformed(final AnActionEvent e){
843 clear();
847 private class CopyAction extends AnAction implements DumbAware {
848 private CopyAction(){
849 super(myEditor != null && myEditor.getSelectionModel().hasSelection() ? ExecutionBundle.message("copy.selected.content.action.name") : ExecutionBundle.message("copy.content.action.name"));
852 public void actionPerformed(final AnActionEvent e){
853 if (myEditor == null) return;
854 if (myEditor.getSelectionModel().hasSelection()){
855 myEditor.getSelectionModel().copySelectionToClipboard();
857 else{
858 myEditor.getSelectionModel().setSelection(0, myEditor.getDocument().getTextLength());
859 myEditor.getSelectionModel().copySelectionToClipboard();
860 myEditor.getSelectionModel().removeSelection();
865 private class MyHighlighter extends DocumentAdapter implements EditorHighlighter {
866 private HighlighterClient myEditor;
868 public HighlighterIterator createIterator(final int startOffset) {
869 final int startIndex = findTokenInfoIndexByOffset(startOffset);
871 return new HighlighterIterator(){
872 private int myIndex = startIndex;
874 public TextAttributes getTextAttributes() {
875 if (myFileType != null && getTokenInfo().contentType == ConsoleViewContentType.USER_INPUT) {
876 return ConsoleViewContentType.NORMAL_OUTPUT.getAttributes();
878 return getTokenInfo() == null ? null : getTokenInfo().attributes;
881 public int getStart() {
882 return getTokenInfo() == null ? 0 : getTokenInfo().startOffset;
885 public int getEnd() {
886 return getTokenInfo() == null ? 0 : getTokenInfo().endOffset;
889 public IElementType getTokenType() {
890 return null;
893 public void advance() {
894 myIndex++;
897 public void retreat() {
898 myIndex--;
901 public boolean atEnd() {
902 return myIndex < 0 || myIndex >= myTokens.size();
905 public Document getDocument() {
906 return myEditor.getDocument();
909 private TokenInfo getTokenInfo() {
910 return myTokens.get(myIndex);
915 public void setText(final CharSequence text) {
918 public void setEditor(final HighlighterClient editor) {
919 LOG.assertTrue(myEditor == null, "Highlighters cannot be reused with different editors");
920 myEditor = editor;
923 public void setColorScheme(EditorColorsScheme scheme) {
927 private int findTokenInfoIndexByOffset(final int offset) {
928 int low = 0;
929 int high = myTokens.size() - 1;
931 while(low <= high){
932 final int mid = (low + high) / 2;
933 final TokenInfo midVal = myTokens.get(mid);
934 if (offset < midVal.startOffset){
935 high = mid - 1;
937 else if (offset >= midVal.endOffset){
938 low = mid + 1;
940 else{
941 return mid;
944 return myTokens.size();
947 private static class MyTypedHandler implements TypedActionHandler {
948 private final TypedActionHandler myOriginalHandler;
950 private MyTypedHandler(final TypedActionHandler originalAction) {
951 myOriginalHandler = originalAction;
954 public void execute(@NotNull final Editor editor, final char charTyped, @NotNull final DataContext dataContext) {
955 final ConsoleViewImpl consoleView = editor.getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW);
956 if (consoleView == null || !consoleView.myState.isRunning() || consoleView.isViewer){
957 myOriginalHandler.execute(editor, charTyped, dataContext);
959 else{
960 final String s = String.valueOf(charTyped);
961 SelectionModel selectionModel = editor.getSelectionModel();
962 if (selectionModel.hasSelection()) {
963 consoleView.replaceUserText(s, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
964 } else {
965 consoleView.insertUserText(s, editor.getCaretModel().getOffset());
971 private abstract static class ConsoleAction extends AnAction implements DumbAware {
972 public void actionPerformed(final AnActionEvent e) {
973 final DataContext context = e.getDataContext();
974 final ConsoleViewImpl console = getRunningConsole(context);
975 execute(console, context);
978 protected abstract void execute(ConsoleViewImpl console, final DataContext context);
980 public void update(final AnActionEvent e) {
981 final ConsoleViewImpl console = getRunningConsole(e.getDataContext());
982 e.getPresentation().setEnabled(console != null);
985 @Nullable
986 private static ConsoleViewImpl getRunningConsole(final DataContext context) {
987 final Editor editor = PlatformDataKeys.EDITOR.getData(context);
988 if (editor != null) {
989 final ConsoleViewImpl console = editor.getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW);
990 if (console != null && console.myState.isRunning()) {
991 return console;
994 return null;
998 private static class EnterHandler extends ConsoleAction {
999 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1000 synchronized (consoleView.LOCK) {
1001 String str = consoleView.myDeferredUserInput.toString();
1002 if (StringUtil.isNotEmpty(str)) {
1003 consoleView.myHistory.remove(str);
1004 consoleView.myHistory.add(str);
1005 if (consoleView.myHistory.size() > consoleView.myHistorySize) consoleView.myHistory.remove(0);
1007 for (ConsoleInputListener listener : consoleView.myConsoleInputListeners) {
1008 listener.textEntered(str);
1011 consoleView.print("\n", ConsoleViewContentType.USER_INPUT);
1012 consoleView.flushDeferredText();
1013 final Editor editor = consoleView.myEditor;
1014 editor.getCaretModel().moveToOffset(editor.getDocument().getTextLength());
1015 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1019 private static class PasteHandler extends ConsoleAction {
1020 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1021 final Transferable content = CopyPasteManager.getInstance().getContents();
1022 if (content == null) return;
1023 String s = null;
1024 try {
1025 s = (String)content.getTransferData(DataFlavor.stringFlavor);
1027 catch(Exception e) {
1028 consoleView.getToolkit().beep();
1030 if (s == null) return;
1031 Editor editor = consoleView.myEditor;
1032 SelectionModel selectionModel = editor.getSelectionModel();
1033 if (selectionModel.hasSelection()) {
1034 consoleView.replaceUserText(s, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
1035 } else {
1036 consoleView.insertUserText(s, editor.getCaretModel().getOffset());
1041 private static class BackSpaceHandler extends ConsoleAction {
1042 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1043 final Editor editor = consoleView.myEditor;
1045 if (IncrementalSearchHandler.isHintVisible(editor)) {
1046 getDefaultActionHandler().execute(editor, context);
1047 return;
1050 final Document document = editor.getDocument();
1051 final int length = document.getTextLength();
1052 if (length == 0) {
1053 return;
1056 SelectionModel selectionModel = editor.getSelectionModel();
1057 if (selectionModel.hasSelection()) {
1058 consoleView.deleteUserText(selectionModel.getSelectionStart(),
1059 selectionModel.getSelectionEnd() - selectionModel.getSelectionStart());
1060 } else if (editor.getCaretModel().getOffset() > 0) {
1061 consoleView.deleteUserText(editor.getCaretModel().getOffset() - 1, 1);
1065 private static EditorActionHandler getDefaultActionHandler() {
1066 return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE);
1070 private static class DeleteHandler extends ConsoleAction {
1071 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1072 final Editor editor = consoleView.myEditor;
1074 if (IncrementalSearchHandler.isHintVisible(editor)) {
1075 getDefaultActionHandler().execute(editor, context);
1076 return;
1079 final Document document = editor.getDocument();
1080 final int length = document.getTextLength();
1081 if (length == 0) {
1082 return;
1085 SelectionModel selectionModel = editor.getSelectionModel();
1086 if (selectionModel.hasSelection()) {
1087 consoleView.deleteUserText(selectionModel.getSelectionStart(),
1088 selectionModel.getSelectionEnd() - selectionModel.getSelectionStart());
1089 } else {
1090 consoleView.deleteUserText(editor.getCaretModel().getOffset(), 1);
1094 private static EditorActionHandler getDefaultActionHandler() {
1095 return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE);
1099 private static class Hyperlinks {
1100 private static final int NO_INDEX = Integer.MIN_VALUE;
1101 private final Map<RangeHighlighter,HyperlinkInfo> myHighlighterToMessageInfoMap = new HashMap<RangeHighlighter, HyperlinkInfo>();
1102 private int myLastIndex = NO_INDEX;
1104 public void clear() {
1105 myHighlighterToMessageInfoMap.clear();
1106 myLastIndex = NO_INDEX;
1109 public HyperlinkInfo getHyperlinkAt(final int offset) {
1110 for (final RangeHighlighter highlighter : myHighlighterToMessageInfoMap.keySet()) {
1111 if (highlighter.isValid() && containsOffset(offset, highlighter)) {
1112 return myHighlighterToMessageInfoMap.get(highlighter);
1115 return null;
1118 private static boolean containsOffset(final int offset, final RangeHighlighter highlighter) {
1119 return highlighter.getStartOffset() <= offset && offset <= highlighter.getEndOffset();
1122 public void add(final RangeHighlighter highlighter, final HyperlinkInfo hyperlinkInfo) {
1123 myHighlighterToMessageInfoMap.put(highlighter, hyperlinkInfo);
1124 if (myLastIndex != NO_INDEX && containsOffset(myLastIndex, highlighter)) myLastIndex = NO_INDEX;
1127 private Map<RangeHighlighter,HyperlinkInfo> getRanges() {
1128 return myHighlighterToMessageInfoMap;
1132 public JComponent getPreferredFocusableComponent() {
1133 //ensure editor created
1134 getComponent();
1135 return myEditor.getContentComponent();
1139 // navigate up/down in stack trace
1140 public boolean hasNextOccurence() {
1141 return next(1, false) != null;
1144 public boolean hasPreviousOccurence() {
1145 return next(-1, false) != null;
1148 public OccurenceInfo goNextOccurence() {
1149 return next(1, true);
1152 private OccurenceInfo next(final int delta, boolean doMove) {
1153 List<RangeHighlighter> ranges = new ArrayList<RangeHighlighter>(myHyperlinks.getRanges().keySet());
1154 Collections.sort(ranges, new Comparator<RangeHighlighter>() {
1155 public int compare(final RangeHighlighter o1, final RangeHighlighter o2) {
1156 return o1.getStartOffset() - o2.getStartOffset();
1159 int i;
1160 for (i = 0; i<ranges.size(); i++) {
1161 RangeHighlighter range = ranges.get(i);
1162 if (range.getUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES) != null) {
1163 break;
1166 int newIndex = ranges.isEmpty() ? -1 : i == ranges.size() ? 0 : (i + delta + ranges.size()) % ranges.size();
1167 RangeHighlighter next = newIndex < ranges.size() && newIndex >= 0 ? ranges.get(newIndex) : null;
1168 if (next == null) return null;
1169 if (doMove) {
1170 scrollTo(next.getStartOffset());
1172 final HyperlinkInfo hyperlinkInfo = myHyperlinks.getRanges().get(next);
1173 return new OccurenceInfo(new Navigatable() {
1174 public void navigate(final boolean requestFocus) {
1175 hyperlinkInfo.navigate(myProject);
1176 linkFollowed(hyperlinkInfo);
1179 public boolean canNavigate() {
1180 return true;
1183 public boolean canNavigateToSource() {
1184 return true;
1186 }, i, ranges.size());
1189 public OccurenceInfo goPreviousOccurence() {
1190 return next(-1, true);
1193 public String getNextOccurenceActionName() {
1194 return ExecutionBundle.message("down.the.stack.trace");
1197 public String getPreviousOccurenceActionName() {
1198 return ExecutionBundle.message("up.the.stack.trace");
1201 public void addCustomConsoleAction(@NotNull AnAction action) {
1202 customActions.add(action);
1205 @NotNull
1206 public AnAction[] createConsoleActions() {
1207 //Initializing prev and next occurrences actions
1208 CommonActionsManager actionsManager = CommonActionsManager.getInstance();
1209 AnAction prevAction = actionsManager.createPrevOccurenceAction(this);
1210 prevAction.getTemplatePresentation().setText(getPreviousOccurenceActionName());
1211 AnAction nextAction = actionsManager.createNextOccurenceAction(this);
1212 nextAction.getTemplatePresentation().setText(getNextOccurenceActionName());
1213 //Initializing custom actions
1214 AnAction[] consoleActions = new AnAction[2 + customActions.size()];
1215 consoleActions[0] = prevAction;
1216 consoleActions[1] = nextAction;
1217 for (int i = 0; i < customActions.size(); ++i) {
1218 consoleActions[i + 2] = customActions.get(i);
1220 return consoleActions;
1223 public void setEditorEnabled(boolean enabled) {
1224 myEditor.getContentComponent().setEnabled(enabled);
1227 private void fireChange() {
1228 if (myDeferredTypes.isEmpty()) return;
1229 Collection<ConsoleViewContentType> types = Collections.unmodifiableCollection(myDeferredTypes);
1231 for (ChangeListener each : myListeners) {
1232 each.contentAdded(types);
1235 myDeferredTypes.clear();
1238 public void addChangeListener(final ChangeListener listener, final Disposable parent) {
1239 myListeners.add(listener);
1240 Disposer.register(parent, new Disposable() {
1241 public void dispose() {
1242 myListeners.remove(listener);
1248 * insert text to document
1249 * @param s inserted text
1250 * @param offset relativly to all document text
1252 private void insertUserText(final String s, int offset) {
1253 final ConsoleViewImpl consoleView = this;
1254 final Editor editor = consoleView.myEditor;
1255 final Document document = editor.getDocument();
1256 final int startOffset;
1258 synchronized (consoleView.LOCK) {
1259 if (consoleView.myTokens.isEmpty()) return;
1260 final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1261 if (info.contentType != ConsoleViewContentType.USER_INPUT && !s.contains("\n")) {
1262 consoleView.print(s, ConsoleViewContentType.USER_INPUT);
1263 consoleView.flushDeferredText();
1264 editor.getCaretModel().moveToOffset(document.getTextLength());
1265 editor.getSelectionModel().removeSelection();
1266 return;
1267 } else if (info.contentType != ConsoleViewContentType.USER_INPUT) {
1268 insertUserText("temp", offset);
1269 final TokenInfo newInfo = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1270 replaceUserText(s, newInfo.startOffset, newInfo.endOffset);
1271 return;
1273 int charCountToAdd;
1276 if (offset > info.endOffset) {
1277 startOffset = info.endOffset;
1279 else if (offset < info.startOffset) {
1280 startOffset = info.startOffset;
1281 } else {
1282 startOffset = offset;
1284 charCountToAdd = s.length();
1286 if (consoleView.myDeferredUserInput.length() < info.endOffset - info.startOffset) return; //user was quick
1288 consoleView.myDeferredUserInput.insert(startOffset - info.startOffset, s);
1290 info.endOffset += charCountToAdd;
1291 consoleView.myContentSize += charCountToAdd;
1294 document.insertString(startOffset, s);
1295 editor.getCaretModel().moveToOffset(startOffset + s.length());
1296 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1300 * replace text
1301 * @param s text for replace
1302 * @param start relativly to all document text
1303 * @param end relativly to all document text
1305 private void replaceUserText(final String s, int start, int end) {
1306 if (start == end) {
1307 insertUserText(s, start);
1308 return;
1310 final ConsoleViewImpl consoleView = this;
1311 final Editor editor = consoleView.myEditor;
1312 final Document document = editor.getDocument();
1313 final int startOffset;
1314 final int endOffset;
1316 synchronized (consoleView.LOCK) {
1317 if (consoleView.myTokens.isEmpty()) return;
1318 final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1319 if (info.contentType != ConsoleViewContentType.USER_INPUT) {
1320 consoleView.print(s, ConsoleViewContentType.USER_INPUT);
1321 consoleView.flushDeferredText();
1322 editor.getCaretModel().moveToOffset(document.getTextLength());
1323 editor.getSelectionModel().removeSelection();
1324 return;
1326 if (consoleView.myDeferredUserInput.length() == 0) return;
1327 int charCountToReplace;
1329 startOffset = getStartOffset(start, info);
1330 endOffset = getEndOffset(end, info);
1332 if (startOffset == -1 ||
1333 endOffset == -1 ||
1334 endOffset <= startOffset) {
1335 editor.getSelectionModel().removeSelection();
1336 editor.getCaretModel().moveToOffset(start);
1337 return;
1339 charCountToReplace = s.length() - endOffset + startOffset;
1341 if (consoleView.myDeferredUserInput.length() < info.endOffset - info.startOffset) return; //user was quick
1343 consoleView.myDeferredUserInput.replace(startOffset - info.startOffset, endOffset - info.startOffset, s);
1345 info.endOffset += charCountToReplace;
1346 if (info.startOffset == info.endOffset) {
1347 consoleView.myTokens.remove(consoleView.myTokens.size() - 1);
1349 consoleView.myContentSize += charCountToReplace;
1352 document.replaceString(startOffset, endOffset, s);
1353 editor.getCaretModel().moveToOffset(startOffset + s.length());
1354 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1355 editor.getSelectionModel().removeSelection();
1359 * delete text
1360 * @param offset relativly to all document text
1361 * @param length lenght of deleted text
1363 private void deleteUserText(int offset, int length) {
1364 ConsoleViewImpl consoleView = this;
1365 final Editor editor = consoleView.myEditor;
1366 final Document document = editor.getDocument();
1367 final int startOffset;
1368 final int endOffset;
1370 synchronized (consoleView.LOCK) {
1371 if (consoleView.myTokens.isEmpty()) return;
1372 final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1373 if (info.contentType != ConsoleViewContentType.USER_INPUT) return;
1374 if (consoleView.myDeferredUserInput.length() == 0) return;
1375 int charCountToDelete;
1377 startOffset = getStartOffset(offset, info);
1378 endOffset = getEndOffset(offset + length, info);
1379 if (startOffset == -1 ||
1380 endOffset == -1 ||
1381 endOffset <= startOffset) {
1382 editor.getSelectionModel().removeSelection();
1383 editor.getCaretModel().moveToOffset(offset);
1384 return;
1387 consoleView.myDeferredUserInput.delete(startOffset - info.startOffset, endOffset - info.startOffset);
1388 charCountToDelete = endOffset - startOffset;
1390 info.endOffset -= charCountToDelete;
1391 if (info.startOffset == info.endOffset) {
1392 consoleView.myTokens.remove(consoleView.myTokens.size() - 1);
1394 consoleView.myContentSize -= charCountToDelete;
1397 document.deleteString(startOffset, endOffset);
1398 editor.getCaretModel().moveToOffset(startOffset);
1399 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1400 editor.getSelectionModel().removeSelection();
1403 //util methods for add, replace, delete methods
1404 private static int getStartOffset(int offset, TokenInfo info) {
1405 int startOffset;
1406 if (offset >= info.startOffset && offset < info.endOffset) {
1407 startOffset = offset;
1408 } else if (offset < info.startOffset) {
1409 startOffset = info.startOffset;
1410 } else {
1411 startOffset = -1;
1413 return startOffset;
1416 private static int getEndOffset(int offset, TokenInfo info) {
1417 int endOffset;
1418 if (offset > info.endOffset) {
1419 endOffset = info.endOffset;
1420 } else if (offset <= info.startOffset) {
1421 endOffset = -1;
1422 } else {
1423 endOffset = offset;
1425 return endOffset;