2 * Copyright 2000-2009 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.
17 package com
.intellij
.codeInsight
.folding
.impl
;
19 import com
.intellij
.lang
.ASTNode
;
20 import com
.intellij
.lang
.folding
.FoldingBuilder
;
21 import com
.intellij
.lang
.folding
.FoldingDescriptor
;
22 import com
.intellij
.lang
.folding
.LanguageFolding
;
23 import com
.intellij
.openapi
.application
.ApplicationManager
;
24 import com
.intellij
.openapi
.diagnostic
.Logger
;
25 import com
.intellij
.openapi
.editor
.Document
;
26 import com
.intellij
.openapi
.editor
.Editor
;
27 import com
.intellij
.openapi
.editor
.FoldRegion
;
28 import com
.intellij
.openapi
.editor
.RangeMarker
;
29 import com
.intellij
.openapi
.editor
.impl
.FoldRegionImpl
;
30 import com
.intellij
.openapi
.fileEditor
.FileDocumentManager
;
31 import com
.intellij
.openapi
.fileEditor
.impl
.text
.CodeFoldingState
;
32 import com
.intellij
.openapi
.project
.Project
;
33 import com
.intellij
.openapi
.util
.JDOMExternalizable
;
34 import com
.intellij
.openapi
.util
.TextRange
;
35 import com
.intellij
.openapi
.util
.WriteExternalException
;
36 import com
.intellij
.openapi
.vfs
.VirtualFile
;
37 import com
.intellij
.psi
.PsiDocumentManager
;
38 import com
.intellij
.psi
.PsiElement
;
39 import com
.intellij
.psi
.PsiFile
;
40 import com
.intellij
.psi
.PsiManager
;
41 import com
.intellij
.util
.text
.StringTokenizer
;
42 import org
.jdom
.Element
;
43 import org
.jetbrains
.annotations
.NonNls
;
47 public class DocumentFoldingInfo
implements JDOMExternalizable
, CodeFoldingState
{
48 private static final Logger LOG
= Logger
.getInstance("#com.intellij.codeInsight.folding.impl.DocumentFoldingInfo");
50 private final Project myProject
;
51 private final VirtualFile myFile
;
53 private final ArrayList
<Object
> myPsiElementsOrRangeMarkers
= new ArrayList
<Object
>();
54 private final ArrayList
<Boolean
> myExpandedStates
= new ArrayList
<Boolean
>();
55 private final Map
<RangeMarker
, String
> myPlaceholderTexts
= new HashMap
<RangeMarker
,String
>();
56 private static final String DEFAULT_PLACEHOLDER
= "...";
57 @NonNls private static final String ELEMENT_TAG
= "element";
58 @NonNls private static final String SIGNATURE_ATT
= "signature";
59 @NonNls private static final String EXPANDED_ATT
= "expanded";
60 @NonNls private static final String MARKER_TAG
= "marker";
61 @NonNls private static final String DATE_ATT
= "date";
62 @NonNls private static final String PLACEHOLDER_ATT
= "placeholder";
64 public DocumentFoldingInfo(Project project
, Document document
) {
66 myFile
= FileDocumentManager
.getInstance().getFile(document
);
69 public void loadFromEditor(Editor editor
) {
72 PsiDocumentManager
.getInstance(myProject
).commitDocument(editor
.getDocument());
74 EditorFoldingInfo info
= EditorFoldingInfo
.get(editor
);
75 FoldRegion
[] foldRegions
= editor
.getFoldingModel().getAllFoldRegions();
76 for (FoldRegion region
: foldRegions
) {
77 PsiElement element
= info
.getPsiElement(region
);
78 boolean expanded
= region
.isExpanded();
79 boolean collapseByDefault
= element
!= null &&
80 FoldingPolicy
.isCollapseByDefault(element
) &&
81 !FoldingUtil
.caretInsideRange(editor
, new TextRange(region
.getStartOffset(), region
.getEndOffset()));
82 if (collapseByDefault
!= !expanded
|| element
== null) {
83 if (element
!= null) {
84 myPsiElementsOrRangeMarkers
.add(element
);
86 else if (region
.isValid()) {
87 myPsiElementsOrRangeMarkers
.add(region
);
88 String placeholderText
= region
.getPlaceholderText();
89 myPlaceholderTexts
.put(region
, placeholderText
);
91 myExpandedStates
.add(expanded ? Boolean
.TRUE
: Boolean
.FALSE
);
96 void setToEditor(Editor editor
) {
97 LOG
.assertTrue(ApplicationManager
.getApplication().isReadAccessAllowed());
98 final PsiManager psiManager
= PsiManager
.getInstance(myProject
);
99 if (psiManager
.isDisposed()) return;
101 if (!myFile
.isValid()) return;
102 final PsiFile psiFile
= psiManager
.findFile(myFile
);
103 if (psiFile
== null) return;
105 Map
<PsiElement
, FoldingDescriptor
> ranges
= null;
106 for(int i
= 0; i
< myPsiElementsOrRangeMarkers
.size(); i
++){
107 Object o
= myPsiElementsOrRangeMarkers
.get(i
);
108 if (o
instanceof PsiElement
) {
109 PsiElement element
= (PsiElement
)o
;
110 if (!element
.isValid()) continue;
112 if (ranges
== null) ranges
= buildRanges(editor
, psiFile
);
113 FoldingDescriptor descriptor
= ranges
.get(element
);
114 if (descriptor
== null) continue;
116 TextRange range
= descriptor
.getRange();
117 FoldRegion region
= FoldingUtil
.findFoldRegion(editor
, range
.getStartOffset(), range
.getEndOffset());
118 if (region
!= null) {
119 boolean state
= myExpandedStates
.get(i
).booleanValue();
120 region
.setExpanded(state
);
123 else if (o
instanceof RangeMarker
) {
124 RangeMarker marker
= (RangeMarker
)o
;
125 if (!marker
.isValid()) continue;
126 FoldRegion region
= FoldingUtil
.findFoldRegion(editor
, marker
.getStartOffset(), marker
.getEndOffset());
127 if (region
== null) {
128 String placeHolderText
= myPlaceholderTexts
.get(marker
);
129 region
= new FoldRegionImpl(editor
, marker
.getStartOffset(), marker
.getEndOffset(), placeHolderText
, null); //may fail to add in case intersecting region exists
130 if (!editor
.getFoldingModel().addFoldRegion(region
)) return;
133 boolean state
= myExpandedStates
.get(i
).booleanValue();
134 region
.setExpanded(state
);
137 LOG
.error("o = " + o
);
142 private static Map
<PsiElement
, FoldingDescriptor
> buildRanges(final Editor editor
, final PsiFile psiFile
) {
143 final FoldingBuilder foldingBuilder
= LanguageFolding
.INSTANCE
.forLanguage(psiFile
.getLanguage());
144 final ASTNode node
= psiFile
.getNode();
145 if (node
== null) return Collections
.emptyMap();
146 final FoldingDescriptor
[] descriptors
= LanguageFolding
.buildFoldingDescriptors(foldingBuilder
, psiFile
, editor
.getDocument(), true);
147 Map
<PsiElement
, FoldingDescriptor
> ranges
= new HashMap
<PsiElement
, FoldingDescriptor
>();
148 for (FoldingDescriptor descriptor
: descriptors
) {
149 final ASTNode ast
= descriptor
.getElement();
150 final PsiElement psi
= ast
.getPsi();
152 ranges
.put(psi
, descriptor
);
158 public void clear() {
159 myPsiElementsOrRangeMarkers
.clear();
160 myExpandedStates
.clear();
161 myPlaceholderTexts
.clear();
164 public void writeExternal(Element element
) throws WriteExternalException
{
165 PsiDocumentManager
.getInstance(myProject
).commitAllDocuments();
167 if (myPsiElementsOrRangeMarkers
.isEmpty()){
168 throw new WriteExternalException();
172 for(int i
= 0; i
< myPsiElementsOrRangeMarkers
.size(); i
++){
173 Object o
= myPsiElementsOrRangeMarkers
.get(i
);
174 Boolean state
= myExpandedStates
.get(i
);
175 if (o
instanceof PsiElement
){
176 PsiElement psiElement
= (PsiElement
)o
;
177 if (!psiElement
.isValid()) continue;
178 String signature
= FoldingPolicy
.getSignature(psiElement
);
179 if (signature
== null) continue;
181 PsiElement restoredElement
= FoldingPolicy
.restoreBySignature(psiElement
.getContainingFile(), signature
);
182 if (!psiElement
.equals(restoredElement
)){
183 restoredElement
= FoldingPolicy
.restoreBySignature(psiElement
.getContainingFile(), signature
);
184 LOG
.assertTrue(false, "element:" + psiElement
+ ", signature:" + signature
+ ", file:" + psiElement
.getContainingFile());
187 Element e
= new Element(ELEMENT_TAG
);
188 e
.setAttribute(SIGNATURE_ATT
, signature
);
189 e
.setAttribute(EXPANDED_ATT
, state
.toString());
190 element
.addContent(e
);
192 RangeMarker marker
= (RangeMarker
) o
;
193 Element e
= new Element(MARKER_TAG
);
195 date
= getTimeStamp();
197 if ("".equals(date
)) continue;
199 e
.setAttribute(DATE_ATT
, date
);
200 e
.setAttribute(EXPANDED_ATT
, state
.toString());
201 String signature
= Integer
.valueOf(marker
.getStartOffset()) + ":" + Integer
.valueOf(marker
.getEndOffset());
202 e
.setAttribute(SIGNATURE_ATT
, signature
);
203 String placeHolderText
= myPlaceholderTexts
.get(marker
);
204 e
.setAttribute(PLACEHOLDER_ATT
, placeHolderText
);
205 element
.addContent(e
);
210 public void readExternal(Element element
) {
211 myPsiElementsOrRangeMarkers
.clear();
212 myExpandedStates
.clear();
214 if (!myFile
.isValid()) return;
216 final Document document
= FileDocumentManager
.getInstance().getDocument(myFile
);
217 if (document
== null) return;
219 PsiFile psiFile
= PsiDocumentManager
.getInstance(myProject
).getPsiFile(document
);
220 if (psiFile
== null || !psiFile
.getViewProvider().isPhysical()) return;
223 for (final Object o
: element
.getChildren()) {
224 Element e
= (Element
)o
;
225 if (ELEMENT_TAG
.equals(e
.getName())) {
226 String signature
= e
.getAttributeValue(SIGNATURE_ATT
);
227 if (signature
== null) {
230 PsiElement restoredElement
= FoldingPolicy
.restoreBySignature(psiFile
, signature
);
231 if (restoredElement
!= null) {
232 myPsiElementsOrRangeMarkers
.add(restoredElement
);
233 myExpandedStates
.add(Boolean
.valueOf(e
.getAttributeValue(EXPANDED_ATT
)));
236 else if (MARKER_TAG
.equals(e
.getName())) {
238 date
= getTimeStamp();
240 if ("".equals(date
)) continue;
242 if (!date
.equals(e
.getAttributeValue(DATE_ATT
)) || FileDocumentManager
.getInstance().isDocumentUnsaved(document
)) continue;
243 StringTokenizer tokenizer
= new StringTokenizer(e
.getAttributeValue(SIGNATURE_ATT
), ":");
245 int start
= Integer
.valueOf(tokenizer
.nextToken()).intValue();
246 int end
= Integer
.valueOf(tokenizer
.nextToken()).intValue();
247 if (start
< 0 || end
>= document
.getTextLength() || start
> end
) continue;
248 RangeMarker marker
= document
.createRangeMarker(start
, end
);
249 myPsiElementsOrRangeMarkers
.add(marker
);
250 myExpandedStates
.add(Boolean
.valueOf(e
.getAttributeValue(EXPANDED_ATT
)));
251 String placeHolderText
= e
.getAttributeValue(PLACEHOLDER_ATT
);
252 if (placeHolderText
== null) placeHolderText
= DEFAULT_PLACEHOLDER
;
253 myPlaceholderTexts
.put(marker
, placeHolderText
);
255 catch (NoSuchElementException exc
) {
260 throw new IllegalStateException("unknown tag: " + e
.getName());
265 private String
getTimeStamp() {
266 if (!myFile
.isValid()) return "";
267 return Long
.toString(myFile
.getTimeStamp());