don't require write access for console view updates
[fedora-idea.git] / platform / lang-impl / src / com / intellij / execution / impl / ConsoleViewImpl.java
blob8b315495d6643778f12a6732735e5b6a98aa366d
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 myMessageFilter;
186 private ArrayList<String> myHistory = new ArrayList<String>();
187 private int myHistorySize = 20;
189 private ArrayList<ConsoleInputListener> myConsoleInputListeners = new ArrayList<ConsoleInputListener>();
191 public void addConsoleUserInputLestener(ConsoleInputListener consoleInputListener) {
192 myConsoleInputListeners.add(consoleInputListener);
196 * By default history works for one session. If
197 * you want to import previous session, set it up here.
198 * @param history where you can save history
200 public void importHistory(Collection<String> history) {
201 this.myHistory.clear();
202 this.myHistory.addAll(history);
203 while (this.myHistory.size() > myHistorySize) {
204 this.myHistory.remove(0);
208 public List<String> getHistory() {
209 return Collections.unmodifiableList(myHistory);
212 public void setHistorySize(int historySize) {
213 this.myHistorySize = historySize;
216 public int getHistorySize() {
217 return myHistorySize;
220 private FileType myFileType;
223 * Use it for custom highlighting for user text.
224 * This will be highlighted as appropriate file to this file type.
225 * @param fileType according to which use highlighting
227 public void setFileType(FileType fileType) {
228 myFileType = fileType;
231 public ConsoleViewImpl(final Project project, boolean viewer) {
232 this(project, viewer, null);
235 public ConsoleViewImpl(final Project project, boolean viewer, FileType fileType) {
236 super(new BorderLayout());
237 isViewer = viewer;
238 myPsiDisposedCheck = new DisposedPsiManagerCheck(project);
239 myProject = project;
240 myFileType = fileType;
242 myMessageFilter = new CompositeFilter(project);
243 final ConsoleFilterProvider[] filterProviders = Extensions.getExtensions(ConsoleFilterProvider.FILTER_PROVIDERS);
244 for (ConsoleFilterProvider filterProvider : filterProviders) {
245 final Filter[] defaultFilters = filterProvider.getDefaultFilters(project);
246 for (Filter filter : defaultFilters) {
247 addMessageFilter(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 myMessageFilter.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 if (myMessageFilter != null){
786 ApplicationManager.getApplication().assertIsDispatchThread();
787 PsiDocumentManager.getInstance(myProject).commitAllDocuments();
788 final Document document = myEditor.getDocument();
789 final CharSequence chars = document.getCharsSequence();
790 final TextAttributes hyperlinkAttributes = getHyperlinkAttributes();
792 for(int line = line1; line <= line2; line++) {
793 if (line < 0) continue;
794 final int startOffset = document.getLineStartOffset(line);
795 int endOffset = document.getLineEndOffset(line);
796 if (endOffset < document.getTextLength()){
797 endOffset++; // add '\n'
799 final String text = chars.subSequence(startOffset, endOffset).toString();
800 final Filter.Result result = myMessageFilter.applyFilter(text, endOffset);
801 if (result != null){
802 final int highlightStartOffset = result.highlightStartOffset;
803 final int highlightEndOffset = result.highlightEndOffset;
804 final HyperlinkInfo hyperlinkInfo = result.hyperlinkInfo;
805 addHyperlink(highlightStartOffset, highlightEndOffset, result.highlightAttributes, hyperlinkInfo, hyperlinkAttributes);
811 private void addHyperlink(final int highlightStartOffset,
812 final int highlightEndOffset,
813 final TextAttributes highlightAttributes,
814 final HyperlinkInfo hyperlinkInfo,
815 final TextAttributes hyperlinkAttributes) {
816 TextAttributes textAttributes = highlightAttributes != null ? highlightAttributes : hyperlinkAttributes;
817 final RangeHighlighter highlighter = myEditor.getMarkupModel().addRangeHighlighter(highlightStartOffset,
818 highlightEndOffset,
819 HYPERLINK_LAYER,
820 textAttributes,
821 HighlighterTargetArea.EXACT_RANGE);
822 myHyperlinks.add(highlighter, hyperlinkInfo);
825 private class ClearAllAction extends AnAction{
826 private ClearAllAction(){
827 super(ExecutionBundle.message("clear.all.from.console.action.name"));
830 public void actionPerformed(final AnActionEvent e){
831 clear();
835 private class CopyAction extends AnAction{
836 private CopyAction(){
837 super(myEditor != null && myEditor.getSelectionModel().hasSelection() ? ExecutionBundle.message("copy.selected.content.action.name") : ExecutionBundle.message("copy.content.action.name"));
840 public void actionPerformed(final AnActionEvent e){
841 if (myEditor == null) return;
842 if (myEditor.getSelectionModel().hasSelection()){
843 myEditor.getSelectionModel().copySelectionToClipboard();
845 else{
846 myEditor.getSelectionModel().setSelection(0, myEditor.getDocument().getTextLength());
847 myEditor.getSelectionModel().copySelectionToClipboard();
848 myEditor.getSelectionModel().removeSelection();
853 private class MyHighlighter extends DocumentAdapter implements EditorHighlighter {
854 private boolean myHasEditor;
856 public HighlighterIterator createIterator(final int startOffset) {
857 final int startIndex = findTokenInfoIndexByOffset(startOffset);
859 return new HighlighterIterator(){
860 private int myIndex = startIndex;
862 public TextAttributes getTextAttributes() {
863 if (myFileType != null && getTokenInfo().contentType == ConsoleViewContentType.USER_INPUT) {
864 return ConsoleViewContentType.NORMAL_OUTPUT.getAttributes();
866 return getTokenInfo() == null ? null : getTokenInfo().attributes;
869 public int getStart() {
870 return getTokenInfo() == null ? 0 : getTokenInfo().startOffset;
873 public int getEnd() {
874 return getTokenInfo() == null ? 0 : getTokenInfo().endOffset;
877 public IElementType getTokenType() {
878 return null;
881 public void advance() {
882 myIndex++;
885 public void retreat() {
886 myIndex--;
889 public boolean atEnd() {
890 return myIndex < 0 || myIndex >= myTokens.size();
893 private TokenInfo getTokenInfo() {
894 return myTokens.get(myIndex);
899 public void setText(final CharSequence text) {
902 public void setEditor(final HighlighterClient editor) {
903 LOG.assertTrue(!myHasEditor, "Highlighters cannot be reused with different editors");
904 myHasEditor = true;
907 public void setColorScheme(EditorColorsScheme scheme) {
911 private int findTokenInfoIndexByOffset(final int offset) {
912 int low = 0;
913 int high = myTokens.size() - 1;
915 while(low <= high){
916 final int mid = (low + high) / 2;
917 final TokenInfo midVal = myTokens.get(mid);
918 if (offset < midVal.startOffset){
919 high = mid - 1;
921 else if (offset >= midVal.endOffset){
922 low = mid + 1;
924 else{
925 return mid;
928 return myTokens.size();
931 private static class MyTypedHandler implements TypedActionHandler {
932 private final TypedActionHandler myOriginalHandler;
934 private MyTypedHandler(final TypedActionHandler originalAction) {
935 myOriginalHandler = originalAction;
938 public void execute(@NotNull final Editor editor, final char charTyped, @NotNull final DataContext dataContext) {
939 final ConsoleViewImpl consoleView = editor.getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW);
940 if (consoleView == null || !consoleView.myState.isRunning() || consoleView.isViewer){
941 myOriginalHandler.execute(editor, charTyped, dataContext);
943 else{
944 final String s = String.valueOf(charTyped);
945 SelectionModel selectionModel = editor.getSelectionModel();
946 if (selectionModel.hasSelection()) {
947 consoleView.replaceUserText(s, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
948 } else {
949 consoleView.insertUserText(s, editor.getCaretModel().getOffset());
955 private static final DataAccessor<ConsoleViewImpl> CONSOLE = new DataAccessor<ConsoleViewImpl>() {
956 public ConsoleViewImpl getImpl(final DataContext dataContext) throws NoDataException {
957 return DataAccessors.EDITOR.getNotNull(dataContext).getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW);
961 private static final Condition<ConsoleViewImpl> CONSOLE_IS_RUNNING = new Condition<ConsoleViewImpl>() {
962 public boolean value(final ConsoleViewImpl consoleView) {
963 return consoleView.myState.isRunning();
967 private static final DataAccessor<ConsoleViewImpl> RUNNINT_CONSOLE =DataAccessor.createConditionalAccessor(CONSOLE, CONSOLE_IS_RUNNING);
969 private abstract static class ConsoleAction extends AnAction implements DumbAware {
970 public void actionPerformed(final AnActionEvent e) {
971 final DataContext context = e.getDataContext();
972 final ConsoleViewImpl console = RUNNINT_CONSOLE.from(context);
973 execute(console, context);
976 protected abstract void execute(ConsoleViewImpl console, final DataContext context);
978 public void update(final AnActionEvent e) {
979 final ConsoleViewImpl console = RUNNINT_CONSOLE.from(e.getDataContext());
980 e.getPresentation().setEnabled(console != null);
984 private static class EnterHandler extends ConsoleAction {
985 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
986 synchronized (consoleView.LOCK) {
987 String str = consoleView.myDeferredUserInput.toString();
988 if (StringUtil.isNotEmpty(str)) {
989 consoleView.myHistory.remove(str);
990 consoleView.myHistory.add(str);
991 if (consoleView.myHistory.size() > consoleView.myHistorySize) consoleView.myHistory.remove(0);
993 for (ConsoleInputListener listener : consoleView.myConsoleInputListeners) {
994 listener.textEntered(str);
997 consoleView.print("\n", ConsoleViewContentType.USER_INPUT);
998 consoleView.flushDeferredText();
999 final Editor editor = consoleView.myEditor;
1000 editor.getCaretModel().moveToOffset(editor.getDocument().getTextLength());
1001 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1005 private static class PasteHandler extends ConsoleAction {
1006 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1007 final Transferable content = CopyPasteManager.getInstance().getContents();
1008 if (content == null) return;
1009 String s = null;
1010 try {
1011 s = (String)content.getTransferData(DataFlavor.stringFlavor);
1013 catch(Exception e) {
1014 consoleView.myEditor.getComponent().getToolkit().beep();
1016 if (s == null) return;
1017 Editor editor = consoleView.myEditor;
1018 SelectionModel selectionModel = editor.getSelectionModel();
1019 if (selectionModel.hasSelection()) {
1020 consoleView.replaceUserText(s, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
1021 } else {
1022 consoleView.insertUserText(s, editor.getCaretModel().getOffset());
1027 private static class BackSpaceHandler extends ConsoleAction {
1028 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1029 final Editor editor = consoleView.myEditor;
1031 if (IncrementalSearchHandler.isHintVisible(editor)) {
1032 getDefaultActionHandler().execute(editor, context);
1033 return;
1036 final Document document = editor.getDocument();
1037 final int length = document.getTextLength();
1038 if (length == 0) {
1039 return;
1042 SelectionModel selectionModel = editor.getSelectionModel();
1043 if (selectionModel.hasSelection()) {
1044 consoleView.deleteUserText(selectionModel.getSelectionStart(),
1045 selectionModel.getSelectionEnd() - selectionModel.getSelectionStart());
1046 } else if (editor.getCaretModel().getOffset() > 0) {
1047 consoleView.deleteUserText(editor.getCaretModel().getOffset() - 1, 1);
1051 private static EditorActionHandler getDefaultActionHandler() {
1052 return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE);
1056 private static class DeleteHandler extends ConsoleAction {
1057 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1058 final Editor editor = consoleView.myEditor;
1060 if (IncrementalSearchHandler.isHintVisible(editor)) {
1061 getDefaultActionHandler().execute(editor, context);
1062 return;
1065 final Document document = editor.getDocument();
1066 final int length = document.getTextLength();
1067 if (length == 0) {
1068 return;
1071 SelectionModel selectionModel = editor.getSelectionModel();
1072 if (selectionModel.hasSelection()) {
1073 consoleView.deleteUserText(selectionModel.getSelectionStart(),
1074 selectionModel.getSelectionEnd() - selectionModel.getSelectionStart());
1075 } else {
1076 consoleView.deleteUserText(editor.getCaretModel().getOffset(), 1);
1080 private static EditorActionHandler getDefaultActionHandler() {
1081 return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE);
1085 private static class Hyperlinks {
1086 private static final int NO_INDEX = Integer.MIN_VALUE;
1087 private final Map<RangeHighlighter,HyperlinkInfo> myHighlighterToMessageInfoMap = new HashMap<RangeHighlighter, HyperlinkInfo>();
1088 private int myLastIndex = NO_INDEX;
1090 public void clear() {
1091 myHighlighterToMessageInfoMap.clear();
1092 myLastIndex = NO_INDEX;
1095 public HyperlinkInfo getHyperlinkAt(final int offset) {
1096 for (final RangeHighlighter highlighter : myHighlighterToMessageInfoMap.keySet()) {
1097 if (highlighter.isValid() && containsOffset(offset, highlighter)) {
1098 return myHighlighterToMessageInfoMap.get(highlighter);
1101 return null;
1104 private static boolean containsOffset(final int offset, final RangeHighlighter highlighter) {
1105 return highlighter.getStartOffset() <= offset && offset <= highlighter.getEndOffset();
1108 public void add(final RangeHighlighter highlighter, final HyperlinkInfo hyperlinkInfo) {
1109 myHighlighterToMessageInfoMap.put(highlighter, hyperlinkInfo);
1110 if (myLastIndex != NO_INDEX && containsOffset(myLastIndex, highlighter)) myLastIndex = NO_INDEX;
1113 private Map<RangeHighlighter,HyperlinkInfo> getRanges() {
1114 return myHighlighterToMessageInfoMap;
1118 public JComponent getPreferredFocusableComponent() {
1119 //ensure editor created
1120 getComponent();
1121 return myEditor.getContentComponent();
1125 // navigate up/down in stack trace
1126 public boolean hasNextOccurence() {
1127 return next(1, false) != null;
1130 public boolean hasPreviousOccurence() {
1131 return next(-1, false) != null;
1134 public OccurenceInfo goNextOccurence() {
1135 return next(1, true);
1138 private OccurenceInfo next(final int delta, boolean doMove) {
1139 List<RangeHighlighter> ranges = new ArrayList<RangeHighlighter>(myHyperlinks.getRanges().keySet());
1140 Collections.sort(ranges, new Comparator<RangeHighlighter>() {
1141 public int compare(final RangeHighlighter o1, final RangeHighlighter o2) {
1142 return o1.getStartOffset() - o2.getStartOffset();
1145 int i;
1146 for (i = 0; i<ranges.size(); i++) {
1147 RangeHighlighter range = ranges.get(i);
1148 if (range.getUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES) != null) {
1149 break;
1152 int newIndex = ranges.isEmpty() ? -1 : i == ranges.size() ? 0 : (i + delta + ranges.size()) % ranges.size();
1153 RangeHighlighter next = newIndex < ranges.size() && newIndex >= 0 ? ranges.get(newIndex) : null;
1154 if (next == null) return null;
1155 if (doMove) {
1156 scrollTo(next.getStartOffset());
1158 final HyperlinkInfo hyperlinkInfo = myHyperlinks.getRanges().get(next);
1159 return new OccurenceInfo(new Navigatable() {
1160 public void navigate(final boolean requestFocus) {
1161 hyperlinkInfo.navigate(myProject);
1162 linkFollowed(hyperlinkInfo);
1165 public boolean canNavigate() {
1166 return true;
1169 public boolean canNavigateToSource() {
1170 return true;
1172 }, i, ranges.size());
1175 public OccurenceInfo goPreviousOccurence() {
1176 return next(-1, true);
1179 public String getNextOccurenceActionName() {
1180 return ExecutionBundle.message("down.the.stack.trace");
1183 public String getPreviousOccurenceActionName() {
1184 return ExecutionBundle.message("up.the.stack.trace");
1187 public void addCustomConsoleAction(@NotNull AnAction action) {
1188 customActions.add(action);
1191 @NotNull
1192 public AnAction[] createConsoleActions() {
1193 //Initializing prev and next occurrences actions
1194 CommonActionsManager actionsManager = CommonActionsManager.getInstance();
1195 AnAction prevAction = actionsManager.createPrevOccurenceAction(this);
1196 prevAction.getTemplatePresentation().setText(getPreviousOccurenceActionName());
1197 AnAction nextAction = actionsManager.createNextOccurenceAction(this);
1198 nextAction.getTemplatePresentation().setText(getNextOccurenceActionName());
1199 //Initializing custom actions
1200 AnAction[] consoleActions = new AnAction[2 + customActions.size()];
1201 consoleActions[0] = prevAction;
1202 consoleActions[1] = nextAction;
1203 for (int i = 0; i < customActions.size(); ++i) {
1204 consoleActions[i + 2] = customActions.get(i);
1206 return consoleActions;
1209 public void setEditorEnabled(boolean enabled) {
1210 myEditor.getContentComponent().setEnabled(enabled);
1213 private void fireChange() {
1214 if (myDeferredTypes.isEmpty()) return;
1215 Collection<ConsoleViewContentType> types = Collections.unmodifiableCollection(myDeferredTypes);
1217 for (ChangeListener each : myListeners) {
1218 each.contentAdded(types);
1221 myDeferredTypes.clear();
1224 public void addChangeListener(final ChangeListener listener, final Disposable parent) {
1225 myListeners.add(listener);
1226 Disposer.register(parent, new Disposable() {
1227 public void dispose() {
1228 myListeners.remove(listener);
1234 * insert text to document
1235 * @param s inserted text
1236 * @param offset relativly to all document text
1238 private void insertUserText(final String s, int offset) {
1239 final ConsoleViewImpl consoleView = this;
1240 final Editor editor = consoleView.myEditor;
1241 final Document document = editor.getDocument();
1242 final int startOffset;
1244 synchronized (consoleView.LOCK) {
1245 if (consoleView.myTokens.isEmpty()) return;
1246 final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1247 if (info.contentType != ConsoleViewContentType.USER_INPUT && !s.contains("\n")) {
1248 consoleView.print(s, ConsoleViewContentType.USER_INPUT);
1249 consoleView.flushDeferredText();
1250 editor.getCaretModel().moveToOffset(document.getTextLength());
1251 editor.getSelectionModel().removeSelection();
1252 return;
1253 } else if (info.contentType != ConsoleViewContentType.USER_INPUT) {
1254 insertUserText("temp", offset);
1255 final TokenInfo newInfo = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1256 replaceUserText(s, newInfo.startOffset, newInfo.endOffset);
1257 return;
1259 int charCountToAdd;
1262 if (offset > info.endOffset) {
1263 startOffset = info.endOffset;
1265 else if (offset < info.startOffset) {
1266 startOffset = info.startOffset;
1267 } else {
1268 startOffset = offset;
1270 charCountToAdd = s.length();
1272 if (consoleView.myDeferredUserInput.length() < info.endOffset - info.startOffset) return; //user was quick
1274 consoleView.myDeferredUserInput.insert(startOffset - info.startOffset, s);
1276 info.endOffset += charCountToAdd;
1277 consoleView.myContentSize += charCountToAdd;
1280 document.insertString(startOffset, s);
1281 editor.getCaretModel().moveToOffset(startOffset + s.length());
1282 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1286 * replace text
1287 * @param s text for replace
1288 * @param start relativly to all document text
1289 * @param end relativly to all document text
1291 private void replaceUserText(final String s, int start, int end) {
1292 if (start == end) {
1293 insertUserText(s, start);
1294 return;
1296 final ConsoleViewImpl consoleView = this;
1297 final Editor editor = consoleView.myEditor;
1298 final Document document = editor.getDocument();
1299 final int startOffset;
1300 final int endOffset;
1302 synchronized (consoleView.LOCK) {
1303 if (consoleView.myTokens.isEmpty()) return;
1304 final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1305 if (info.contentType != ConsoleViewContentType.USER_INPUT) {
1306 consoleView.print(s, ConsoleViewContentType.USER_INPUT);
1307 consoleView.flushDeferredText();
1308 editor.getCaretModel().moveToOffset(document.getTextLength());
1309 editor.getSelectionModel().removeSelection();
1310 return;
1312 if (consoleView.myDeferredUserInput.length() == 0) return;
1313 int charCountToReplace;
1315 startOffset = getStartOffset(start, info);
1316 endOffset = getEndOffset(end, info);
1318 if (startOffset == -1 ||
1319 endOffset == -1 ||
1320 endOffset <= startOffset) {
1321 editor.getSelectionModel().removeSelection();
1322 editor.getCaretModel().moveToOffset(start);
1323 return;
1325 charCountToReplace = s.length() - endOffset + startOffset;
1327 if (consoleView.myDeferredUserInput.length() < info.endOffset - info.startOffset) return; //user was quick
1329 consoleView.myDeferredUserInput.replace(startOffset - info.startOffset, endOffset - info.startOffset, s);
1331 info.endOffset += charCountToReplace;
1332 if (info.startOffset == info.endOffset) {
1333 consoleView.myTokens.remove(consoleView.myTokens.size() - 1);
1335 consoleView.myContentSize += charCountToReplace;
1338 document.replaceString(startOffset, endOffset, s);
1339 editor.getCaretModel().moveToOffset(startOffset + s.length());
1340 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1341 editor.getSelectionModel().removeSelection();
1345 * delete text
1346 * @param offset relativly to all document text
1347 * @param length lenght of deleted text
1349 private void deleteUserText(int offset, int length) {
1350 ConsoleViewImpl consoleView = this;
1351 final Editor editor = consoleView.myEditor;
1352 final Document document = editor.getDocument();
1353 final int startOffset;
1354 final int endOffset;
1356 synchronized (consoleView.LOCK) {
1357 if (consoleView.myTokens.isEmpty()) return;
1358 final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1359 if (info.contentType != ConsoleViewContentType.USER_INPUT) return;
1360 if (consoleView.myDeferredUserInput.length() == 0) return;
1361 int charCountToDelete;
1363 startOffset = getStartOffset(offset, info);
1364 endOffset = getEndOffset(offset + length, info);
1365 if (startOffset == -1 ||
1366 endOffset == -1 ||
1367 endOffset <= startOffset) {
1368 editor.getSelectionModel().removeSelection();
1369 editor.getCaretModel().moveToOffset(offset);
1370 return;
1373 consoleView.myDeferredUserInput.delete(startOffset - info.startOffset, endOffset - info.startOffset);
1374 charCountToDelete = endOffset - startOffset;
1376 info.endOffset -= charCountToDelete;
1377 if (info.startOffset == info.endOffset) {
1378 consoleView.myTokens.remove(consoleView.myTokens.size() - 1);
1380 consoleView.myContentSize -= charCountToDelete;
1383 document.deleteString(startOffset, endOffset);
1384 editor.getCaretModel().moveToOffset(startOffset);
1385 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1386 editor.getSelectionModel().removeSelection();
1389 //util methods for add, replace, delete methods
1390 private static int getStartOffset(int offset, TokenInfo info) {
1391 int startOffset;
1392 if (offset >= info.startOffset && offset < info.endOffset) {
1393 startOffset = offset;
1394 } else if (offset < info.startOffset) {
1395 startOffset = info.startOffset;
1396 } else {
1397 startOffset = -1;
1399 return startOffset;
1402 private static int getEndOffset(int offset, TokenInfo info) {
1403 int endOffset;
1404 if (offset > info.endOffset) {
1405 endOffset = info.endOffset;
1406 } else if (offset <= info.startOffset) {
1407 endOffset = -1;
1408 } else {
1409 endOffset = offset;
1411 return endOffset;