blob: 14f0c95ec31b7cb3abb26c3eb2a99fe19a25d8cb [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.codeHighlighting.BackgroundEditorHighlighter;
import com.intellij.ide.structureView.StructureViewBuilder;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.fileEditor.*;
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
import com.intellij.openapi.fileTypes.BinaryFileTypeDecompilers;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.Navigatable;
import com.intellij.psi.SingleRootFileViewProvider;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
/**
* @author Anton Katilin
* @author Vladimir Kondratyev
*/
public class TextEditorProvider implements FileEditorProvider, DumbAware {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.text.TextEditorProvider");
private static final Key<TextEditor> TEXT_EDITOR_KEY = Key.create("textEditor");
@NonNls private static final String TYPE_ID = "text-editor";
@NonNls private static final String LINE_ATTR = "line";
@NonNls private static final String COLUMN_ATTR = "column";
@NonNls private static final String SELECTION_START_LINE_ATTR = "selection-start-line";
@NonNls private static final String SELECTION_START_COLUMN_ATTR = "selection-start-column";
@NonNls private static final String SELECTION_END_LINE_ATTR = "selection-end-line";
@NonNls private static final String SELECTION_END_COLUMN_ATTR = "selection-end-column";
@NonNls private static final String VERTICAL_SCROLL_PROPORTION_ATTR = "vertical-scroll-proportion";
@NonNls private static final String VERTICAL_OFFSET_ATTR = "vertical-offset";
@NonNls private static final String MAX_VERTICAL_OFFSET_ATTR = "max-vertical-offset";
@NonNls private static final String CARET_ELEMENT = "caret";
public static TextEditorProvider getInstance() {
return ApplicationManager.getApplication().getComponent(TextEditorProvider.class);
}
@Override
public boolean accept(@NotNull Project project, @NotNull VirtualFile file) {
if (file.isDirectory() || !file.isValid()) {
return false;
}
if (SingleRootFileViewProvider.isTooLargeForContentLoading(file)) {
return false;
}
final FileType ft = file.getFileType();
return !ft.isBinary() || BinaryFileTypeDecompilers.INSTANCE.forFileType(ft) != null;
}
@Override
@NotNull
public FileEditor createEditor(@NotNull Project project, @NotNull final VirtualFile file) {
LOG.assertTrue(accept(project, file));
return new TextEditorImpl(project, file, this);
}
@Override
public void disposeEditor(@NotNull FileEditor editor) {
Disposer.dispose(editor);
}
@Override
@NotNull
public FileEditorState readState(@NotNull Element element, @NotNull Project project, @NotNull VirtualFile file) {
TextEditorState state = new TextEditorState();
try {
List<Element> caretElements = element.getChildren(CARET_ELEMENT);
if (caretElements.isEmpty()) {
state.CARETS = new TextEditorState.CaretState[] {readCaretInfo(element)};
}
else {
state.CARETS = new TextEditorState.CaretState[caretElements.size()];
for (int i = 0; i < caretElements.size(); i++) {
state.CARETS[i] = readCaretInfo(caretElements.get(i));
}
}
String verticalScrollProportion = element.getAttributeValue(VERTICAL_SCROLL_PROPORTION_ATTR);
state.VERTICAL_SCROLL_PROPORTION = verticalScrollProportion == null ? 0 : Float.parseFloat(verticalScrollProportion);
String verticalOffset = element.getAttributeValue(VERTICAL_OFFSET_ATTR);
String maxVerticalOffset = element.getAttributeValue(MAX_VERTICAL_OFFSET_ATTR);
if (!StringUtil.isEmpty(verticalOffset) && !StringUtil.isEmpty(maxVerticalOffset)) {
state.VERTICAL_SCROLL_OFFSET = Integer.parseInt(verticalOffset);
state.MAX_VERTICAL_SCROLL_OFFSET = Integer.parseInt(maxVerticalOffset);
}
}
catch (NumberFormatException ignored) {
}
return state;
}
private static TextEditorState.CaretState readCaretInfo(Element element) {
TextEditorState.CaretState caretState = new TextEditorState.CaretState();
caretState.LINE = parseWithDefault(element, LINE_ATTR);
caretState.COLUMN = parseWithDefault(element, COLUMN_ATTR);
caretState.SELECTION_START_LINE = parseWithDefault(element, SELECTION_START_LINE_ATTR);
caretState.SELECTION_START_COLUMN = parseWithDefault(element, SELECTION_START_COLUMN_ATTR);
caretState.SELECTION_END_LINE = parseWithDefault(element, SELECTION_END_LINE_ATTR);
caretState.SELECTION_END_COLUMN = parseWithDefault(element, SELECTION_END_COLUMN_ATTR);
return caretState;
}
private static int parseWithDefault(Element element, String attributeName) {
String value = element.getAttributeValue(attributeName);
return value == null ? 0 : Integer.parseInt(value);
}
@Override
public void writeState(@NotNull FileEditorState _state, @NotNull Project project, @NotNull Element element) {
TextEditorState state = (TextEditorState)_state;
element.setAttribute(VERTICAL_SCROLL_PROPORTION_ATTR, Float.toString(state.VERTICAL_SCROLL_PROPORTION));
element.setAttribute(VERTICAL_OFFSET_ATTR, Integer.toString(state.VERTICAL_SCROLL_OFFSET));
element.setAttribute(MAX_VERTICAL_OFFSET_ATTR, Integer.toString(state.MAX_VERTICAL_SCROLL_OFFSET));
if (state.CARETS != null) {
for (TextEditorState.CaretState caretState : state.CARETS) {
Element e = new Element(CARET_ELEMENT);
e.setAttribute(LINE_ATTR, Integer.toString(caretState.LINE));
e.setAttribute(COLUMN_ATTR, Integer.toString(caretState.COLUMN));
e.setAttribute(SELECTION_START_LINE_ATTR, Integer.toString(caretState.SELECTION_START_LINE));
e.setAttribute(SELECTION_START_COLUMN_ATTR, Integer.toString(caretState.SELECTION_START_COLUMN));
e.setAttribute(SELECTION_END_LINE_ATTR, Integer.toString(caretState.SELECTION_END_LINE));
e.setAttribute(SELECTION_END_COLUMN_ATTR, Integer.toString(caretState.SELECTION_END_COLUMN));
element.addContent(e);
}
}
}
@Override
@NotNull
public String getEditorTypeId() {
return TYPE_ID;
}
@Override
@NotNull
public FileEditorPolicy getPolicy() {
return FileEditorPolicy.NONE;
}
@NotNull
public TextEditor getTextEditor(@NotNull Editor editor) {
TextEditor textEditor = editor.getUserData(TEXT_EDITOR_KEY);
if (textEditor == null) {
textEditor = createWrapperForEditor(editor);
putTextEditor(editor, textEditor);
}
return textEditor;
}
@NotNull
protected EditorWrapper createWrapperForEditor(@NotNull Editor editor) {
return new EditorWrapper(editor);
}
@Nullable
public static Document[] getDocuments(@NotNull FileEditor editor) {
if (editor instanceof DocumentsEditor) {
DocumentsEditor documentsEditor = (DocumentsEditor)editor;
Document[] documents = documentsEditor.getDocuments();
return documents.length > 0 ? documents : null;
}
if (editor instanceof TextEditor) {
Document document = ((TextEditor)editor).getEditor().getDocument();
return new Document[]{document};
}
Project[] projects = ProjectManager.getInstance().getOpenProjects();
for (int i = projects.length - 1; i >= 0; i--) {
VirtualFile file = FileEditorManagerEx.getInstanceEx(projects[i]).getFile(editor);
if (file != null) {
Document document = FileDocumentManager.getInstance().getDocument(file);
if (document != null) {
return new Document[]{document};
}
}
}
return null;
}
static void putTextEditor(Editor editor, TextEditor textEditor) {
editor.putUserData(TEXT_EDITOR_KEY, textEditor);
}
protected TextEditorState getStateImpl(final Project project, @NotNull Editor editor, @NotNull FileEditorStateLevel level){
TextEditorState state = new TextEditorState();
CaretModel caretModel = editor.getCaretModel();
if (caretModel.supportsMultipleCarets()) {
List<CaretState> caretsAndSelections = caretModel.getCaretsAndSelections();
state.CARETS = new TextEditorState.CaretState[caretsAndSelections.size()];
for (int i = 0; i < caretsAndSelections.size(); i++) {
CaretState caretState = caretsAndSelections.get(i);
LogicalPosition caretPosition = caretState.getCaretPosition();
LogicalPosition selectionStartPosition = caretState.getSelectionStart();
LogicalPosition selectionEndPosition = caretState.getSelectionEnd();
state.CARETS[i] = createCaretState(caretPosition, selectionStartPosition, selectionEndPosition);
}
}
else {
LogicalPosition caretPosition = caretModel.getLogicalPosition();
LogicalPosition selectionStartPosition = editor.offsetToLogicalPosition(editor.getSelectionModel().getSelectionStart());
LogicalPosition selectionEndPosition = editor.offsetToLogicalPosition(editor.getSelectionModel().getSelectionEnd());
state.CARETS = new TextEditorState.CaretState[1];
state.CARETS[0] = createCaretState(caretPosition, selectionStartPosition, selectionEndPosition);
}
// Saving scrolling proportion on UNDO may cause undesirable results of undo action fails to perform since
// scrolling proportion restored slightly differs from what have been saved.
state.VERTICAL_SCROLL_PROPORTION = level == FileEditorStateLevel.UNDO ? -1 : EditorUtil.calcVerticalScrollProportion(editor);
if (editor instanceof EditorEx) {
state.VERTICAL_SCROLL_OFFSET = editor.getScrollingModel().getVerticalScrollOffset();
JScrollBar scrollBar = ((EditorEx)editor).getScrollPane().getVerticalScrollBar();
state.MAX_VERTICAL_SCROLL_OFFSET = scrollBar == null ? 0 : scrollBar.getMaximum();
}
return state;
}
private static TextEditorState.CaretState createCaretState(LogicalPosition caretPosition, LogicalPosition selectionStartPosition, LogicalPosition selectionEndPosition) {
TextEditorState.CaretState caretState = new TextEditorState.CaretState();
caretState.LINE = getLine(caretPosition);
caretState.COLUMN = getColumn(caretPosition);
caretState.SELECTION_START_LINE = getLine(selectionStartPosition);
caretState.SELECTION_START_COLUMN = getColumn(selectionStartPosition);
caretState.SELECTION_END_LINE = getLine(selectionEndPosition);
caretState.SELECTION_END_COLUMN = getColumn(selectionEndPosition);
return caretState;
}
private static int getLine(@Nullable LogicalPosition pos) {
return pos == null ? 0 : pos.line;
}
private static int getColumn(@Nullable LogicalPosition pos) {
return pos == null ? 0 : pos.column;
}
protected void setStateImpl(final Project project, final Editor editor, final TextEditorState state){
if (state.CARETS != null) {
if (editor.getCaretModel().supportsMultipleCarets()) {
CaretModel caretModel = editor.getCaretModel();
List<CaretState> states = new ArrayList<CaretState>(state.CARETS.length);
for (TextEditorState.CaretState caretState : state.CARETS) {
states.add(new CaretState(new LogicalPosition(caretState.LINE, caretState.COLUMN),
new LogicalPosition(caretState.SELECTION_START_LINE, caretState.SELECTION_START_COLUMN),
new LogicalPosition(caretState.SELECTION_END_LINE, caretState.SELECTION_END_COLUMN)));
}
caretModel.setCaretsAndSelections(states, false);
}
else {
LogicalPosition pos = new LogicalPosition(state.CARETS[0].LINE, state.CARETS[0].COLUMN);
editor.getCaretModel().moveToLogicalPosition(pos);
editor.getSelectionModel().removeSelection();
}
}
EditorEx editorEx = editor instanceof EditorEx ? (EditorEx)editor : null;
boolean preciselyScrollVertically =
state.VERTICAL_SCROLL_OFFSET > 0
&& editorEx != null
&& editorEx.getScrollPane().getVerticalScrollBar() != null
&& editorEx.getScrollPane().getVerticalScrollBar().getMaximum() == state.MAX_VERTICAL_SCROLL_OFFSET;
if (preciselyScrollVertically) {
editor.getScrollingModel().scrollVertically(state.VERTICAL_SCROLL_OFFSET);
}
else {
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
if (state.VERTICAL_SCROLL_PROPORTION != -1) {
EditorUtil.setVerticalScrollProportion(editor, state.VERTICAL_SCROLL_PROPORTION);
}
}
if (!editor.getCaretModel().supportsMultipleCarets()) {
if (state.CARETS[0].SELECTION_START_LINE == state.CARETS[0].SELECTION_END_LINE
&& state.CARETS[0].SELECTION_START_COLUMN == state.CARETS[0].SELECTION_END_COLUMN) {
editor.getSelectionModel().removeSelection();
}
else {
int startOffset = editor.logicalPositionToOffset(new LogicalPosition(state.CARETS[0].SELECTION_START_LINE, state.CARETS[0].SELECTION_START_COLUMN));
int endOffset = editor.logicalPositionToOffset(new LogicalPosition(state.CARETS[0].SELECTION_END_LINE, state.CARETS[0].SELECTION_END_COLUMN));
editor.getSelectionModel().setSelection(startOffset, endOffset);
}
}
if (!preciselyScrollVertically) {
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
}
}
protected class EditorWrapper extends UserDataHolderBase implements TextEditor {
private final Editor myEditor;
public EditorWrapper(@NotNull Editor editor) {
myEditor = editor;
}
@Override
@NotNull
public Editor getEditor() {
return myEditor;
}
@Override
@NotNull
public JComponent getComponent() {
return myEditor.getComponent();
}
@Override
public JComponent getPreferredFocusedComponent() {
return myEditor.getContentComponent();
}
@Override
@NotNull
public String getName() {
return "Text";
}
@Override
public StructureViewBuilder getStructureViewBuilder() {
VirtualFile file = FileDocumentManager.getInstance().getFile(myEditor.getDocument());
if (file == null) return null;
final Project project = myEditor.getProject();
LOG.assertTrue(project != null);
return StructureViewBuilder.PROVIDER.getStructureViewBuilder(file.getFileType(), file, project);
}
@Override
@NotNull
public FileEditorState getState(@NotNull FileEditorStateLevel level) {
return getStateImpl(null, myEditor, level);
}
@Override
public void setState(@NotNull FileEditorState state) {
setStateImpl(null, myEditor, (TextEditorState)state);
}
@Override
public boolean isModified() {
return false;
}
@Override
public boolean isValid() {
return true;
}
@Override
public void dispose() { }
@Override
public void selectNotify() { }
@Override
public void deselectNotify() { }
@Override
public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) { }
@Override
public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) { }
@Override
public BackgroundEditorHighlighter getBackgroundHighlighter() {
return null;
}
@Override
public FileEditorLocation getCurrentLocation() {
return null;
}
@Override
public boolean canNavigateTo(@NotNull final Navigatable navigatable) {
return false;
}
@Override
public void navigateTo(@NotNull final Navigatable navigatable) {
}
}
}