KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > ant > internal > ui > editor > AntAutoIndentStrategy


1 /*******************************************************************************
2  * Copyright (c) 2000, 2004 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Common Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11  
12 package org.eclipse.ant.internal.ui.editor;
13 import org.eclipse.ant.internal.ui.editor.formatter.XmlDocumentFormatter;
14 import org.eclipse.ant.internal.ui.editor.model.AntElementNode;
15 import org.eclipse.ant.internal.ui.editor.outline.AntModel;
16 import org.eclipse.ant.internal.ui.model.AntUIPlugin;
17 import org.eclipse.jface.text.BadLocationException;
18 import org.eclipse.jface.text.DefaultAutoIndentStrategy;
19 import org.eclipse.jface.text.Document;
20 import org.eclipse.jface.text.DocumentCommand;
21 import org.eclipse.jface.text.IDocument;
22 import org.eclipse.jface.text.IRegion;
23 import org.eclipse.jface.text.TextUtilities;
24 import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
25
26 /**
27  * Auto indent strategy for Ant build files
28  */

29 public class AntAutoIndentStrategy extends DefaultAutoIndentStrategy {
30     
31     private AntModel fModel;
32     private int fAccumulatedChange= 0;
33     
34     public AntAutoIndentStrategy(AntModel model) {
35         fModel= model;
36     }
37     
38     /**
39      * Sets the indentation based on the Ant element node that contains the offset
40      * of the document command.
41      *
42      * @param d the document to work on
43      * @param c the command to deal with
44      */

45     private synchronized void autoIndentAfterNewLine(IDocument d, DocumentCommand c) {
46         
47         if (c.offset == -1 || d.getLength() == 0 || fModel.getProjectNode(false) == null) {
48             return;
49         }
50         
51         int position= (c.offset == d.getLength() ? c.offset - 1 : c.offset);
52         AntElementNode node= fModel.getProjectNode(false).getNode(position - fAccumulatedChange);
53         if (node == null) {
54             return;
55         }
56         
57         try {
58             StringBuffer JavaDoc correct= XmlDocumentFormatter.getLeadingWhitespace(node.getOffset(), d);
59             if (!nextNodeIsEndTag(c.offset, d)) {
60                 correct.append(XmlDocumentFormatter.createIndent());
61             }
62             StringBuffer JavaDoc buf= new StringBuffer JavaDoc(c.text);
63             buf.append(correct);
64             fAccumulatedChange+= buf.length();
65             
66             int line= d.getLineOfOffset(position);
67             IRegion reg= d.getLineInformation(line);
68             int lineEnd= reg.getOffset() + reg.getLength();
69             int contentStart= findEndOfWhiteSpace(d, c.offset, lineEnd);
70             
71             c.length= Math.max(contentStart - c.offset, 0);
72             c.caretOffset= c.offset + buf.length();
73             c.shiftsCaret= false;
74             c.text= buf.toString();
75     
76         } catch (BadLocationException e) {
77             AntUIPlugin.log(e);
78         }
79     }
80     
81     private boolean nextNodeIsEndTag(int offset, IDocument document) {
82         if (offset + 1 > document.getLength()) {
83             return false;
84         }
85         try {
86             IRegion lineRegion= document.getLineInformationOfOffset(offset);
87             offset= findEndOfWhiteSpace(document, offset, lineRegion.getOffset() + lineRegion.getLength());
88             String JavaDoc nextChars= document.get(offset, 2).trim();
89             if ("</".equals(nextChars) || "/>".equals(nextChars)) { //$NON-NLS-1$ //$NON-NLS-2$
90
return true;
91             }
92         } catch (BadLocationException e) {
93         }
94         return false;
95     }
96
97     /* (non-Javadoc)
98      * @see org.eclipse.jface.text.IAutoEditStrategy#customizeDocumentCommand(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.DocumentCommand)
99      */

100     public void customizeDocumentCommand(IDocument d, DocumentCommand c) {
101         
102         if (c.length == 0 && c.text != null && isLineDelimiter(d, c.text)) {
103             autoIndentAfterNewLine(d, c);
104         } else if (c.text.length() > 1) {
105             smartPaste(d, c);
106         }
107     }
108     
109      private boolean isLineDelimiter(IDocument document, String JavaDoc text) {
110         String JavaDoc[] delimiters= document.getLegalLineDelimiters();
111         if (delimiters != null)
112             return TextUtilities.equals(delimiters, text) > -1;
113         return false;
114     }
115     
116     public synchronized void reconciled() {
117         fAccumulatedChange= 0;
118     }
119     
120     private void smartPaste(IDocument document, DocumentCommand command) {
121         try {
122             if (command.offset == -1 || document.getLength() == 0 || fModel.getProjectNode(false) == null) {
123                 return;
124             }
125             String JavaDoc origChange= command.text;
126             int position= (command.offset == document.getLength() ? command.offset - 1 : command.offset);
127             AntElementNode node= fModel.getProjectNode(false).getNode(position - fAccumulatedChange);
128             if (node == null) {
129                 return;
130             }
131             
132             // eat any WS before the insertion to the beginning of the line
133
int firstLine= 1; // don't format the first line if it has other content before it
134
IRegion line= document.getLineInformationOfOffset(command.offset);
135             String JavaDoc notSelected= document.get(line.getOffset(), command.offset - line.getOffset());
136             if (notSelected.trim().length() == 0) {
137                 command.length += notSelected.length();
138                 command.offset= line.getOffset();
139                 firstLine= 0;
140             }
141             
142             // handle the indentation computation inside a temporary document
143
Document temp= new Document(command.text);
144             
145             // indent the first and second line
146
// compute the relative indentation difference from the second line
147
// (as the first might be partially selected) and use the value to
148
// indent all other lines.
149
boolean isIndentDetected= false;
150             StringBuffer JavaDoc addition= new StringBuffer JavaDoc();
151             int insertLength= 0;
152             int lines= temp.getNumberOfLines();
153             for (int l= firstLine; l < lines; l++) { // we don't change the number of lines while adding indents
154

155                 IRegion r= temp.getLineInformation(l);
156                 int lineOffset= r.getOffset();
157                 int lineLength= r.getLength();
158                 
159                 if (lineLength == 0) { // don't modify empty lines
160
continue;
161                 }
162                 
163                 if (!isIndentDetected){
164                     
165                     // indent the first pasted line
166
StringBuffer JavaDoc current= XmlDocumentFormatter.getLeadingWhitespace(lineOffset, temp);
167                     StringBuffer JavaDoc correct= XmlDocumentFormatter.getLeadingWhitespace(node.getOffset(), document);
168                     correct.append(XmlDocumentFormatter.createIndent());
169                     
170                     insertLength= subtractIndent(correct, current, addition);
171                     isIndentDetected= true;
172                 }
173                 
174                 // relatively indent all pasted lines
175
if (insertLength > 0) {
176                     addIndent(temp, l, addition);
177                 } else if (insertLength < 0) {
178                     cutIndent(temp, l, -insertLength);
179                 }
180             }
181             
182             // modify the command
183
if (!origChange.equals(temp.get())) {
184                 fAccumulatedChange+= temp.getLength();
185                 command.text= temp.get();
186             }
187             
188         } catch (BadLocationException e) {
189             AntUIPlugin.log(e);
190         }
191     }
192     
193     /**
194      * Indents line <code>line</code> in <code>document</code> with <code>indent</code>.
195      * Leaves leading comment signs alone.
196      *
197      * @param document the document
198      * @param line the line
199      * @param indent the indentation to insert
200      * @throws BadLocationException on concurrent document modification
201      */

202     private void addIndent(Document document, int line, CharSequence JavaDoc indent) throws BadLocationException {
203         IRegion region= document.getLineInformation(line);
204         int insert= region.getOffset();
205         
206         // insert indent
207
document.replace(insert, 0, indent.toString());
208     }
209     
210     /**
211      * Cuts the visual equivalent of <code>toDelete</code> characters out of the
212      * indentation of line <code>line</code> in <code>document</code>.
213      *
214      * @param document the document
215      * @param line the line
216      * @param toDelete the number of space equivalents to delete.
217      * @throws BadLocationException on concurrent document modification
218      */

219     private void cutIndent(Document document, int line, int toDelete) throws BadLocationException {
220         IRegion region= document.getLineInformation(line);
221         int from= region.getOffset();
222         int endOffset= region.getOffset() + region.getLength();
223         
224         int to= from;
225         while (toDelete > 0 && to < endOffset) {
226             char ch= document.getChar(to);
227             if (!Character.isWhitespace(ch))
228                 break;
229             toDelete -= computeVisualLength(ch);
230             if (toDelete >= 0) {
231                 to++;
232             } else {
233                 break;
234             }
235         }
236         
237         document.replace(from, to - from, null);
238     }
239     
240     /**
241      * Returns the visual length of a given character taking into
242      * account the visual tabulator length.
243      *
244      * @param ch the character to measure
245      * @return the visual length of <code>ch</code>
246      */

247     private int computeVisualLength(char ch) {
248         if (ch == '\t') {
249             return getVisualTabLengthPreference();
250         }
251             
252         return 1;
253     }
254     
255     /**
256      * Returns the visual length of a given <code>CharSequence</code> taking into
257      * account the visual tabulator length.
258      *
259      * @param seq the string to measure
260      * @return the visual length of <code>seq</code>
261      */

262     private int computeVisualLength(CharSequence JavaDoc seq) {
263         int size= 0;
264         int tablen= getVisualTabLengthPreference();
265         
266         for (int i= 0; i < seq.length(); i++) {
267             char ch= seq.charAt(i);
268             if (ch == '\t') {
269                 size += tablen - size % tablen;
270             } else {
271                 size++;
272             }
273         }
274         return size;
275     }
276     
277     /**
278      * Computes the difference of two indentations and returns the difference in
279      * length of current and correct. If the return value is positive, <code>addition</code>
280      * is initialized with a substring of that length of <code>correct</code>.
281      *
282      * @param correct the correct indentation
283      * @param current the current indentation (might contain non-whitespace)
284      * @param difference a string buffer - if the return value is positive, it will be cleared and set to the substring of <code>current</code> of that length
285      * @return the difference in length of <code>correct</code> and <code>current</code>
286      */

287     private int subtractIndent(CharSequence JavaDoc correct, CharSequence JavaDoc current, StringBuffer JavaDoc difference) {
288         int c1= computeVisualLength(correct);
289         int c2= computeVisualLength(current);
290         int diff= c1 - c2;
291         if (diff <= 0) {
292             return diff;
293         }
294         
295         difference.setLength(0);
296         int len= 0, i= 0;
297         while (len < diff) {
298             char c= correct.charAt(i++);
299             difference.append(c);
300             len += computeVisualLength(c);
301         }
302         
303         return diff;
304     }
305     
306     /**
307      * The preference setting for the visual tabulator display.
308      *
309      * @return the number of spaces displayed for a tabulator in the editor
310      */

311     private int getVisualTabLengthPreference() {
312         return AntUIPlugin.getDefault().getPreferenceStore().getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH);
313     }
314 }
Popular Tags