/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2010 Oracle and/or its affiliates. All rights reserved. * * Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. * * Contributor(s): * * Portions Copyrighted 2008 Sun Microsystems, Inc. */ package org.netbeans.modules.ruby.rhtml.editor; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.netbeans.api.lexer.Token; import org.netbeans.api.lexer.TokenHierarchy; import org.netbeans.api.lexer.TokenSequence; import org.netbeans.modules.parsing.api.Embedding; import org.netbeans.modules.parsing.api.Snapshot; import org.netbeans.modules.parsing.spi.EmbeddingProvider; import org.netbeans.modules.parsing.spi.SchedulerTask; import org.netbeans.modules.parsing.spi.TaskFactory; import org.netbeans.modules.ruby.rhtml.lexer.api.RhtmlTokenId; public final class RubyEmbeddingProvider extends EmbeddingProvider { public static class Factory extends TaskFactory { @Override public Collection<? extends SchedulerTask> create(Snapshot snapshot) { if (snapshot.getSource().getMimeType().equals(RhtmlTokenId.MIME_TYPE)) { return Collections.singleton(new RubyEmbeddingProvider()); } else { return Collections.<SchedulerTask>emptyList(); } } } public static final String RUBY_MIMETYPE = "text/x-ruby"; //NOI18N @Override public List<Embedding> getEmbeddings(Snapshot snapshot) { List<Embedding> ems = extractRuby(snapshot); if (ems.isEmpty()) { return Collections.emptyList(); } else { return Collections.singletonList(Embedding.create(ems)); } } @Override public int getPriority() { return 10; } @Override public void cancel() { //no cancel supported } private List<Embedding> extractRuby(Snapshot snapshot) { List<Embedding> ems = new ArrayList<Embedding>(40); // Add a super class such that code completion, goto declaration etc. // knows where to pull the various link_to etc. methods from // Pretend that this code is an extension to ActionView::Base such that // code completion, go to declaration etc. sees the inherited methods from // ActionView -- link_to and friends. ems.add(snapshot.create("class ActionView::Base\n", RUBY_MIMETYPE)); // NOI18N // TODO Try to include the helper class as well as the controller fields too; // for now this logic is hardcoded into Ruby's code completion engine (CodeCompleter) // Erubis uses _buf; I've seen eruby using something else (_erbout?) ems.add(snapshot.create("_buf='';", RUBY_MIMETYPE)); // NOI18N TokenHierarchy tokenHierarchy = TokenHierarchy.create(snapshot.getText(), RhtmlTokenId.language()); TokenSequence<RhtmlTokenId> tokenSequence = tokenHierarchy.tokenSequence(); boolean skipNewline = false; while (tokenSequence.moveNext()) { Token<RhtmlTokenId> token = tokenSequence.token(); if (token.id() == RhtmlTokenId.HTML) { int sourceStart = token.offset(tokenHierarchy); int sourceEnd = sourceStart + token.length(); String text = token.text().toString(); // If there is leading whitespace in this token followed by a newline, // emit it directly first, then insert my buffer append. Otherwise, // insert a semicolon if we're on the same line as the previous output. boolean found = false; int i = 0; for (; i < text.length(); i++) { char c = text.charAt(i); if (c == '\n') { i++; // include it found = true; break; } else if (!Character.isWhitespace(c)) { break; } } if (found) { ems.add(snapshot.create(text.substring(0, i), RUBY_MIMETYPE)); text = text.substring(i); } else { ems.add(snapshot.create(";", RUBY_MIMETYPE)); } ems.add(snapshot.create("_buf << '", RUBY_MIMETYPE)); if (skipNewline && text.startsWith("\n")) { // NOI18N text = text.substring(1); sourceEnd--; } // Escape 's in the document so they don't escape out of the ruby code // I don't have to do this on lines that are in comments... But no big harm text = text.replace("'", "\\'"); ems.add(snapshot.create(text, RUBY_MIMETYPE)); // TODO: This "\n" shouldn't be there if the next "<%" is a "<%-" ! ems.add(snapshot.create("';\n", RUBY_MIMETYPE)); // NOI18N skipNewline = false; } else if (token.id() == RhtmlTokenId.RUBY) { int sourceStart = token.offset(tokenHierarchy); int sourceEnd = sourceStart + token.length(); String text = token.text().toString(); skipNewline = false; if (text.endsWith("-")) { // NOI18N skipNewline = true; } ems.add(snapshot.create(sourceStart, sourceEnd - sourceStart - (skipNewline ? 1 : 0), RUBY_MIMETYPE)); if (tokenSequence.moveNext()) { Token<RhtmlTokenId> nextToken = tokenSequence.token(); if (nextToken != null && nextToken.id() == RhtmlTokenId.DELIMITER) { // Insert a semicolon if there is something else on this line int delimiterEnd = tokenSequence.offset() + nextToken.length(); if (delimiterEnd <= snapshot.getText().length()) { for (int i = delimiterEnd; i < snapshot.getText().length(); i++) { char c = snapshot.getText().charAt(i); if (c == '\n') { break; } else if (!Character.isWhitespace(c)) { // Yep, we have more stuff on this line ems.add(snapshot.create(";", RUBY_MIMETYPE)); break; } } } } tokenSequence.movePrevious(); } skipNewline = false; } else if (token.id() == RhtmlTokenId.RUBY_EXPR) { ems.add(snapshot.create("_buf << (", RUBY_MIMETYPE)); // NOI18N int sourceStart = token.offset(tokenHierarchy); int sourceEnd = sourceStart + token.length(); String text = token.text().toString(); skipNewline = false; if (text.endsWith("-")) { // NOI18N skipNewline = true; } ems.add(snapshot.create(sourceStart, sourceEnd - sourceStart - (skipNewline ? 1 : 0), RUBY_MIMETYPE)); // Make code sanitizing work better: buffer.append("\n).to_s;"); // NOI18N ems.add(snapshot.create(").to_s;", RUBY_MIMETYPE)); // NOI18N } } // Close off the class // eruby also ends with this statement: _buf.to_s ems.add(snapshot.create("\nend\n", RUBY_MIMETYPE)); // NOI18N return ems; } }