001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.php.editor;
042:
043: import java.util.LinkedList;
044: import java.util.List;
045:
046: import javax.swing.Action;
047: import javax.swing.text.BadLocationException;
048: import javax.swing.text.Caret;
049: import javax.swing.text.Document;
050: import javax.swing.text.JTextComponent;
051: import javax.swing.text.TextAction;
052:
053: import org.netbeans.modules.gsf.api.OffsetRange;
054: import org.netbeans.api.lexer.Token;
055: import org.netbeans.editor.BaseDocument;
056: import org.netbeans.editor.SyntaxSupport;
057: import org.netbeans.editor.Utilities;
058: import org.netbeans.editor.ext.html.HTMLLexerFormatter;
059: import org.netbeans.editor.ext.html.HTMLSyntaxSupport;
060: import org.netbeans.modules.editor.html.HTMLKit;
061: import org.netbeans.modules.editor.indent.api.IndentUtils;
062: import org.netbeans.modules.php.doc.DocumentationRegistry;
063: import org.netbeans.modules.php.lexer.PhpTokenId;
064: import org.openide.util.HelpCtx;
065:
066: /**
067: * @author ads
068: *
069: */
070: public class PhpKit extends HTMLKit {
071:
072: private static final long serialVersionUID = 1858280307247622290L;
073:
074: private static final String LINE_COMMENT = "//"; // NOI18N
075:
076: /* (non-Javadoc)
077: * @see org.netbeans.modules.languages.dataobject.LanguagesEditorKit#initDocument(javax.swing.text.Document)
078: */
079: @Override
080: protected void initDocument(Document doc) {
081: super .initDocument(doc);
082: }
083:
084: public HelpCtx getHelpCtx() {
085: return new HelpCtx(PhpKit.class);
086: }
087:
088: public PhpKit() {
089: super (PhpTokenId.MIME_TYPE);
090: }
091:
092: public String getContentType() {
093: return PhpTokenId.MIME_TYPE;
094: }
095:
096: public Object clone() {
097: return new PhpKit();
098: }
099:
100: public SyntaxSupport createSyntaxSupport(BaseDocument doc) {
101: doc.putProperty(
102: HTMLLexerFormatter.HTML_FORMATTER_ACTS_ON_TOP_LEVEL,
103: Boolean.TRUE);
104: return new HTMLSyntaxSupport(doc) {
105:
106: public int[] findMatchingBlock(int offset,
107: boolean simpleSearch) throws BadLocationException {
108: OffsetRange range = BracketCompletion.findMatching(
109: getDocument(), offset);
110: if (range == null || range == OffsetRange.NONE) {
111: return super
112: .findMatchingBlock(offset, simpleSearch);
113: } else {
114: return new int[] { range.getStart(), range.getEnd() };
115: }
116: }
117: };
118: }
119:
120: /*protected void initDocument(BaseDocument doc) {
121:
122: // XXX This appears in JavaKit, not sure why, but doing it just in case.
123: //do not ask why, fire bug in the IZ:
124: CodeTemplateManager.get(doc);
125: }*/
126:
127: @Override
128: protected Action[] createActions() {
129: List<Action> actions = new LinkedList<Action>();
130:
131: for (Action htmlAction : super .createActions()) {
132: actions.add(htmlAction);
133: }
134:
135: actions.add(new PhpDefaultKeyTypedAction());
136: actions.add(new PhpInsertBreakAction());
137: actions
138: .add(new PhpDeleteCharAction(deletePrevCharAction,
139: false));
140: //actions.add(new PhpDeleteCharAction(deleteNextCharAction, true));
141:
142: actions.add(new CommentAction(LINE_COMMENT));
143: actions.add(new UncommentAction(LINE_COMMENT));
144:
145: /*Collection<? extends EditorAction> extraActions = Lookup.getDefault().lookupAll(EditorAction.class);
146: for (EditorAction action : extraActions) {
147: actions.add(new EditorActionWrapper(action));
148: }*/
149:
150: /*actions.add(new InstantRenameAction());
151: actions.add(new PrettyPrintAction());
152: actions.add(new GenericGoToDeclarationAction());
153: actions.add(new GenericGenerateGoToPopupAction());
154:
155: actions.add(new SelectCodeElementAction(selectNextElementAction, true));
156: actions.add(new SelectCodeElementAction(selectPreviousElementAction, false));*/
157:
158: return TextAction.augmentList(super .createActions(), actions
159: .toArray(new Action[actions.size()]));
160: }
161:
162: /*
163: * Start low priority task for accessing to documentation.
164: * It loads categories with their function list and loads
165: * names of function .
166: * Documentation for each function will load on demand.
167: */
168: static {
169: Thread thread = new Thread() {
170: public void run() {
171: DocumentationRegistry.getInstance().getCategories();
172: }
173: };
174: thread.setPriority(Thread.MIN_PRIORITY);
175: thread.start();
176: }
177:
178: public static class PhpDefaultKeyTypedAction extends
179: ExtDefaultKeyTypedAction {
180:
181: private static final long serialVersionUID = 8779107741901181392L;
182:
183: protected void insertString(BaseDocument doc, int dotPos,
184: Caret caret, String str, boolean overwrite)
185: throws BadLocationException {
186: char insertedChar = str.charAt(0);
187: if (TokenUtils.checkPhp(doc, dotPos)
188: && (insertedChar == '\"' || insertedChar == '\'' || insertedChar == '`')) {
189: boolean inserted = BracketCompletion.completeQuote(doc,
190: dotPos, caret, insertedChar);
191: if (inserted) {
192: caret.setDot(dotPos + 1);
193: } else {
194: super .insertString(doc, dotPos, caret, str,
195: overwrite);
196: }
197: } else {
198: super .insertString(doc, dotPos, caret, str, overwrite);
199: if (TokenUtils.checkPhp(doc, dotPos)) {
200: BracketCompletion.charInserted(doc, dotPos, caret,
201: insertedChar);
202: }
203:
204: // reformat line when the bracket is opened or closed
205: if (insertedChar == '}' || insertedChar == '{') {
206: doc.atomicLock();
207: try {
208: int startOffset = Utilities.getRowStart(doc,
209: dotPos);
210: int endOffset = Utilities
211: .getRowEnd(doc, dotPos);
212: FormattingUtils.reformat(doc, startOffset,
213: endOffset);
214: } finally {
215: doc.atomicUnlock();
216: }
217: }
218: }
219: }
220:
221: protected void replaceSelection(JTextComponent target,
222: int dotPos, Caret caret, String str, boolean overwrite)
223: throws BadLocationException {
224: char insertedChar = str.charAt(0);
225: // the same cast was already performed in parent
226: BaseDocument doc = (BaseDocument) target.getDocument();
227: if (TokenUtils.checkPhp(doc, dotPos)
228: && (insertedChar == '\"' || insertedChar == '\'' || insertedChar == '`')) {
229: removeSelection(doc, dotPos, caret);
230:
231: int caretPosition = caret.getDot();
232: boolean inserted = BracketCompletion.completeQuote(doc,
233: caretPosition, caret, insertedChar);
234: if (inserted) {
235: caret.setDot(caretPosition + 1);
236: } else {
237: doc.insertString(caretPosition, str, null);
238: }
239: } else {
240: super .replaceSelection(target, dotPos, caret, str,
241: overwrite);
242: if (TokenUtils.checkPhp(doc, dotPos)) {
243: BracketCompletion.charInserted(doc,
244: caret.getDot() - 1, caret, insertedChar);
245: }
246: }
247: }
248:
249: /**
250: * removes selected block if any.
251: * @returns true if something was removed. false otherwise
252: */
253: private boolean removeSelection(BaseDocument doc, int dotPos,
254: Caret caret) throws BadLocationException {
255: int p0 = Math.min(caret.getDot(), caret.getMark());
256: int p1 = Math.max(caret.getDot(), caret.getMark());
257: if (p0 != p1) {
258: doc.remove(p0, p1 - p0);
259: return true;
260: }
261: return false;
262: }
263: }
264:
265: public static class PhpInsertBreakAction extends InsertBreakAction {
266:
267: private static final long serialVersionUID = 1669306776471076647L;
268:
269: private static final String STATE_DOT_CONNECTED_STRING = "string-connected-with-dot"; // NOI18N
270: private static final String STATE_MULTILINE_STRING = "string-multiline"; // NOI18N
271: private static final String STATE_OPENED_BRACE = "opened-brace"; // NOI18N
272: private static final String STATE_EOD_STRING = "string-eod"; // NOI18N
273:
274: @Override
275: protected Object beforeBreak(JTextComponent target,
276: BaseDocument doc, Caret caret) {
277: int dotPos = caret.getDot();
278: if (!TokenUtils.checkPhp(doc, dotPos)) {
279: return null;
280: }
281: if (BracketCompletion.posWithinString(doc, dotPos)) {
282: if (BracketCompletion.useDotConnectorInStringSetting()) {
283: insertDotConnectorBeforeBreak(doc, caret, dotPos);
284: return STATE_DOT_CONNECTED_STRING;
285: } else {
286: return STATE_MULTILINE_STRING;
287: }
288: } else if (BracketCompletion.isNotClosedEOD(doc, dotPos)) {
289: try {
290: closeEODBeforeBreak(doc, caret, dotPos);
291: return STATE_EOD_STRING;
292: } catch (BadLocationException ex) {
293: }
294: } else {
295: try {
296: if (BracketCompletion.isAddRightBrace(doc, dotPos)) {
297: BracketCompletion.addRightBrace(doc, caret,
298: dotPos);
299: return STATE_OPENED_BRACE;
300: }
301: } catch (BadLocationException ex) {
302: }
303: }
304: return null;
305: }
306:
307: @Override
308: protected void afterBreak(JTextComponent target,
309: BaseDocument doc, Caret caret, Object cookie) {
310: if (STATE_DOT_CONNECTED_STRING.equals(cookie)) {
311: if (BracketCompletion.useDotConnectorInStringSetting()) {
312: insertDotConnectorAfterBreak(caret);
313: }
314: }
315: // do nothing for STATE_MULTILINE_STRING and STATE_OPENED_BRACE
316: }
317:
318: private Integer closeEODBeforeBreak(BaseDocument doc,
319: Caret caret, int dotPos) throws BadLocationException {
320: String eodLabel = BracketCompletion.getEODStringLabel(doc,
321: dotPos);
322: if (eodLabel.length() > 0) {
323: // do not leave trailing spaces on the line
324: cursorBeforeTrailingSpaces(doc, caret, dotPos);
325:
326: doc.insertString(dotPos, eodLabel
327: + TokenUtils.SEMICOLON, null);
328: FormattingUtils.indentNewLine(doc, dotPos);
329: caret.setDot(dotPos);
330: }
331: return dotPos;
332: }
333:
334: private void cursorBeforeTrailingSpaces(BaseDocument doc,
335: Caret caret, int dotPos) throws BadLocationException {
336: int lastNonWhite = Utilities.getFirstNonWhiteBwd(doc,
337: dotPos);
338: if (lastNonWhite < dotPos) {
339: caret.setDot(lastNonWhite + 1);
340: dotPos = caret.getDot();
341: }
342: }
343:
344: private Integer insertDotConnectorBeforeBreak(BaseDocument doc,
345: Caret caret, int dotPos) {
346: try {
347: Token token = TokenUtils.getPhpToken(doc, dotPos);
348: char quote = token.text().charAt(0);
349: doc.insertString(dotPos, quote + ". " + quote, null); // NOI18N
350: dotPos += 3;
351: caret.setDot(dotPos);
352: return dotPos;
353: } catch (BadLocationException ex) {
354: }
355: return null;
356: }
357:
358: private void insertDotConnectorAfterBreak(Caret caret) {
359: int nowDotPos = caret.getDot();
360: caret.setDot(nowDotPos + 1);
361: }
362:
363: }
364:
365: public static class PhpDeleteCharAction extends ExtDeleteCharAction {
366:
367: private static final long serialVersionUID = 7261622068721435459L;
368:
369: public PhpDeleteCharAction(String nm, boolean nextChar) {
370: super (nm, nextChar);
371: }
372:
373: @Override
374: protected void charDeleted(BaseDocument doc, int dotPos,
375: Caret caret, char ch) throws BadLocationException {
376: charBackspaced(doc, dotPos, caret, ch);
377: }
378:
379: @Override
380: protected void charBackspaced(BaseDocument doc, int dotPos,
381: Caret caret, char ch) throws BadLocationException {
382: if (TokenUtils.checkPhp(doc, dotPos)) {
383: BracketCompletion
384: .charBackspaced(doc, dotPos, caret, ch);
385: }
386: }
387:
388: }
389: }
|