18722 assert: CaretModelImpl.validateCallContext
[fedora-idea.git] / platform / lang-impl / src / com / intellij / execution / console / LanguageConsoleImpl.java
blob4c6ce94623c9e53665f78f421ac73c4e5ff62552
1 /*
2 * Copyright 2000-2010 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.
16 package com.intellij.execution.console;
18 import com.intellij.execution.ui.ConsoleViewContentType;
19 import com.intellij.ide.DataManager;
20 import com.intellij.ide.impl.TypeSafeDataProviderAdapter;
21 import com.intellij.lang.Language;
22 import com.intellij.openapi.Disposable;
23 import com.intellij.openapi.actionSystem.*;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.editor.*;
26 import com.intellij.openapi.editor.actions.EditorActionUtil;
27 import com.intellij.openapi.editor.colors.EditorColors;
28 import com.intellij.openapi.editor.event.*;
29 import com.intellij.openapi.editor.ex.EditorEx;
30 import com.intellij.openapi.editor.ex.RangeHighlighterEx;
31 import com.intellij.openapi.editor.ex.util.EditorUtil;
32 import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory;
33 import com.intellij.openapi.editor.highlighter.HighlighterIterator;
34 import com.intellij.openapi.editor.impl.DocumentImpl;
35 import com.intellij.openapi.editor.impl.EditorFactoryImpl;
36 import com.intellij.openapi.editor.impl.EditorImpl;
37 import com.intellij.openapi.editor.markup.HighlighterLayer;
38 import com.intellij.openapi.editor.markup.HighlighterTargetArea;
39 import com.intellij.openapi.editor.markup.MarkupModel;
40 import com.intellij.openapi.editor.markup.RangeHighlighter;
41 import com.intellij.openapi.fileEditor.FileEditor;
42 import com.intellij.openapi.fileEditor.FileEditorManager;
43 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
44 import com.intellij.openapi.fileEditor.TextEditor;
45 import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl;
46 import com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl;
47 import com.intellij.openapi.fileTypes.FileType;
48 import com.intellij.openapi.fileTypes.StdFileTypes;
49 import com.intellij.openapi.project.DumbAwareAction;
50 import com.intellij.openapi.project.Project;
51 import com.intellij.openapi.ui.DialogBuilder;
52 import com.intellij.openapi.util.Disposer;
53 import com.intellij.openapi.util.Ref;
54 import com.intellij.openapi.util.TextRange;
55 import com.intellij.openapi.vfs.VirtualFile;
56 import com.intellij.psi.PsiFile;
57 import com.intellij.psi.PsiFileFactory;
58 import com.intellij.psi.impl.PsiDocumentManagerImpl;
59 import com.intellij.psi.impl.PsiFileFactoryImpl;
60 import com.intellij.psi.impl.PsiManagerEx;
61 import com.intellij.testFramework.LightVirtualFile;
62 import com.intellij.ui.SideBorder;
63 import com.intellij.util.FileContentUtil;
64 import com.intellij.util.ui.UIUtil;
65 import com.intellij.util.ui.update.MergingUpdateQueue;
66 import com.intellij.util.ui.update.Update;
67 import org.jetbrains.annotations.NonNls;
69 import javax.swing.FocusManager;
70 import javax.swing.*;
71 import java.awt.*;
72 import java.awt.event.*;
73 import java.util.ArrayList;
74 import java.util.Collections;
75 import java.util.concurrent.atomic.AtomicBoolean;
77 /**
78 * @author Gregory.Shrago
80 public class LanguageConsoleImpl implements Disposable, TypeSafeDataProvider {
81 private static int SEPARATOR_THICKNESS = 1;
83 private final Project myProject;
85 private final EditorEx myConsoleEditor;
86 private final EditorEx myHistoryViewer;
87 private final Document myEditorDocument;
88 private PsiFile myFile;
90 private final JPanel myPanel = new JPanel(new BorderLayout());
92 private String myTitle;
93 private String myPrompt = "> ";
94 private LightVirtualFile myHistoryFile;
96 private Editor myCurrentEditor;
98 private final AtomicBoolean myForceScrollToEnd = new AtomicBoolean(false);
99 private final MergingUpdateQueue myUpdateQueue;
100 private Runnable myUiUpdateRunnable;
103 public LanguageConsoleImpl(final Project project, String title, final Language language) {
104 myProject = project;
105 myTitle = title;
106 installEditorFactoryListener();
107 final EditorFactory editorFactory = EditorFactory.getInstance();
108 myHistoryFile = new LightVirtualFile(getTitle() + ".history.txt", StdFileTypes.PLAIN_TEXT, "");
109 myEditorDocument = editorFactory.createDocument("");
110 setLanguage(language);
111 myConsoleEditor = (EditorEx)editorFactory.createEditor(myEditorDocument, myProject);
112 myCurrentEditor = myConsoleEditor;
113 myHistoryViewer = (EditorEx)editorFactory.createViewer(((EditorFactoryImpl)editorFactory).createDocument(true), myProject);
114 myPanel.add(myHistoryViewer.getComponent(), BorderLayout.NORTH);
115 myPanel.add(myConsoleEditor.getComponent(), BorderLayout.CENTER);
116 setupComponents();
117 myPanel.putClientProperty(DataManager.CLIENT_PROPERTY_DATA_PROVIDER, new TypeSafeDataProviderAdapter(this));
118 myUpdateQueue = new MergingUpdateQueue("ConsoleUpdateQueue", 300, true, null);
119 Disposer.register(this, myUpdateQueue);
120 myPanel.addComponentListener(new ComponentAdapter() {
121 public void componentResized(ComponentEvent e) {
122 try {
123 myHistoryViewer.getScrollingModel().disableAnimation();
124 updateSizes(true);
126 finally {
127 myHistoryViewer.getScrollingModel().enableAnimation();
131 public void componentShown(ComponentEvent e) {
132 componentResized(e);
137 private void setupComponents() {
138 setupEditorDefault(myConsoleEditor);
139 setupEditorDefault(myHistoryViewer);
140 setPrompt(myPrompt);
141 myConsoleEditor.addEditorMouseListener(EditorActionUtil.createEditorPopupHandler(IdeActions.GROUP_CUT_COPY_PASTE));
142 if (SEPARATOR_THICKNESS > 0) {
143 myHistoryViewer.getComponent().setBorder(new SideBorder(Color.LIGHT_GRAY, SideBorder.BOTTOM));
145 myHistoryViewer.getComponent().setMinimumSize(new Dimension(0, 0));
146 myHistoryViewer.getComponent().setPreferredSize(new Dimension(0, 0));
147 myConsoleEditor.getSettings().setAdditionalLinesCount(2);
148 myConsoleEditor.setHighlighter(EditorHighlighterFactory.getInstance().createEditorHighlighter(myProject, myFile.getVirtualFile()));
149 myHistoryViewer.setCaretEnabled(false);
150 myConsoleEditor.setHorizontalScrollbarVisible(true);
151 final VisibleAreaListener areaListener = new VisibleAreaListener() {
152 public void visibleAreaChanged(VisibleAreaEvent e) {
153 final int offset = myConsoleEditor.getScrollingModel().getHorizontalScrollOffset();
154 final ScrollingModel model = myHistoryViewer.getScrollingModel();
155 final int historyOffset = model.getHorizontalScrollOffset();
156 if (historyOffset != offset) {
157 try {
158 model.disableAnimation();
159 model.scrollHorizontally(offset);
161 finally {
162 model.enableAnimation();
167 myConsoleEditor.getScrollingModel().addVisibleAreaListener(areaListener);
168 final DocumentAdapter docListener = new DocumentAdapter() {
169 @Override
170 public void documentChanged(final DocumentEvent e) {
171 queueUiUpdate(false);
174 myEditorDocument.addDocumentListener(docListener, this);
175 myHistoryViewer.getDocument().addDocumentListener(docListener, this);
177 myHistoryViewer.getContentComponent().addKeyListener(new KeyAdapter() {
178 public void keyTyped(KeyEvent event) {
179 if (UIUtil.isReallyTypedEvent(event)) {
180 myConsoleEditor.getContentComponent().requestFocus();
181 myConsoleEditor.processKeyTyped(event);
185 for (AnAction action : createActions()) {
186 action.registerCustomShortcutSet(action.getShortcutSet(), myConsoleEditor.getComponent());
188 registerActionShortcuts(myHistoryViewer.getComponent());
191 protected AnAction[] createActions() {
192 return new AnAction[]{
193 new MyOpenInEditorAction()
197 private static void setupEditorDefault(EditorEx editor) {
198 editor.getContentComponent().setFocusCycleRoot(false);
199 editor.setHorizontalScrollbarVisible(false);
200 editor.setVerticalScrollbarVisible(true);
201 editor.getColorsScheme().setColor(EditorColors.CARET_ROW_COLOR, null);
202 editor.getScrollPane().setBorder(null);
203 editor.getContentComponent().setFocusCycleRoot(false);
205 final EditorSettings editorSettings = editor.getSettings();
206 editorSettings.setAdditionalLinesCount(0);
207 editorSettings.setAdditionalColumnsCount(1);
208 editorSettings.setRightMarginShown(false);
209 editorSettings.setFoldingOutlineShown(false);
210 editorSettings.setLineNumbersShown(false);
211 editorSettings.setLineMarkerAreaShown(false);
212 editorSettings.setVirtualSpace(false);
213 editorSettings.setLineCursorWidth(1);
216 public void setUiUpdateRunnable(Runnable uiUpdateRunnable) {
217 assert myUiUpdateRunnable == null : "can be set only once";
218 myUiUpdateRunnable = uiUpdateRunnable;
221 public LightVirtualFile getHistoryFile() {
222 return myHistoryFile;
225 public String getPrompt() {
226 return myPrompt;
229 public void setPrompt(String prompt) {
230 myPrompt = prompt;
231 ((EditorImpl)myConsoleEditor).setPrefixTextAndAttributes(myPrompt, ConsoleViewContentType.USER_INPUT.getAttributes());
234 public PsiFile getFile() {
235 return myFile;
238 public EditorEx getHistoryViewer() {
239 return myHistoryViewer;
242 public Document getEditorDocument() {
243 return myEditorDocument;
246 public EditorEx getConsoleEditor() {
247 return myConsoleEditor;
250 public Project getProject() {
251 return myProject;
254 public String getTitle() {
255 return myTitle;
258 public void setTitle(String title) {
259 this.myTitle = title;
262 public void addToHistory(final String text, final ConsoleViewContentType contentType) {
263 final boolean scrollToEnd = shouldScrollHistoryToEnd();
264 final Document history = myHistoryViewer.getDocument();
265 final MarkupModel markupModel = history.getMarkupModel(myProject);
266 final int offset = history.getTextLength();
267 history.insertString(offset, text);
268 if (!text.endsWith("\n")) history.insertString(history.getTextLength(), "\n");
269 markupModel.addRangeHighlighter(offset, history.getTextLength(), HighlighterLayer.SYNTAX, contentType.getAttributes(),
270 HighlighterTargetArea.EXACT_RANGE);
271 queueUiUpdate(scrollToEnd);
275 public String addCurrentToHistory(final TextRange textRange, final boolean erase) {
276 final Ref<String> ref = Ref.create("");
277 final boolean scrollToEnd = shouldScrollHistoryToEnd();
278 ApplicationManager.getApplication().runWriteAction(new Runnable() {
279 public void run() {
280 ref.set(addTextRangeToHistory(textRange));
281 if (erase) {
282 myConsoleEditor.getDocument().deleteString(textRange.getStartOffset(), textRange.getEndOffset());
286 queueUiUpdate(scrollToEnd);
287 return ref.get();
290 private boolean shouldScrollHistoryToEnd() {
291 final Rectangle visibleArea = myHistoryViewer.getScrollingModel().getVisibleArea();
292 final int lineNum = (visibleArea.y + visibleArea.height + myHistoryViewer.getLineHeight()) / myHistoryViewer.getLineHeight();
293 final int lineCount = myHistoryViewer.getDocument().getLineCount();
294 return lineNum == lineCount;
297 private void scrollHistoryToEnd() {
298 final int lineCount = myHistoryViewer.getDocument().getLineCount();
299 if (lineCount == 0) return;
300 myHistoryViewer.getCaretModel().moveToOffset(myHistoryViewer.getDocument().getLineStartOffset(lineCount - 1));
301 myHistoryViewer.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
304 private String addTextRangeToHistory(TextRange textRange) {
305 final DocumentImpl history = (DocumentImpl)myHistoryViewer.getDocument();
306 final MarkupModel markupModel = history.getMarkupModel(myProject);
307 final int promptOffset = history.getTextLength();
308 history.insertString(history.getTextLength(), myPrompt);
309 markupModel.addRangeHighlighter(promptOffset, history.getTextLength(), HighlighterLayer.SYNTAX, ConsoleViewContentType.USER_INPUT.getAttributes(),
310 HighlighterTargetArea.EXACT_RANGE);
312 final int offset = history.getTextLength();
313 final String text = textRange.substring(myConsoleEditor.getDocument().getText());
314 history.insertString(offset, text);
315 final HighlighterIterator iterator = myConsoleEditor.getHighlighter().createIterator(0);
316 while (!iterator.atEnd()) {
317 final int localOffset = textRange.getStartOffset();
318 final int start = Math.max(iterator.getStart(), localOffset) - localOffset;
319 final int end = Math.min(iterator.getEnd(), textRange.getEndOffset()) - localOffset;
320 if (start <= end) {
321 markupModel.addRangeHighlighter(start + offset, end + offset, HighlighterLayer.SYNTAX, iterator.getTextAttributes(),
322 HighlighterTargetArea.EXACT_RANGE);
324 iterator.advance();
326 duplicateHighlighters(markupModel, myConsoleEditor.getDocument().getMarkupModel(myProject), offset, textRange);
327 duplicateHighlighters(markupModel, myConsoleEditor.getMarkupModel(), offset, textRange);
328 if (!text.endsWith("\n")) history.insertString(history.getTextLength(), "\n");
329 return text;
332 private static void duplicateHighlighters(MarkupModel to, MarkupModel from, int offset, TextRange textRange) {
333 for (RangeHighlighter rangeHighlighter : from.getAllHighlighters()) {
334 final int localOffset = textRange.getStartOffset();
335 final int start = Math.max(rangeHighlighter.getStartOffset(), localOffset) - localOffset;
336 final int end = Math.min(rangeHighlighter.getEndOffset(), textRange.getEndOffset()) - localOffset;
337 if (start > end) continue;
338 final RangeHighlighter h = to.addRangeHighlighter(
339 start + offset, end + offset, rangeHighlighter.getLayer(), rangeHighlighter.getTextAttributes(), rangeHighlighter.getTargetArea());
340 ((RangeHighlighterEx)h).setAfterEndOfLine(((RangeHighlighterEx)rangeHighlighter).isAfterEndOfLine());
344 public JComponent getComponent() {
345 return myPanel;
348 private void queueUiUpdate(final boolean forceScrollToEnd) {
349 myForceScrollToEnd.compareAndSet(false, forceScrollToEnd);
350 myUpdateQueue.queue(new Update("UpdateUi") {
351 public void run() {
352 if (Disposer.isDisposed(LanguageConsoleImpl.this)) return;
353 updateSizes(myForceScrollToEnd.getAndSet(false));
354 if (myUiUpdateRunnable != null) {
355 ApplicationManager.getApplication().runReadAction(myUiUpdateRunnable);
361 private void updateSizes(boolean forceScrollToEnd) {
362 final Dimension panelSize = myPanel.getSize();
363 final Dimension historyContentSize = myHistoryViewer.getContentSize();
364 final Dimension contentSize = myConsoleEditor.getContentSize();
365 final Dimension newEditorSize = new Dimension();
366 final int minHistorySize = historyContentSize.height > 0 ? 2 * myHistoryViewer.getLineHeight() + SEPARATOR_THICKNESS : 0;
367 final int width = Math.max(contentSize.width, historyContentSize.width);
368 newEditorSize.height = Math.min(Math.max(panelSize.height - minHistorySize, 2 * myConsoleEditor.getLineHeight()),
369 contentSize.height + myConsoleEditor.getScrollPane().getHorizontalScrollBar().getHeight());
370 newEditorSize.width = width + myConsoleEditor.getScrollPane().getHorizontalScrollBar().getHeight();
371 myConsoleEditor.getSettings().setAdditionalColumnsCount(2 + (width - contentSize.width) / EditorUtil.getSpaceWidth(Font.PLAIN, myConsoleEditor));
372 myHistoryViewer.getSettings().setAdditionalColumnsCount(2 + (width - historyContentSize.width) / EditorUtil.getSpaceWidth(Font.PLAIN, myHistoryViewer));
374 final Dimension editorSize = myConsoleEditor.getComponent().getSize();
375 if (!editorSize.equals(newEditorSize)) {
376 myConsoleEditor.getComponent().setPreferredSize(newEditorSize);
378 final boolean scrollToEnd = forceScrollToEnd || shouldScrollHistoryToEnd();
379 final Dimension newHistorySize = new Dimension(
380 width, Math.max(0, Math.min(minHistorySize == 0? 0 : historyContentSize.height + SEPARATOR_THICKNESS,
381 panelSize.height - newEditorSize.height)));
382 final Dimension historySize = myHistoryViewer.getComponent().getSize();
383 if (!historySize.equals(newHistorySize)) {
384 myHistoryViewer.getComponent().setPreferredSize(newHistorySize);
386 myPanel.validate();
387 if (scrollToEnd) scrollHistoryToEnd();
390 public void dispose() {
391 final EditorFactory editorFactory = EditorFactory.getInstance();
392 editorFactory.releaseEditor(myConsoleEditor);
393 editorFactory.releaseEditor(myHistoryViewer);
395 final VirtualFile virtualFile = myFile.getVirtualFile();
396 assert virtualFile != null;
397 final FileEditorManager editorManager = FileEditorManager.getInstance(getProject());
398 final boolean isOpen = editorManager.isFileOpen(virtualFile);
399 if (isOpen) {
400 editorManager.closeFile(virtualFile);
404 public void calcData(DataKey key, DataSink sink) {
405 if (OpenFileDescriptor.NAVIGATE_IN_EDITOR == key) {
406 sink.put(OpenFileDescriptor.NAVIGATE_IN_EDITOR, myConsoleEditor);
407 return;
409 final Object o =
410 ((FileEditorManagerImpl)FileEditorManager.getInstance(getProject())).getData(key.getName(), myConsoleEditor, myFile.getVirtualFile());
411 sink.put(key, o);
414 public void openInEditor() {
415 final VirtualFile virtualFile = myFile.getVirtualFile();
416 assert virtualFile != null;
417 FileEditorManager.getInstance(getProject()).openTextEditor(
418 new OpenFileDescriptor(getProject(), virtualFile, myConsoleEditor.getCaretModel().getOffset()), true);
421 private void installEditorFactoryListener() {
422 final EditorFactoryListener factoryListener = new EditorFactoryListener() {
423 public void editorCreated(final EditorFactoryEvent event) {
424 final Editor editor = event.getEditor();
425 if (editor.getDocument() == myEditorDocument) {
426 if (myConsoleEditor != null) {
427 // i.e. if console is initialized
428 queueUiUpdate(false);
429 registerActionShortcuts(editor.getComponent());
431 editor.getCaretModel().addCaretListener(new CaretListener() {
432 public void caretPositionChanged(CaretEvent e) {
433 queueUiUpdate(false);
436 editor.getContentComponent().addFocusListener(new FocusListener() {
437 public void focusGained(final FocusEvent e) {
438 myCurrentEditor = editor;
441 public void focusLost(final FocusEvent e) {
447 public void editorReleased(final EditorFactoryEvent event) {
450 EditorFactory.getInstance().addEditorFactoryListener(factoryListener);
451 Disposer.register(this, new Disposable() {
452 public void dispose() {
453 EditorFactory.getInstance().removeEditorFactoryListener(factoryListener);
458 protected void registerActionShortcuts(JComponent component) {
459 final ArrayList<AnAction> actionList = (ArrayList<AnAction>)myConsoleEditor.getComponent().getClientProperty(AnAction.ourClientProperty);
460 if (actionList != null) {
461 for (AnAction anAction : actionList) {
462 anAction.registerCustomShortcutSet(anAction.getShortcutSet(), component);
467 public Editor getCurrentEditor() {
468 return myCurrentEditor;
471 public void setLanguage(Language language) {
472 final PsiFile prevFile = myFile;
473 if (prevFile != null) {
474 final VirtualFile file = prevFile.getVirtualFile();
475 assert file instanceof LightVirtualFile;
476 ((LightVirtualFile)file).setValid(false);
477 ((PsiManagerEx)prevFile.getManager()).getFileManager().setViewProvider(file, null);
480 final FileType type = language.getAssociatedFileType();
481 @NonNls final String name = getTitle() + "." + (type == null ? "txt" : type.getDefaultExtension());
482 final LightVirtualFile newVFile = new LightVirtualFile(name, language, myEditorDocument.getText());
483 FileDocumentManagerImpl.registerDocument(myEditorDocument, newVFile);
484 myFile = ((PsiFileFactoryImpl)PsiFileFactory.getInstance(myProject)).trySetupPsiForFile(newVFile, language, true, false);
485 if (myFile == null) {
486 throw new AssertionError("file=null, name=" + name + ", language=" + language.getDisplayName());
488 PsiDocumentManagerImpl.cachePsi(myEditorDocument, myFile);
489 FileContentUtil.reparseFiles(myProject, Collections.<VirtualFile>singletonList(newVFile), false);
491 if (prevFile != null) {
492 final FileEditorManager editorManager = FileEditorManager.getInstance(getProject());
493 final VirtualFile file = prevFile.getVirtualFile();
494 if (file != null && editorManager.isFileOpen(file)) {
495 final FileEditor prevEditor = editorManager.getSelectedEditor(file);
496 final boolean focusEditor;
497 final int offset;
498 if (prevEditor != null) {
499 offset = prevEditor instanceof TextEditor ? ((TextEditor)prevEditor).getEditor().getCaretModel().getOffset() : 0;
500 final Component owner = FocusManager.getCurrentManager().getFocusOwner();
501 focusEditor = owner != null && SwingUtilities.isDescendingFrom(owner, prevEditor.getComponent());
503 else {
504 focusEditor = false;
505 offset = 0;
507 editorManager.closeFile(file);
508 assert newVFile != null;
509 editorManager.openTextEditor(new OpenFileDescriptor(getProject(), newVFile, offset), focusEditor);
514 public void setInputText(final String query) {
515 ApplicationManager.getApplication().runWriteAction(new Runnable() {
516 public void run() {
517 myConsoleEditor.getDocument().setText(query);
522 private class MyOpenInEditorAction extends DumbAwareAction {
524 protected MyOpenInEditorAction() {
525 super("Open In Editor", null, null);
526 setShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.CTRL_MASK | InputEvent.ALT_MASK)));
529 @Override
530 public void actionPerformed(AnActionEvent e) {
531 openInEditor();
534 @Override
535 public void update(AnActionEvent e) {
536 super.update(e);