fbf057f50acc2b1c09e45a5e06b0abe897cff072
[idea/community.git] / platform / platform-api / src / com / intellij / openapi / fileEditor / OpenFileDescriptor.java
1 /*
2  * Copyright 2000-2014 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.openapi.fileEditor;
17
18 import com.intellij.ide.*;
19 import com.intellij.ide.FileEditorProvider;
20 import com.intellij.openapi.actionSystem.DataContext;
21 import com.intellij.openapi.actionSystem.DataKey;
22 import com.intellij.openapi.editor.*;
23 import com.intellij.openapi.fileTypes.FileType;
24 import com.intellij.openapi.fileTypes.FileTypeManager;
25 import com.intellij.openapi.fileTypes.INativeFileType;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.openapi.util.Comparing;
28 import com.intellij.openapi.util.TextRange;
29 import com.intellij.openapi.vfs.VirtualFile;
30 import com.intellij.openapi.wm.IdeFocusManager;
31 import com.intellij.pom.Navigatable;
32 import org.jetbrains.annotations.NotNull;
33 import org.jetbrains.annotations.Nullable;
34
35 import java.util.List;
36
37 public class OpenFileDescriptor implements Navigatable {
38   /**
39    * Tells descriptor to navigate in specific editor rather than file editor
40    * in main IDEA window.
41    * For example if you want to navigate in editor embedded into modal dialog,
42    * you should provide this data.
43    */
44   public static final DataKey<Editor> NAVIGATE_IN_EDITOR = DataKey.create("NAVIGATE_IN_EDITOR");
45
46   @NotNull
47   private final VirtualFile myFile;
48   private final int myOffset;
49   private final int myLogicalLine;
50   private final int myLogicalColumn;
51   private final RangeMarker myRangeMarker;
52   @NotNull
53   private final Project myProject;
54
55   private boolean myUseCurrentWindow = false;
56
57   public OpenFileDescriptor(@NotNull Project project, @NotNull VirtualFile file, int offset) {
58     this(project, file, -1, -1, offset, false);
59   }
60
61   public OpenFileDescriptor(@NotNull Project project, @NotNull VirtualFile file, int logicalLine, int logicalColumn) {
62     this(project, file, logicalLine, logicalColumn, -1, false);
63   }
64
65   public OpenFileDescriptor(@NotNull Project project, @NotNull VirtualFile file,
66                             int logicalLine, int logicalColumn, boolean persistent) {
67     this(project, file, logicalLine, logicalColumn, -1, persistent);
68   }
69
70   public OpenFileDescriptor(@NotNull Project project, @NotNull VirtualFile file) {
71     this(project, file, -1, -1, -1, false);
72   }
73
74   private OpenFileDescriptor(@NotNull Project project, @NotNull VirtualFile file,
75                              int logicalLine, int logicalColumn, int offset, boolean persistent) {
76     myProject = project;
77
78     myFile = file;
79     myLogicalLine = logicalLine;
80     myLogicalColumn = logicalColumn;
81     myOffset = offset;
82     if (offset >= 0) {
83       myRangeMarker = LazyRangeMarkerFactory.getInstance(project).createRangeMarker(file, offset);
84     }
85     else if (logicalLine >= 0 ){
86       myRangeMarker = LazyRangeMarkerFactory.getInstance(project).createRangeMarker(file, logicalLine, Math.max(0, logicalColumn), persistent);
87     }
88     else {
89       myRangeMarker = null;
90     }
91   }
92
93   @NotNull
94   public VirtualFile getFile() {
95     return myFile;
96   }
97
98   @Nullable
99   public RangeMarker getRangeMarker() {
100     return myRangeMarker;
101   }
102
103   public int getOffset() {
104     return myRangeMarker != null && myRangeMarker.isValid() ? myRangeMarker.getStartOffset() : myOffset;
105   }
106
107   public int getLine() {
108     return myLogicalLine;
109   }
110
111   public int getColumn() {
112     return myLogicalColumn;
113   }
114
115   @Override
116   public void navigate(boolean requestFocus) {
117     if (!canNavigate()) {
118       throw new IllegalStateException("Navigation is not possible with null project");
119     }
120
121     if (!myFile.isDirectory() && navigateInEditorOrNativeApp(myProject, requestFocus)) return;
122
123     navigateInProjectView(requestFocus);
124   }
125
126   private boolean navigateInEditorOrNativeApp(@NotNull Project project, boolean requestFocus) {
127     FileType type = FileTypeManager.getInstance().getKnownFileTypeOrAssociate(myFile,project);
128     if (type == null || !myFile.isValid()) return false;
129
130     if (type instanceof INativeFileType) {
131       return ((INativeFileType) type).openFileInAssociatedApplication(project, myFile);
132     }
133
134     return navigateInEditor(project, requestFocus);
135   }
136
137   public boolean navigateInEditor(@NotNull Project project, boolean requestFocus) {
138     return navigateInRequestedEditor() || navigateInAnyFileEditor(project, requestFocus);
139   }
140
141   private boolean navigateInRequestedEditor() {
142     DataContext ctx = DataManager.getInstance().getDataContext();
143     Editor e = NAVIGATE_IN_EDITOR.getData(ctx);
144     if (e == null) return false;
145     if (!Comparing.equal(FileDocumentManager.getInstance().getFile(e.getDocument()), myFile)) return false;
146     
147     navigateIn(e);
148     return true;
149   }
150
151   private boolean navigateInAnyFileEditor(Project project, boolean focusEditor) {
152     List<FileEditor> editors = FileEditorManager.getInstance(project).openEditor(this, focusEditor);
153     for (FileEditor editor : editors) {
154       if (editor instanceof TextEditor) {
155         Editor e = ((TextEditor)editor).getEditor();
156         unfoldCurrentLine(e);
157         if (focusEditor) {
158           IdeFocusManager.getInstance(myProject).requestFocus(e.getContentComponent(), true);
159         }
160       }
161     }
162     return !editors.isEmpty();
163   }
164
165   private void navigateInProjectView(boolean requestFocus) {
166     SelectInContext context = new SelectInContext() {
167       @Override
168       @NotNull
169       public Project getProject() {
170         return myProject;
171       }
172
173       @Override
174       @NotNull
175       public VirtualFile getVirtualFile() {
176         return myFile;
177       }
178
179       @Override
180       @Nullable
181       public Object getSelectorInFile() {
182         return null;
183       }
184
185       @Override
186       @Nullable
187       public FileEditorProvider getFileEditorProvider() {
188         return null;
189       }
190     };
191
192     for (SelectInTarget target : SelectInManager.getInstance(myProject).getTargets()) {
193       if (target.canSelect(context)) {
194         target.selectIn(context, requestFocus);
195         return;
196       }
197     }
198   }
199
200   public void navigateIn(@NotNull Editor e) {
201     final int offset = getOffset();
202     CaretModel caretModel = e.getCaretModel();
203     boolean caretMoved = false;
204     if (myLogicalLine >= 0) {
205       LogicalPosition pos = new LogicalPosition(myLogicalLine, Math.max(myLogicalColumn, 0));
206       if (offset < 0 || offset == e.logicalPositionToOffset(pos)) {
207         caretModel.removeSecondaryCarets();
208         caretModel.moveToLogicalPosition(pos);
209         caretMoved = true;
210       }
211     }
212     if (!caretMoved && offset >= 0) {
213       caretModel.removeSecondaryCarets();
214       caretModel.moveToOffset(Math.min(offset, e.getDocument().getTextLength()));
215       caretMoved = true;
216     }
217
218     if (caretMoved) {
219       e.getSelectionModel().removeSelection();
220       scrollToCaret(e);
221       unfoldCurrentLine(e);
222     }
223   }
224
225   private static void unfoldCurrentLine(@NotNull final Editor editor) {
226     final FoldRegion[] allRegions = editor.getFoldingModel().getAllFoldRegions();
227     final TextRange range = getRangeToUnfoldOnNavigation(editor);
228     editor.getFoldingModel().runBatchFoldingOperation(new Runnable() {
229       @Override
230       public void run() {
231         for (FoldRegion region : allRegions) {
232           if (!region.isExpanded() && range.intersects(TextRange.create(region))) {
233             region.setExpanded(true);
234           }
235         }
236       }
237     });
238   }
239
240   @NotNull
241   public static TextRange getRangeToUnfoldOnNavigation(@NotNull Editor editor) {
242     final int offset = editor.getCaretModel().getOffset();
243     int line = editor.getDocument().getLineNumber(offset);
244     int start = editor.getDocument().getLineStartOffset(line);
245     int end = editor.getDocument().getLineEndOffset(line);
246     return new TextRange(start, end);
247   }
248
249   private static void scrollToCaret(@NotNull Editor e) {
250     e.getScrollingModel().scrollToCaret(ScrollType.CENTER);
251   }
252
253   @Override
254   public boolean canNavigate() {
255     return myFile.isValid();
256   }
257
258   @Override
259   public boolean canNavigateToSource() {
260     return canNavigate();
261   }
262
263   @NotNull
264   public Project getProject() {
265     return myProject;
266   }
267
268   public OpenFileDescriptor setUseCurrentWindow(boolean search) {
269     myUseCurrentWindow = search;
270     return this;
271   }
272
273   public boolean isUseCurrentWindow() {
274     return myUseCurrentWindow;
275   }
276
277   public void dispose() {
278     if (myRangeMarker != null) {
279       myRangeMarker.dispose();
280     }
281   }
282 }