blob: de11bd5b9a12fa753fb5f8da972fdfd8668dd0e9 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.fileEditor.impl.text;
import com.intellij.ide.ui.customization.CustomActionsSchema;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.EditorMouseEvent;
import com.intellij.openapi.editor.event.EditorMouseEventArea;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.EditorMarkupModel;
import com.intellij.openapi.editor.highlighter.EditorHighlighter;
import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory;
import com.intellij.openapi.editor.impl.EditorImpl;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.impl.EditorHistoryManager;
import com.intellij.openapi.fileTypes.FileTypeEvent;
import com.intellij.openapi.fileTypes.FileTypeListener;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileAdapter;
import com.intellij.openapi.vfs.VirtualFileEvent;
import com.intellij.openapi.vfs.VirtualFilePropertyEvent;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.ex.StatusBarEx;
import com.intellij.ui.components.JBLoadingPanel;
import com.intellij.util.EditorPopupHandler;
import com.intellij.util.FileContentUtilCore;
import com.intellij.util.messages.MessageBusConnection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
/**
* @author Anton Katilin
* @author Vladimir Kondratyev
*/
class TextEditorComponent extends JBLoadingPanel implements DataProvider {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.text.TextEditorComponent");
private final Project myProject;
@NotNull private final VirtualFile myFile;
private final TextEditorImpl myTextEditor;
/**
* Document to be edited
*/
private final Document myDocument;
private final MyEditorMouseListener myEditorMouseListener;
private final MyDocumentListener myDocumentListener;
private final MyVirtualFileListener myVirtualFileListener;
@NotNull private final Editor myEditor;
/**
* Whether the editor's document is modified or not
*/
private boolean myModified;
/**
* Whether the editor is valid or not
*/
private boolean myValid;
private final MessageBusConnection myConnection;
TextEditorComponent(@NotNull final Project project, @NotNull final VirtualFile file, @NotNull final TextEditorImpl textEditor) {
super(new BorderLayout(), textEditor);
myProject = project;
myFile = file;
myTextEditor = textEditor;
myDocument = FileDocumentManager.getInstance().getDocument(myFile);
LOG.assertTrue(myDocument!=null);
myDocumentListener = new MyDocumentListener();
myDocument.addDocumentListener(myDocumentListener);
myEditorMouseListener = new MyEditorMouseListener();
myEditor = createEditor();
add(myEditor.getComponent(), BorderLayout.CENTER);
myModified = isModifiedImpl();
myValid = isEditorValidImpl();
LOG.assertTrue(myValid);
myVirtualFileListener = new MyVirtualFileListener();
myFile.getFileSystem().addVirtualFileListener(myVirtualFileListener);
myConnection = project.getMessageBus().connect();
myConnection.subscribe(FileTypeManager.TOPIC, new MyFileTypeListener());
myConnection.subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
@Override
public void enteredDumbMode() {
updateHighlighters();
}
@Override
public void exitDumbMode() {
updateHighlighters();
}
});
}
/**
* Disposes all resources allocated be the TextEditorComponent. It disposes all created
* editors, unregisters listeners. The behaviour of the splitter after disposing is
* unpredictable.
*/
void dispose(){
myDocument.removeDocumentListener(myDocumentListener);
EditorHistoryManager.getInstance(myProject).updateHistoryEntry(myFile, false);
disposeEditor(myEditor);
myConnection.disconnect();
myFile.getFileSystem().removeVirtualFileListener(myVirtualFileListener);
//myFocusWatcher.deinstall(this);
//removePropertyChangeListener(mySplitterPropertyChangeListener);
//super.dispose();
}
/**
* Should be invoked when the corresponding <code>TextEditorImpl</code>
* is selected. Updates the status bar.
*/
void selectNotify(){
updateStatusBar();
}
private static void assertThread(){
ApplicationManager.getApplication().assertIsDispatchThread();
}
/**
* @return most recently used editor. This method never returns <code>null</code>.
*/
@NotNull
Editor getEditor(){
return myEditor;
}
/**
* @return created editor. This editor should be released by {@link #disposeEditor(Editor) }
* method.
*/
@NotNull
private Editor createEditor(){
Editor editor = EditorFactory.getInstance().createEditor(myDocument, myProject);
((EditorMarkupModel) editor.getMarkupModel()).setErrorStripeVisible(true);
EditorHighlighter highlighter = EditorHighlighterFactory.getInstance().createEditorHighlighter(myFile, EditorColorsManager.getInstance().getGlobalScheme(), myProject);
((EditorEx) editor).setHighlighter(highlighter);
((EditorEx) editor).setFile(myFile);
editor.addEditorMouseListener(myEditorMouseListener);
((EditorImpl) editor).setDropHandler(new FileDropHandler(editor));
TextEditorProvider.putTextEditor(editor, myTextEditor);
return editor;
}
/**
* Disposes resources allocated by the specified editor view and registers all
* it's listeners
*/
private void disposeEditor(@NotNull Editor editor){
EditorFactory.getInstance().releaseEditor(editor);
editor.removeEditorMouseListener(myEditorMouseListener);
}
/**
* @return whether the editor's document is modified or not
*/
boolean isModified(){
assertThread();
return myModified;
}
/**
* Just calculates "modified" property
*/
private boolean isModifiedImpl(){
return FileDocumentManager.getInstance().isFileModified(myFile);
}
/**
* Updates "modified" property and fires event if necessary
*/
void updateModifiedProperty(){
Boolean oldModified=Boolean.valueOf(myModified);
myModified = isModifiedImpl();
myTextEditor.firePropertyChange(FileEditor.PROP_MODIFIED, oldModified, Boolean.valueOf(myModified));
}
/**
* Name <code>isValid</code> is in use in <code>java.awt.Component</code>
* so we change the name of method to <code>isEditorValid</code>
*
* @return whether the editor is valid or not
*/
boolean isEditorValid(){
return myValid && !myEditor.isDisposed();
}
/**
* Just calculates
*/
private boolean isEditorValidImpl(){
return FileDocumentManager.getInstance().getDocument(myFile) != null;
}
private void updateValidProperty(){
Boolean oldValid = Boolean.valueOf(myValid);
myValid = isEditorValidImpl();
myTextEditor.firePropertyChange(FileEditor.PROP_VALID, oldValid, Boolean.valueOf(myValid));
}
/**
* Updates editors' highlighters. This should be done when the opened file
* changes its file type.
*/
private void updateHighlighters(){
if (!myProject.isDisposed()) {
final EditorHighlighter highlighter = EditorHighlighterFactory.getInstance().createEditorHighlighter(myProject, myFile);
((EditorEx)myEditor).setHighlighter(highlighter);
}
}
/**
* Updates frame's status bar: insert/overwrite mode, caret position
*/
private void updateStatusBar(){
final StatusBarEx statusBar = (StatusBarEx)WindowManager.getInstance().getStatusBar(myProject);
if (statusBar == null) return;
statusBar.updateWidgets(); // TODO: do we need this?!
}
@Nullable
private Editor validateCurrentEditor() {
Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
if (focusOwner instanceof JComponent) {
final JComponent jComponent = (JComponent)focusOwner;
if (jComponent.getClientProperty("AuxEditorComponent") != null) return null; // Hack for EditorSearchComponent
}
return myEditor;
}
@Override
public Object getData(final String dataId) {
final Editor e = validateCurrentEditor();
if (e == null) return null;
if (!myProject.isDisposed()) {
final Object o = FileEditorManager.getInstance(myProject).getData(dataId, e, e.getCaretModel().getCurrentCaret());
if (o != null) return o;
}
if (CommonDataKeys.EDITOR.is(dataId)) {
return e;
}
if (CommonDataKeys.VIRTUAL_FILE.is(dataId)) {
return myFile.isValid()? myFile : null; // fix for SCR 40329
}
return null;
}
/**
* Shows popup menu
*/
private static final class MyEditorMouseListener extends EditorPopupHandler {
@Override
public void invokePopup(final EditorMouseEvent event) {
if (!event.isConsumed() && event.getArea() == EditorMouseEventArea.EDITING_AREA) {
ActionGroup group = (ActionGroup)CustomActionsSchema.getInstance().getCorrectedAction(IdeActions.GROUP_EDITOR_POPUP);
ActionPopupMenu popupMenu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.EDITOR_POPUP, group);
MouseEvent e = event.getMouseEvent();
final Component c = e.getComponent();
if (c != null && c.isShowing()) {
popupMenu.getComponent().show(c, e.getX(), e.getY());
}
e.consume();
}
}
}
/**
* Updates "modified" property
*/
private final class MyDocumentListener extends DocumentAdapter {
/**
* We can reuse this runnable to decrease number of allocated object.
*/
private final Runnable myUpdateRunnable;
public MyDocumentListener() {
myUpdateRunnable = new Runnable() {
@Override
public void run() {
updateModifiedProperty();
}
};
}
@Override
public void documentChanged(DocumentEvent e) {
// document's timestamp is changed later on undo or PSI changes
ApplicationManager.getApplication().invokeLater(myUpdateRunnable);
}
}
/**
* Listen changes of file types. When type of the file changes we need
* to also change highlighter.
*/
private final class MyFileTypeListener extends FileTypeListener.Adapter {
@Override
public void fileTypesChanged(@NotNull final FileTypeEvent event) {
assertThread();
// File can be invalid after file type changing. The editor should be removed
// by the FileEditorManager if it's invalid.
updateValidProperty();
updateHighlighters();
}
}
/**
* Updates "valid" property and highlighters (if necessary)
*/
private final class MyVirtualFileListener extends VirtualFileAdapter{
@Override
public void propertyChanged(@NotNull final VirtualFilePropertyEvent e) {
if(VirtualFile.PROP_NAME.equals(e.getPropertyName())){
// File can be invalidated after file changes name (extension also
// can changes). The editor should be removed if it's invalid.
updateValidProperty();
if (Comparing.equal(e.getFile(), myFile) &&
(FileContentUtilCore.FORCE_RELOAD_REQUESTOR.equals(e.getRequestor()) ||
!Comparing.equal(e.getOldValue(), e.getNewValue()))) {
updateHighlighters();
}
}
}
@Override
public void contentsChanged(@NotNull VirtualFileEvent event){
if (event.isFromSave()){ // commit
assertThread();
VirtualFile file = event.getFile();
LOG.assertTrue(file.isValid());
if(myFile.equals(file)){
updateModifiedProperty();
}
}
}
}
@NotNull
public VirtualFile getFile() {
return myFile;
}
}