js repl: in/out markers done
[idea/community.git] / platform / lang-impl / src / com / intellij / execution / console / LanguageConsoleImpl.java
1 /*
2  * Copyright 2000-2013 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.execution.console;
17
18 import com.intellij.codeInsight.daemon.impl.HighlightInfo;
19 import com.intellij.execution.impl.ConsoleViewUtil;
20 import com.intellij.execution.ui.ConsoleViewContentType;
21 import com.intellij.ide.DataManager;
22 import com.intellij.ide.highlighter.HighlighterFactory;
23 import com.intellij.ide.impl.TypeSafeDataProviderAdapter;
24 import com.intellij.injected.editor.EditorWindow;
25 import com.intellij.lang.Language;
26 import com.intellij.lang.annotation.HighlightSeverity;
27 import com.intellij.openapi.Disposable;
28 import com.intellij.openapi.actionSystem.*;
29 import com.intellij.openapi.application.ApplicationManager;
30 import com.intellij.openapi.editor.*;
31 import com.intellij.openapi.editor.actions.EditorActionUtil;
32 import com.intellij.openapi.editor.colors.EditorColors;
33 import com.intellij.openapi.editor.colors.EditorColorsManager;
34 import com.intellij.openapi.editor.colors.EditorColorsScheme;
35 import com.intellij.openapi.editor.colors.impl.DelegateColorScheme;
36 import com.intellij.openapi.editor.event.*;
37 import com.intellij.openapi.editor.ex.EditorEx;
38 import com.intellij.openapi.editor.ex.FocusChangeListener;
39 import com.intellij.openapi.editor.ex.RangeHighlighterEx;
40 import com.intellij.openapi.editor.ex.util.EditorUtil;
41 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
42 import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory;
43 import com.intellij.openapi.editor.highlighter.HighlighterIterator;
44 import com.intellij.openapi.editor.impl.DocumentMarkupModel;
45 import com.intellij.openapi.editor.impl.EditorFactoryImpl;
46 import com.intellij.openapi.editor.markup.*;
47 import com.intellij.openapi.fileEditor.*;
48 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
49 import com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl;
50 import com.intellij.openapi.fileTypes.FileTypes;
51 import com.intellij.openapi.project.Project;
52 import com.intellij.openapi.util.Comparing;
53 import com.intellij.openapi.util.TextRange;
54 import com.intellij.openapi.util.text.StringUtil;
55 import com.intellij.openapi.util.text.StringUtilRt;
56 import com.intellij.openapi.vfs.VirtualFile;
57 import com.intellij.psi.PsiFile;
58 import com.intellij.psi.PsiManager;
59 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
60 import com.intellij.testFramework.LightVirtualFile;
61 import com.intellij.ui.JBColor;
62 import com.intellij.ui.SideBorder;
63 import com.intellij.util.DocumentUtil;
64 import com.intellij.util.FileContentUtil;
65 import com.intellij.util.ObjectUtils;
66 import com.intellij.util.SingleAlarm;
67 import com.intellij.util.ui.AbstractLayoutManager;
68 import com.intellij.util.ui.UIUtil;
69 import org.jetbrains.annotations.NotNull;
70 import org.jetbrains.annotations.Nullable;
71
72 import javax.swing.*;
73 import java.awt.*;
74 import java.awt.event.ComponentAdapter;
75 import java.awt.event.ComponentEvent;
76 import java.awt.event.KeyAdapter;
77 import java.awt.event.KeyEvent;
78 import java.util.Collections;
79 import java.util.concurrent.atomic.AtomicBoolean;
80
81 /**
82  * @author Gregory.Shrago
83  * In case of REPL consider to use {@link LanguageConsoleBuilder}
84  */
85 public class LanguageConsoleImpl implements Disposable, TypeSafeDataProvider {
86   private static final int SEPARATOR_THICKNESS = 1;
87   private final Project myProject;
88
89   private final EditorEx myConsoleEditor;
90   private final EditorEx myHistoryViewer;
91   private final Document myEditorDocument;
92   private final LightVirtualFile myVirtualFile;
93
94   protected PsiFile myFile; // will change on language change
95
96   private final JPanel myPanel = new JPanel(new MyLayout());
97   private String myTitle;
98   @Nullable
99   private String myPrompt = "> ";
100   private final LightVirtualFile myHistoryFile;
101   private Editor myCurrentEditor;
102
103   private final AtomicBoolean myForceScrollToEnd = new AtomicBoolean(false);
104   private final SingleAlarm myUpdateQueue;
105   private Runnable myUiUpdateRunnable;
106
107   private boolean myShowSeparatorLine = true;
108
109   private final FocusChangeListener myFocusListener = new FocusChangeListener() {
110     @Override
111     public void focusGained(Editor editor) {
112       myCurrentEditor = editor;
113     }
114
115     @Override
116     public void focusLost(Editor editor) {
117     }
118   };
119
120   public LanguageConsoleImpl(@NotNull Project project, @NotNull String title, @NotNull Language language) {
121     this(project, title, language, true);
122   }
123
124   public LanguageConsoleImpl(@NotNull Project project, @NotNull String title, @NotNull Language language, boolean initComponents) {
125     this(project, title, new LightVirtualFile(title, language, ""), initComponents);
126   }
127
128   public LanguageConsoleImpl(@NotNull Project project, @NotNull String title, @NotNull LightVirtualFile lightFile, boolean initComponents) {
129     myProject = project;
130     myTitle = title;
131     myVirtualFile = lightFile;
132     EditorFactory editorFactory = EditorFactory.getInstance();
133     myHistoryFile = new LightVirtualFile(getTitle() + ".history.txt", FileTypes.PLAIN_TEXT, "");
134     myEditorDocument = FileDocumentManager.getInstance().getDocument(lightFile);
135     assert myEditorDocument != null;
136     myFile = createFile(myVirtualFile, myEditorDocument, myProject);
137     myConsoleEditor = (EditorEx)editorFactory.createEditor(myEditorDocument, myProject);
138     myConsoleEditor.addFocusListener(myFocusListener);
139     myCurrentEditor = myConsoleEditor;
140     myHistoryViewer = (EditorEx)editorFactory.createViewer(((EditorFactoryImpl)editorFactory).createDocument(true), myProject);
141     myUpdateQueue = new SingleAlarm(new Runnable() {
142       @Override
143       public void run() {
144         if (isConsoleEditorEnabled()) {
145           myPanel.revalidate();
146           myPanel.repaint();
147         }
148         if (myUiUpdateRunnable != null) {
149           myUiUpdateRunnable.run();
150         }
151       }
152     }, 300, this);
153
154     // action shortcuts are not yet registered
155     ApplicationManager.getApplication().invokeLater(new Runnable() {
156       @Override
157       public void run() {
158         installEditorFactoryListener();
159       }
160     }, myProject.getDisposed());
161
162     if (initComponents) {
163       initComponents();
164     }
165   }
166
167   public void initComponents() {
168     final EditorColorsScheme colorsScheme = myConsoleEditor.getColorsScheme();
169     final DelegateColorScheme scheme = new DelegateColorScheme(colorsScheme) {
170       @NotNull
171       @Override
172       public Color getDefaultBackground() {
173         final Color color = getColor(ConsoleViewContentType.CONSOLE_BACKGROUND_KEY);
174         return color == null ? super.getDefaultBackground() : color;
175       }
176     };
177     myConsoleEditor.setColorsScheme(scheme);
178     myHistoryViewer.setColorsScheme(scheme);
179     myPanel.add(myHistoryViewer.getComponent());
180     myPanel.add(myConsoleEditor.getComponent());
181     setupComponents();
182     DataManager.registerDataProvider(myPanel, new TypeSafeDataProviderAdapter(this));
183
184     myHistoryViewer.getComponent().addComponentListener(new ComponentAdapter() {
185       @Override
186       public void componentResized(ComponentEvent e) {
187         if (myForceScrollToEnd.getAndSet(false)) {
188           scrollHistoryToEnd();
189         }
190       }
191
192       @Override
193       public void componentShown(ComponentEvent e) {
194         componentResized(e);
195       }
196     });
197     setPromptInner(myPrompt);
198   }
199
200   public void setConsoleEditorEnabled(boolean consoleEditorEnabled) {
201     if (isConsoleEditorEnabled() == consoleEditorEnabled) {
202       return;
203     }
204
205     if (consoleEditorEnabled) {
206       FileEditorManager.getInstance(getProject()).closeFile(myVirtualFile);
207       myPanel.removeAll();
208       myPanel.add(myHistoryViewer.getComponent());
209       myPanel.add(myConsoleEditor.getComponent());
210
211       myHistoryViewer.setHorizontalScrollbarVisible(false);
212       myCurrentEditor = myConsoleEditor;
213     }
214     else {
215       myPanel.removeAll();
216       myPanel.add(myHistoryViewer.getComponent(), BorderLayout.CENTER);
217       myHistoryViewer.setHorizontalScrollbarVisible(true);
218     }
219   }
220
221   public void setShowSeparatorLine(boolean showSeparatorLine) {
222     myShowSeparatorLine = showSeparatorLine;
223   }
224
225   private void setupComponents() {
226     setupEditorDefault(myConsoleEditor);
227     setupEditorDefault(myHistoryViewer);
228
229     //noinspection PointlessBooleanExpression,ConstantConditions
230     if (SEPARATOR_THICKNESS > 0 && myShowSeparatorLine) {
231       myHistoryViewer.getComponent().setBorder(new SideBorder(JBColor.LIGHT_GRAY, SideBorder.BOTTOM));
232     }
233     myHistoryViewer.getComponent().setMinimumSize(new Dimension(0, 0));
234     myHistoryViewer.getComponent().setPreferredSize(new Dimension(0, 0));
235     myHistoryViewer.setCaretEnabled(false);
236
237     myConsoleEditor.addEditorMouseListener(EditorActionUtil.createEditorPopupHandler(IdeActions.GROUP_CONSOLE_EDITOR_POPUP));
238     myConsoleEditor.setHighlighter(EditorHighlighterFactory.getInstance().createEditorHighlighter(myProject, myVirtualFile));
239
240     final VisibleAreaListener areaListener = new VisibleAreaListener() {
241       @Override
242       public void visibleAreaChanged(VisibleAreaEvent e) {
243         final int offset = myConsoleEditor.getScrollingModel().getHorizontalScrollOffset();
244         final ScrollingModel model = myHistoryViewer.getScrollingModel();
245         final int historyOffset = model.getHorizontalScrollOffset();
246         if (historyOffset != offset) {
247           try {
248             model.disableAnimation();
249             model.scrollHorizontally(offset);
250           }
251           finally {
252             model.enableAnimation();
253           }
254         }
255       }
256     };
257     myConsoleEditor.getScrollingModel().addVisibleAreaListener(areaListener);
258     final DocumentAdapter docListener = new DocumentAdapter() {
259       @Override
260       public void documentChanged(final DocumentEvent e) {
261         queueUiUpdate(false);
262       }
263     };
264     myEditorDocument.addDocumentListener(docListener, this);
265     myHistoryViewer.getDocument().addDocumentListener(docListener, this);
266
267     myHistoryViewer.getContentComponent().addKeyListener(new KeyAdapter() {
268       @Override
269       public void keyTyped(KeyEvent event) {
270         if (isConsoleEditorEnabled() && UIUtil.isReallyTypedEvent(event)) {
271           myConsoleEditor.getContentComponent().requestFocus();
272           myConsoleEditor.processKeyTyped(event);
273         }
274       }
275     });
276     for (AnAction action : createActions()) {
277       action.registerCustomShortcutSet(action.getShortcutSet(), myConsoleEditor.getComponent());
278     }
279     EmptyAction.registerActionShortcuts(myHistoryViewer.getComponent(), myConsoleEditor.getComponent());
280   }
281
282   public boolean isConsoleEditorEnabled() {
283     return myPanel.getComponentCount() > 1;
284   }
285
286   @NotNull
287   protected AnAction[] createActions() {
288     return AnAction.EMPTY_ARRAY;
289   }
290
291   public void setTextToEditor(@NotNull final String text) {
292     ApplicationManager.getApplication().runWriteAction(new Runnable() {
293       @Override
294       public void run() {
295         myConsoleEditor.getDocument().setText(text);
296       }
297     });
298     queueUiUpdate(true);
299   }
300
301   protected void setupEditorDefault(@NotNull EditorEx editor) {
302     ConsoleViewUtil.setupConsoleEditor(editor, false, false);
303     editor.getContentComponent().setFocusCycleRoot(false);
304     editor.setHorizontalScrollbarVisible(false);
305     editor.setVerticalScrollbarVisible(true);
306     editor.setBorder(null);
307
308     final EditorSettings editorSettings = editor.getSettings();
309     editorSettings.setAdditionalLinesCount(myHistoryViewer == editor ? 0 : 2);
310     editorSettings.setAdditionalColumnsCount(1);
311     editorSettings.setRightMarginShown(false);
312   }
313
314   public void setUiUpdateRunnable(Runnable uiUpdateRunnable) {
315     assert myUiUpdateRunnable == null : "can be set only once";
316     myUiUpdateRunnable = uiUpdateRunnable;
317   }
318
319   public void flushAllUiUpdates() {
320     myUpdateQueue.flush();
321   }
322
323   @SuppressWarnings("UnusedDeclaration")
324   @NotNull
325   public LightVirtualFile getHistoryFile() {
326     return myHistoryFile;
327   }
328
329   @Nullable
330   public String getPrompt() {
331     return myPrompt;
332   }
333
334   public void setPrompt(@Nullable String prompt) {
335     // always add space to the prompt otherwise it may look ugly
336     myPrompt = prompt != null && !prompt.endsWith(" ") ? prompt + " " : prompt;
337     setPromptInner(myPrompt);
338   }
339
340   private void setPromptInner(@Nullable final String prompt) {
341     UIUtil.invokeAndWaitIfNeeded(new Runnable() {
342       @Override
343       public void run() {
344         myConsoleEditor.setPrefixTextAndAttributes(prompt, ConsoleViewContentType.USER_INPUT.getAttributes());
345         if (myPanel.isVisible()) {
346           queueUiUpdate(false);
347         }
348       }
349     });
350   }
351
352   public void setEditable(boolean editable) {
353     myConsoleEditor.setRendererMode(!editable);
354     setPromptInner(editable ? myPrompt : "");
355   }
356
357   public boolean isEditable() {
358     return !myConsoleEditor.isRendererMode();
359   }
360
361   @NotNull
362   public PsiFile getFile() {
363     return myFile;
364   }
365
366   @NotNull
367   public VirtualFile getVirtualFile() {
368     return myVirtualFile;
369   }
370
371   @NotNull
372   public EditorEx getHistoryViewer() {
373     return myHistoryViewer;
374   }
375
376   @NotNull
377   public Document getEditorDocument() {
378     return myEditorDocument;
379   }
380
381   @NotNull
382   public EditorEx getConsoleEditor() {
383     return myConsoleEditor;
384   }
385
386   @NotNull
387   public Project getProject() {
388     return myProject;
389   }
390
391   @NotNull
392   public String getTitle() {
393     return myTitle;
394   }
395
396   public void setTitle(@NotNull String title) {
397     myTitle = title;
398   }
399
400   public void printToHistory(@NotNull CharSequence text, @NotNull TextAttributes attributes) {
401     ApplicationManager.getApplication().assertIsDispatchThread();
402     text = StringUtilRt.unifyLineSeparators(text);
403     final boolean scrollToEnd = shouldScrollHistoryToEnd();
404     addTextToHistory(text, attributes);
405     if (scrollToEnd) {
406       scrollHistoryToEnd();
407     }
408     queueUiUpdate(scrollToEnd);
409   }
410
411   protected void addTextToHistory(@Nullable CharSequence text, @Nullable TextAttributes attributes) {
412     if (StringUtil.isEmpty(text) || attributes == null) {
413       return;
414     }
415
416     Document history = myHistoryViewer.getDocument();
417     int offset = appendToHistoryDocument(history, text);
418     DocumentMarkupModel.forDocument(history, myProject, true).addRangeHighlighter(offset, offset + text.length(), HighlighterLayer.SYNTAX, attributes,
419                                                                                   HighlighterTargetArea.EXACT_RANGE);
420   }
421
422   @SuppressWarnings("UnusedDeclaration")
423   @Deprecated
424   /**
425    * @deprecated Use {@link LanguageConsoleBuilder},
426    * {@link LanguageConsoleBuilder#registerExecuteAction)} or
427    * {@link ConsoleExecuteAction#prepareRunExecuteAction)}
428    *
429    * to remove in IDEA 15
430    */
431   public String addCurrentToHistory(@NotNull TextRange textRange, boolean erase, boolean preserveMarkup) {
432     return addToHistoryInner(textRange, myConsoleEditor, erase, preserveMarkup);
433   }
434
435   public String addToHistory(@NotNull TextRange textRange, @NotNull EditorEx editor, boolean preserveMarkup) {
436     return addToHistoryInner(textRange, editor, false, preserveMarkup);
437   }
438
439   @NotNull
440   public String prepareExecuteAction(boolean addToHistory, boolean preserveMarkup, boolean clearInput) {
441     Editor editor = getCurrentEditor();
442     Document document = editor.getDocument();
443     String text = document.getText();
444     TextRange range = new TextRange(0, document.getTextLength());
445     if (!clearInput) {
446       editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
447     }
448
449     if (addToHistory) {
450       addToHistoryInner(range, myConsoleEditor, clearInput, preserveMarkup);
451     }
452     else if (clearInput) {
453       setInputText("");
454     }
455     return text;
456   }
457
458   @NotNull
459   protected String addToHistoryInner(@NotNull final TextRange textRange, @NotNull final EditorEx editor, boolean erase, final boolean preserveMarkup) {
460     ApplicationManager.getApplication().assertIsDispatchThread();
461
462     String result = addTextRangeToHistory(textRange, editor, preserveMarkup);
463     if (erase) {
464       DocumentUtil.writeInRunUndoTransparentAction(new Runnable() {
465         @Override
466         public void run() {
467           editor.getDocument().deleteString(textRange.getStartOffset(), textRange.getEndOffset());
468         }
469       });
470     }
471     // always scroll to end on user input
472     scrollHistoryToEnd();
473     queueUiUpdate(true);
474     return result;
475   }
476
477   public boolean shouldScrollHistoryToEnd() {
478     final Rectangle visibleArea = myHistoryViewer.getScrollingModel().getVisibleArea();
479     final Dimension contentSize = myHistoryViewer.getContentSize();
480     return contentSize.getHeight() - visibleArea.getMaxY() < 2 * myHistoryViewer.getLineHeight();
481   }
482
483   private void scrollHistoryToEnd() {
484     final int lineCount = myHistoryViewer.getDocument().getLineCount();
485     if (lineCount == 0) {
486       return;
487     }
488     myHistoryViewer.getCaretModel().moveToOffset(myHistoryViewer.getDocument().getLineStartOffset(lineCount - 1), false);
489     myHistoryViewer.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
490   }
491
492   @NotNull
493   protected String addTextRangeToHistory(@NotNull TextRange textRange, @NotNull EditorEx consoleEditor, boolean preserveMarkup) {
494     doAddPromptToHistory();
495
496     final Document history = myHistoryViewer.getDocument();
497     final MarkupModel markupModel = DocumentMarkupModel.forDocument(history, myProject, true);
498     final int localStartOffset = textRange.getStartOffset();
499     String text;
500     EditorHighlighter highlighter;
501     if (consoleEditor instanceof EditorWindow) {
502       PsiFile file = ((EditorWindow)consoleEditor).getInjectedFile();
503       highlighter = HighlighterFactory.createHighlighter(file.getVirtualFile(), EditorColorsManager.getInstance().getGlobalScheme(), getProject());
504       String fullText = InjectedLanguageUtil.getUnescapedText(file, null, null);
505       highlighter.setText(fullText);
506       text = textRange.substring(fullText);
507     }
508     else {
509       text = consoleEditor.getDocument().getText(textRange);
510       highlighter = consoleEditor.getHighlighter();
511     }
512     //offset can be changed after text trimming after insert due to buffer constraints
513     int offset = appendToHistoryDocument(history, text);
514
515     final HighlighterIterator iterator = highlighter.createIterator(localStartOffset);
516     final int localEndOffset = textRange.getEndOffset();
517     while (!iterator.atEnd()) {
518       final int itStart = iterator.getStart();
519       if (itStart > localEndOffset) {
520         break;
521       }
522       final int itEnd = iterator.getEnd();
523       if (itEnd >= localStartOffset) {
524         final int start = Math.max(itStart, localStartOffset) - localStartOffset + offset;
525         final int end = Math.min(itEnd, localEndOffset) - localStartOffset + offset;
526         markupModel.addRangeHighlighter(start, end, HighlighterLayer.SYNTAX, iterator.getTextAttributes(),
527                                         HighlighterTargetArea.EXACT_RANGE);
528       }
529       iterator.advance();
530     }
531     if (preserveMarkup) {
532       duplicateHighlighters(markupModel, DocumentMarkupModel.forDocument(consoleEditor.getDocument(), myProject, true), offset, textRange);
533       // don't copy editor markup model, i.e. brace matcher, spell checker, etc.
534       // duplicateHighlighters(markupModel, consoleEditor.getMarkupModel(), offset, textRange);
535     }
536     if (!text.endsWith("\n")) {
537       appendToHistoryDocument(history, "\n");
538     }
539     return text;
540   }
541
542   protected void doAddPromptToHistory() {
543     addTextToHistory(myPrompt, ConsoleViewContentType.USER_INPUT.getAttributes());
544   }
545
546   // returns the real (cyclic-buffer-aware) start offset of the inserted text
547   protected int appendToHistoryDocument(@NotNull Document history, @NotNull CharSequence text) {
548     ApplicationManager.getApplication().assertIsDispatchThread();
549     history.insertString(history.getTextLength(), text);
550     return history.getTextLength() - text.length();
551   }
552
553   private static void duplicateHighlighters(@NotNull MarkupModel to, @NotNull MarkupModel from, int offset, @NotNull TextRange textRange) {
554     for (RangeHighlighter rangeHighlighter : from.getAllHighlighters()) {
555       if (!rangeHighlighter.isValid()) {
556         continue;
557       }
558       Object tooltip = rangeHighlighter.getErrorStripeTooltip();
559       HighlightInfo highlightInfo = tooltip instanceof HighlightInfo? (HighlightInfo)tooltip : null;
560       if (highlightInfo != null) {
561         if (highlightInfo.getSeverity() != HighlightSeverity.INFORMATION) {
562           continue;
563         }
564         if (highlightInfo.type.getAttributesKey() == EditorColors.IDENTIFIER_UNDER_CARET_ATTRIBUTES) {
565           continue;
566         }
567       }
568       final int localOffset = textRange.getStartOffset();
569       final int start = Math.max(rangeHighlighter.getStartOffset(), localOffset) - localOffset;
570       final int end = Math.min(rangeHighlighter.getEndOffset(), textRange.getEndOffset()) - localOffset;
571       if (start > end) {
572         continue;
573       }
574       final RangeHighlighter h = to.addRangeHighlighter(start + offset, end + offset, rangeHighlighter.getLayer(), rangeHighlighter.getTextAttributes(), rangeHighlighter.getTargetArea());
575       ((RangeHighlighterEx)h).setAfterEndOfLine(((RangeHighlighterEx)rangeHighlighter).isAfterEndOfLine());
576     }
577   }
578
579   @NotNull
580   public JComponent getComponent() {
581     return myPanel;
582   }
583
584   public void queueUiUpdate(boolean forceScrollToEnd) {
585     myForceScrollToEnd.compareAndSet(false, forceScrollToEnd);
586     myUpdateQueue.request();
587   }
588
589   @Override
590   public void dispose() {
591     final EditorFactory editorFactory = EditorFactory.getInstance();
592     editorFactory.releaseEditor(myConsoleEditor);
593     editorFactory.releaseEditor(myHistoryViewer);
594
595     final FileEditorManager editorManager = FileEditorManager.getInstance(getProject());
596     if (editorManager.isFileOpen(myVirtualFile)) {
597       editorManager.closeFile(myVirtualFile);
598     }
599   }
600
601   @Override
602   public void calcData(@NotNull DataKey key, @NotNull DataSink sink) {
603     if (OpenFileDescriptor.NAVIGATE_IN_EDITOR == key) {
604       sink.put(OpenFileDescriptor.NAVIGATE_IN_EDITOR, myConsoleEditor);
605     }
606     else if (getProject().isInitialized()) {
607       sink.put(key, FileEditorManagerEx.getInstanceEx(getProject()).getData(key.getName(), myConsoleEditor, myVirtualFile));
608     }
609   }
610
611   private void installEditorFactoryListener() {
612     FileEditorManagerAdapter fileEditorListener = new FileEditorManagerAdapter() {
613       @Override
614       public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
615         if (myConsoleEditor == null || !Comparing.equal(file, myVirtualFile)) {
616           return;
617         }
618
619         Editor selectedTextEditor = source.getSelectedTextEditor();
620         for (FileEditor fileEditor : source.getAllEditors(file)) {
621           if (!(fileEditor instanceof TextEditor)) {
622             continue;
623           }
624
625           final EditorEx editor = (EditorEx)((TextEditor)fileEditor).getEditor();
626           editor.addFocusListener(myFocusListener);
627           if (selectedTextEditor == editor) { // already focused
628             myCurrentEditor = editor;
629           }
630           EmptyAction.registerActionShortcuts(editor.getComponent(), myConsoleEditor.getComponent());
631           editor.getCaretModel().addCaretListener(new CaretListener() {
632             @Override
633             public void caretPositionChanged(CaretEvent e) {
634               queueUiUpdate(false);
635             }
636           });
637         }
638         queueUiUpdate(false);
639       }
640
641       @Override
642       public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
643         if (!Comparing.equal(file, myVirtualFile)) {
644           return;
645         }
646         if (myUiUpdateRunnable != null && !Boolean.TRUE.equals(file.getUserData(FileEditorManagerImpl.CLOSING_TO_REOPEN))) {
647           if (myCurrentEditor != null && myCurrentEditor.isDisposed()) {
648             myCurrentEditor = null;
649           }
650           ApplicationManager.getApplication().runReadAction(myUiUpdateRunnable);
651         }
652       }
653     };
654     myProject.getMessageBus().connect(this).subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, fileEditorListener);
655     FileEditorManager editorManager = FileEditorManager.getInstance(getProject());
656     if (editorManager.isFileOpen(myVirtualFile)) {
657       fileEditorListener.fileOpened(editorManager, myVirtualFile);
658     }
659   }
660
661   public Editor getCurrentEditor() {
662     return ObjectUtils.chooseNotNull(myCurrentEditor, myConsoleEditor);
663   }
664
665   public Language getLanguage() {
666     return myVirtualFile.getLanguage();
667   }
668
669   public void setLanguage(@NotNull Language language) {
670     myVirtualFile.setLanguage(language);
671     myVirtualFile.setContent(myEditorDocument, myEditorDocument.getText(), false);
672     FileContentUtil.reparseFiles(myProject, Collections.<VirtualFile>singletonList(myVirtualFile), false);
673     myFile = createFile(myVirtualFile, myEditorDocument, myProject);
674   }
675
676   public void setInputText(@NotNull final String query) {
677     DocumentUtil.writeInRunUndoTransparentAction(new Runnable() {
678       @Override
679       public void run() {
680         myConsoleEditor.getDocument().setText(query);
681       }
682     });
683   }
684
685   @NotNull
686   protected PsiFile createFile(@NotNull LightVirtualFile virtualFile, @NotNull Document document, @NotNull Project project) {
687     return ObjectUtils.assertNotNull(PsiManager.getInstance(project).findFile(virtualFile));
688   }
689
690   private class MyLayout extends AbstractLayoutManager {
691     @Override
692     public Dimension preferredLayoutSize(final Container parent) {
693       return new Dimension(0, 0);
694     }
695
696     @Override
697     public void layoutContainer(@NotNull final Container parent) {
698       final int componentCount = parent.getComponentCount();
699       if (componentCount == 0) {
700         return;
701       }
702
703       final EditorEx history = myHistoryViewer;
704       final EditorEx editor = componentCount == 2 ? myConsoleEditor : null;
705       if (editor == null) {
706         parent.getComponent(0).setBounds(parent.getBounds());
707         return;
708       }
709
710       final Dimension panelSize = parent.getSize();
711       if (panelSize.getHeight() <= 0) {
712         return;
713       }
714       final Dimension historySize = history.getContentSize();
715       final Dimension editorSize = editor.getContentSize();
716       final Dimension newEditorSize = new Dimension();
717
718       // deal with width
719       final int width = Math.max(editorSize.width, historySize.width);
720       newEditorSize.width = width + editor.getScrollPane().getHorizontalScrollBar().getHeight();
721       history.getSoftWrapModel().forceAdditionalColumnsUsage();
722       editor.getSettings().setAdditionalColumnsCount(2 + (width - editorSize.width) / EditorUtil.getSpaceWidth(Font.PLAIN, editor));
723       history.getSettings().setAdditionalColumnsCount(2 + (width - historySize.width) / EditorUtil.getSpaceWidth(Font.PLAIN, history));
724
725       // deal with height
726       if (historySize.width == 0) {
727         historySize.height = 0;
728       }
729       final int minHistorySize = historySize.height > 0 ? 2 * history.getLineHeight() + (myShowSeparatorLine ? SEPARATOR_THICKNESS : 0) : 0;
730       final int minEditorSize = editor.isViewer() ? 0 : editor.getLineHeight();
731       final int editorPreferred = editor.isViewer() ? 0 : Math.max(minEditorSize, editorSize.height);
732       final int historyPreferred = Math.max(minHistorySize, historySize.height);
733       if (panelSize.height < minEditorSize) {
734         newEditorSize.height = panelSize.height;
735       }
736       else if (panelSize.height < editorPreferred) {
737         newEditorSize.height = panelSize.height - minHistorySize;
738       }
739       else if (panelSize.height < editorPreferred + historyPreferred) {
740         newEditorSize.height = editorPreferred;
741       }
742       else {
743         newEditorSize.height = editorPreferred == 0 ? 0 : panelSize.height - historyPreferred;
744       }
745       final Dimension newHistorySize = new Dimension(width, panelSize.height - newEditorSize.height);
746
747       // apply
748       editor.getComponent().setBounds(0, newHistorySize.height, panelSize.width, newEditorSize.height);
749       myForceScrollToEnd.compareAndSet(false, shouldScrollHistoryToEnd());
750       history.getComponent().setBounds(0, 0, panelSize.width, newHistorySize.height);
751     }
752   }
753 }