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
;
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
;
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");
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;
115 return Integer
.parseInt(cycleBufferSizeProperty
) * 1024;
117 catch (NumberFormatException e
) {
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
>();
137 public Editor
getEditor() {
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() {
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());
238 myPsiDisposedCheck
= new DisposedPsiManagerCheck(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
;
264 if (USE_CYCLIC_BUFFER
) {
265 myDeferredOutput
= new StringBuffer(Math
.min(myDeferredOutput
.length(), CYCLIC_BUFFER_SIZE
));
268 myDeferredOutput
= new StringBuffer();
270 myDeferredTypes
.clear();
271 myDeferredUserInput
= new StringBuffer();
272 myHyperlinks
.clear();
274 if (myEditor
== null) return;
275 myEditor
.getMarkupModel().removeAllHighlighters();
276 document
= myEditor
.getDocument();
278 CommandProcessor
.getInstance().executeCommand(myProject
, new Runnable() {
280 document
.deleteString(0, document
.getTextLength());
282 }, null, DocCommandGroupId
.noneGroupId(document
));
285 public void scrollTo(final int offset
) {
286 assertIsDispatchThread();
288 if (myEditor
== null) return;
289 int moveOffset
= offset
;
290 if (USE_CYCLIC_BUFFER
&& moveOffset
>= myEditor
.getDocument().getTextLength()) {
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
;
304 requestFlushImmediately();
308 public boolean isOutputPaused() {
309 return myOutputPaused
;
312 public boolean hasDeferredOutput() {
314 return myDeferredOutput
.length() > 0;
318 public void performWhenNoDeferredOutput(final Runnable runnable
) {
319 //Q: implement in another way without timer?
320 if (!hasDeferredOutput()){
324 mySpareTimeAlarm
.addRequest(
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
;
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();
387 public void print(String s
, final ConsoleViewContentType contentType
) {
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
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() {
438 private void flushDeferredText() {
439 ApplicationManager
.getApplication().assertIsDispatchThread();
440 if (myProject
.isDisposed()) {
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
));
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() {
464 document
.insertString(document
.getTextLength(), text
);
465 synchronized (LOCK
) {
469 }, null, DocCommandGroupId
.noneGroupId(document
));
470 myPsiDisposedCheck
.performCheck();
471 final int newLineCount
= document
.getLineCount();
473 final int lineCount
= LineTokenizer
.calcLineCount(text
, true);
474 for (Iterator
<RangeHighlighter
> it
= myHyperlinks
.getRanges().keySet().iterator(); it
.hasNext();) {
475 if (!it
.next().isValid()) {
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;
498 myState
.sendUserInput(text
.substring(0, index
+ 1));
500 catch(IOException e
){
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) {
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()) {
519 return openFileDescriptor
;
522 if (DataConstants
.EDITOR
.equals(dataId
)) {
525 if (DataConstants
.HELP_ID
.equals(dataId
)) {
531 public void setHelpId(final String 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
);
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()){
616 editor
.getContentComponent().addMouseMotionListener(
617 new MouseMotionAdapter(){
618 public void mouseMoved(final MouseEvent e
){
619 final HyperlinkInfo info
= getHyperlinkInfoByPoint(e
.getPoint());
621 editor
.getContentComponent().setCursor(Cursor
.getPredefinedCursor(Cursor
.HAND_CURSOR
));
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
) {
645 if (historyPosition
< 0) historyPosition
= 0;
648 } else if (e
.getKeyCode() == KeyEvent
.VK_DOWN
) {
650 if (historyPosition
> myHistory
.size()) historyPosition
= myHistory
.size();
655 historyPosition
= myHistory
.size();
659 private void replaceString() {
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);
670 consoleView
.replaceUserText(str
, info
.startOffset
, info
.endOffset
);
676 setEditorUpActions(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);
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
);
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
);
741 info
.navigate(myProject
);
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
);
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
,
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
){
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();
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() {
881 public void advance() {
885 public void retreat() {
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");
907 public void setColorScheme(EditorColorsScheme scheme
) {
911 private int findTokenInfoIndexByOffset(final int offset
) {
913 int high
= myTokens
.size() - 1;
916 final int mid
= (low
+ high
) / 2;
917 final TokenInfo midVal
= myTokens
.get(mid
);
918 if (offset
< midVal
.startOffset
){
921 else if (offset
>= midVal
.endOffset
){
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
);
944 final String s
= String
.valueOf(charTyped
);
945 SelectionModel selectionModel
= editor
.getSelectionModel();
946 if (selectionModel
.hasSelection()) {
947 consoleView
.replaceUserText(s
, selectionModel
.getSelectionStart(), selectionModel
.getSelectionEnd());
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;
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());
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
);
1036 final Document document
= editor
.getDocument();
1037 final int length
= document
.getTextLength();
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
);
1065 final Document document
= editor
.getDocument();
1066 final int length
= document
.getTextLength();
1071 SelectionModel selectionModel
= editor
.getSelectionModel();
1072 if (selectionModel
.hasSelection()) {
1073 consoleView
.deleteUserText(selectionModel
.getSelectionStart(),
1074 selectionModel
.getSelectionEnd() - selectionModel
.getSelectionStart());
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
);
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
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();
1146 for (i
= 0; i
<ranges
.size(); i
++) {
1147 RangeHighlighter range
= ranges
.get(i
);
1148 if (range
.getUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES
) != null) {
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;
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() {
1169 public boolean canNavigateToSource() {
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
);
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();
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
);
1262 if (offset
> info
.endOffset
) {
1263 startOffset
= info
.endOffset
;
1265 else if (offset
< info
.startOffset
) {
1266 startOffset
= info
.startOffset
;
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
);
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
) {
1293 insertUserText(s
, start
);
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();
1312 if (consoleView
.myDeferredUserInput
.length() == 0) return;
1313 int charCountToReplace
;
1315 startOffset
= getStartOffset(start
, info
);
1316 endOffset
= getEndOffset(end
, info
);
1318 if (startOffset
== -1 ||
1320 endOffset
<= startOffset
) {
1321 editor
.getSelectionModel().removeSelection();
1322 editor
.getCaretModel().moveToOffset(start
);
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();
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 ||
1367 endOffset
<= startOffset
) {
1368 editor
.getSelectionModel().removeSelection();
1369 editor
.getCaretModel().moveToOffset(offset
);
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
) {
1392 if (offset
>= info
.startOffset
&& offset
< info
.endOffset
) {
1393 startOffset
= offset
;
1394 } else if (offset
< info
.startOffset
) {
1395 startOffset
= info
.startOffset
;
1402 private static int getEndOffset(int offset
, TokenInfo info
) {
1404 if (offset
> info
.endOffset
) {
1405 endOffset
= info
.endOffset
;
1406 } else if (offset
<= info
.startOffset
) {