|
From: <caw...@us...> - 2007-03-15 12:47:29
|
Revision: 2177
http://svn.sourceforge.net/rubyeclipse/?rev=2177&view=rev
Author: cawilliams
Date: 2007-03-15 05:46:53 -0700 (Thu, 15 Mar 2007)
Log Message:
-----------
Revert some of Mirko's changes and merge some into my own solution for closing blocks. Block closing
(and auto-indenting) are now handled by RubyAutoIndentStrategy, which is set in RubySourceViewerConfiguration.
There's a way to toggle turning it on and off in the preferences Ui for "Ruby -> Typing".
By default it is turned on.
Modified Paths:
--------------
trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/rubyeditor/RubyEditor.java
trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/ui/PreferenceConstants.java
trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/ui/text/RubySourceViewerConfiguration.java
Added Paths:
-----------
trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/RubyIndenter.java
trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/ruby/RubyAutoIndentStrategy.java
Modified: trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/rubyeditor/RubyEditor.java
===================================================================
--- trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/rubyeditor/RubyEditor.java 2007-03-14 20:52:14 UTC (rev 2176)
+++ trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/rubyeditor/RubyEditor.java 2007-03-15 12:46:53 UTC (rev 2177)
@@ -127,8 +127,6 @@
private final static String CLOSE_BRACKETS= PreferenceConstants.EDITOR_CLOSE_BRACKETS;
/** Preference key for automatically closing braces */
private final static String CLOSE_BRACES= PreferenceConstants.EDITOR_CLOSE_BRACES;
- /** Preference key for automatically 'end'ing statements */
- private final static String END_STATEMENTS= PreferenceConstants.EDITOR_END_STATEMENTS;
/** Preference key for code formatter tab size */
private final static String CODE_FORMATTER_TAB_SIZE= DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE;
/** Preference key for inserting spaces rather than tabs */
@@ -178,7 +176,6 @@
private FoldingActionGroup fFoldingGroup;
private BracketInserter fBracketInserter = new BracketInserter();
- private EndInserter fEndInserter = new EndInserter();
private CompositeActionGroup fActionGroups;
private CompositeActionGroup fContextMenuGroup;
@@ -312,13 +309,10 @@
boolean closeBrackets= preferenceStore.getBoolean(CLOSE_BRACKETS);
boolean closeBraces= preferenceStore.getBoolean(CLOSE_BRACES);
boolean closeStrings= preferenceStore.getBoolean(CLOSE_STRINGS);
- boolean endStatements= preferenceStore.getBoolean(END_STATEMENTS);
fBracketInserter.setCloseBracketsEnabled(closeBrackets);
fBracketInserter.setCloseBracesEnabled(closeBraces);
fBracketInserter.setCloseStringsEnabled(closeStrings);
- fEndInserter.setEndStatementsEnabled(endStatements);
((ITextViewerExtension) sourceViewer).prependVerifyKeyListener(fBracketInserter);
- ((ITextViewerExtension) sourceViewer).prependVerifyKeyListener(fEndInserter);
}
}
@@ -626,7 +620,6 @@
ISourceViewer sourceViewer= getSourceViewer();
if (sourceViewer instanceof ITextViewerExtension) {
((ITextViewerExtension) sourceViewer).removeVerifyKeyListener(fBracketInserter);
- ((ITextViewerExtension) sourceViewer).removeVerifyKeyListener(fEndInserter);
}
if (fProjectionModelUpdater != null) {
@@ -769,12 +762,7 @@
if (CLOSE_STRINGS.equals(property)) {
fBracketInserter.setCloseStringsEnabled(getPreferenceStore().getBoolean(property));
return;
- }
-
- if (END_STATEMENTS.equals(property)) {
- fEndInserter.setEndStatementsEnabled(getPreferenceStore().getBoolean(property));
- return;
- }
+ }
AdaptedSourceViewer sourceViewer= (AdaptedSourceViewer) getSourceViewer();
if (sourceViewer == null)
@@ -1057,68 +1045,6 @@
}
}
-
- private class EndInserter implements VerifyKeyListener {
-
- /** Pattern to match lines with statements that can be completed with 'end' */
- private final Pattern openBlockPattern =
- Pattern.compile("(\\s*)" + // Capture the space before the statement, we need it to indent 'end'
- "((def|class|module)\\s.*" + // Either we look for one of these statements
- "|.*[\\S].*do[\\w|\\s]*)" + // or for an iterator, which needs at least one none-space character and 'do' with optional arguments.
- "[^(end)]"); // And it should not contain end already.
-
- private boolean endStatements;
-
- public void verifyKey(VerifyEvent event) {
- if (!event.doit || !endStatements) return;
-
- switch (event.character) {
- case '\n':
- case '\r':
- break;
- default:
- return;
- }
-
- final IDocument document = getSourceViewer().getDocument();
- final int offset = getSourceViewer().getSelectedRange().x;
- final int length = getSourceViewer().getSelectedRange().y;
- try {
- IRegion startLine = document.getLineInformationOfOffset(offset);
- String lineContent = document.get(startLine.getOffset(), startLine.getLength());
- Matcher matched = openBlockPattern.matcher(lineContent);
- if(matched.matches()) {
- String baseIndentation = matched.group(1); // 1 marks the spaces in front of the statement
- String bodyIndentation = addOneIndentationLevel(baseIndentation);
-
- String lineDelimiter = Platform.getPreferencesService().getString(Platform.PI_RUNTIME, Platform.PREF_LINE_SEPARATOR, null, null);
-
- String body = event.character + bodyIndentation + lineDelimiter + baseIndentation + "end";
- document.replace(offset, length, body);
- getSourceViewer().setSelectedRange(offset + bodyIndentation.length() + 1 /*for the newline char*/, 0);
- event.doit = false;
- }
- } catch (BadLocationException e) {
- RubyPlugin.log(e);
- }
- }
-
- private String addOneIndentationLevel(String indentation) {
- if(RubyCore.getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR).equals(RubyCore.SPACE)) {
- for(int i = 0; i < Integer.parseInt(RubyCore.getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE)); i++) {
- indentation += ' ';
- }
- } else {
- indentation += '\t';
- }
- return indentation;
- }
-
- public void setEndStatementsEnabled(boolean endStatements) {
- this.endStatements = endStatements;
- }
- }
-
private class BracketInserter implements VerifyKeyListener, ILinkedModeListener {
private boolean fCloseBrackets = true;
Added: trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/RubyIndenter.java
===================================================================
--- trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/RubyIndenter.java (rev 0)
+++ trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/RubyIndenter.java 2007-03-15 12:46:53 UTC (rev 2177)
@@ -0,0 +1,53 @@
+package org.rubypeople.rdt.internal.ui.text;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.rubypeople.rdt.core.IRubyProject;
+
+public class RubyIndenter {
+
+ private IDocument fDocument;
+ private IRubyProject fProject;
+ private RubyHeuristicScanner fScanner;
+
+ public RubyIndenter(IDocument d, RubyHeuristicScanner scanner, IRubyProject project) {
+ fDocument = d;
+ fScanner = scanner;
+ fProject = project;
+ }
+
+ public StringBuffer computeIndentation(int offset) {
+ StringBuffer buf = getLeadingWhitespace(offset);
+ // TODO Convert the whitespaces into units?
+ return buf;
+// return CodeFormatterUtil.createIndentString(indentationUnits, fProject);
+ }
+
+ /**
+ * Returns the indentation of the line at <code>offset</code> as a
+ * <code>StringBuffer</code>. If the offset is not valid, the empty string
+ * is returned.
+ *
+ * @param offset the offset in the document
+ * @return the indentation (leading whitespace) of the line in which
+ * <code>offset</code> is located
+ */
+ private StringBuffer getLeadingWhitespace(int offset) {
+ StringBuffer indent= new StringBuffer();
+ try {
+ IRegion line= fDocument.getLineInformationOfOffset(offset);
+ int lineOffset= line.getOffset();
+ int nonWS= fScanner.findNonWhitespaceForwardInAnyPartition(lineOffset, lineOffset + line.getLength());
+ if (nonWS == -1) {
+ indent.append(fDocument.get(lineOffset, line.getLength()));
+ return indent;
+ }
+ indent.append(fDocument.get(lineOffset, nonWS - lineOffset));
+ return indent;
+ } catch (BadLocationException e) {
+ return indent;
+ }
+ }
+
+}
Added: trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/ruby/RubyAutoIndentStrategy.java
===================================================================
--- trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/ruby/RubyAutoIndentStrategy.java (rev 0)
+++ trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/ruby/RubyAutoIndentStrategy.java 2007-03-15 12:46:53 UTC (rev 2177)
@@ -0,0 +1,158 @@
+package org.rubypeople.rdt.internal.ui.text.ruby;
+
+import java.util.regex.Pattern;
+
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.DefaultIndentLineAutoEditStrategy;
+import org.eclipse.jface.text.DocumentCommand;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITypedRegion;
+import org.eclipse.jface.text.TextUtilities;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.jruby.lexer.yacc.SyntaxException;
+import org.rubypeople.rdt.core.IRubyProject;
+import org.rubypeople.rdt.internal.core.parser.RubyParser;
+import org.rubypeople.rdt.internal.corext.util.CodeFormatterUtil;
+import org.rubypeople.rdt.internal.ui.RubyPlugin;
+import org.rubypeople.rdt.internal.ui.text.IRubyPartitions;
+import org.rubypeople.rdt.internal.ui.text.RubyHeuristicScanner;
+import org.rubypeople.rdt.internal.ui.text.RubyIndenter;
+import org.rubypeople.rdt.ui.PreferenceConstants;
+
+public class RubyAutoIndentStrategy extends DefaultIndentLineAutoEditStrategy implements IPropertyChangeListener {
+
+ /** Preference key for automatically 'end'ing statements */
+ private final static String END_STATEMENTS= PreferenceConstants.EDITOR_END_STATEMENTS;
+
+ private final Pattern openBlockPattern = Pattern.compile(".*[\\S].*do[\\w|\\s]*");
+
+ private static final String BLOCK_CLOSER = "end";
+ private String fPartitioning;
+ private final IRubyProject fProject;
+ private boolean endStatements;
+ private IPreferenceStore fPreferenceStore;
+
+ /**
+ * Creates a new Ruby auto indent strategy for the given document partitioning.
+ *
+ * @param partitioning the document partitioning
+ * @param project the project to get formatting preferences from, or null to use default preferences
+ */
+ public RubyAutoIndentStrategy(String partitioning, IRubyProject project) {
+ fPartitioning= partitioning;
+ fProject= project;
+ fPreferenceStore = RubyPlugin.getDefault().getPreferenceStore();
+ endStatements= fPreferenceStore.getBoolean(END_STATEMENTS);
+ fPreferenceStore.addPropertyChangeListener(this);
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IAutoIndentStrategy#customizeDocumentCommand(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.DocumentCommand)
+ */
+ public void customizeDocumentCommand(IDocument d, DocumentCommand c) {
+ if (c.doit == false)
+ return;
+ if (c.length == 0 && c.text != null && isLineDelimiter(d, c.text))
+ smartIndentAfterNewLine(d, c);
+ }
+
+ private boolean isLineDelimiter(IDocument document, String text) {
+ String[] delimiters= document.getLegalLineDelimiters();
+ if (delimiters != null)
+ return TextUtilities.equals(delimiters, text) > -1;
+ return false;
+ }
+
+ private void smartIndentAfterNewLine(IDocument d, DocumentCommand c) {
+ RubyHeuristicScanner scanner= new RubyHeuristicScanner(d);
+ RubyIndenter indenter= new RubyIndenter(d, scanner, fProject);
+ StringBuffer indent= indenter.computeIndentation(c.offset);
+ if (indent == null)
+ indent= new StringBuffer();
+
+ int docLength= d.getLength();
+ if (c.offset == -1 || docLength == 0)
+ return;
+
+ try {
+ int p= (c.offset == docLength ? c.offset - 1 : c.offset);
+ int line= d.getLineOfOffset(p);
+
+ StringBuffer buf= new StringBuffer(c.text + indent);
+
+
+ IRegion reg= d.getLineInformation(line);
+ int lineEnd= reg.getOffset() + reg.getLength();
+
+ int contentStart= findEndOfWhiteSpace(d, c.offset, lineEnd);
+ c.length= Math.max(contentStart - c.offset, 0);
+
+ int start= reg.getOffset();
+ ITypedRegion region= TextUtilities.getPartition(d, fPartitioning, start, true);
+ if (IRubyPartitions.RUBY_DOC.equals(region.getType()))
+ start= d.getLineInformationOfOffset(region.getOffset()).getOffset();
+
+ // insert closing "end" on new line after an unclosed block
+ if (closeBlock() && unclosedBlock(d, start, c.offset)) {
+ buf.append(CodeFormatterUtil.createIndentString(1, fProject));
+ c.caretOffset= c.offset + buf.length();
+ c.shiftsCaret= false;
+
+ // copy old content of line behind insertion point to new line
+ if (c.offset == 0) {
+ if (lineEnd - contentStart > 0) {
+ c.length= lineEnd - c.offset;
+ buf.append(d.get(contentStart, lineEnd - contentStart).toCharArray());
+ }
+ }
+
+ buf.append(TextUtilities.getDefaultLineDelimiter(d));
+ buf.append(indent);
+ buf.append(BLOCK_CLOSER);
+ }
+ c.text= buf.toString();
+
+ } catch (BadLocationException e) {
+ RubyPlugin.log(e);
+ }
+ }
+
+ private boolean unclosedBlock(IDocument d, int start, int offset) {
+ // FIXME wow is this ugly! There has to be an easier way to tell if there's an unclosed block besides parsing and catching a syntaxError!
+ try {
+ String line = d.get(start, offset - start);
+ line = line.trim();
+ if (!line.startsWith("class ") && !line.startsWith("if ") && !line.startsWith("module ") && !line.startsWith("unless ")
+ && !line.startsWith("def ") && !line.equals("begin") && !openBlockPattern.matcher(line).matches()) {
+ return false;
+ }
+ } catch (BadLocationException e1) {
+ RubyPlugin.log(e1);
+ }
+
+ try {
+ RubyParser parser = new RubyParser();
+ parser.parse(d.get());
+ } catch (SyntaxException e) {
+ String msg = e.getMessage();
+ return msg.contains("expecting") && (msg.contains("kEND") || msg.contains("kTHEN"));
+ }
+ return false;
+ }
+
+ private boolean closeBlock() {
+ return endStatements;
+ }
+
+ public void propertyChange(PropertyChangeEvent event) {
+ String property = event.getProperty();
+ if (END_STATEMENTS.equals(property)) {
+ endStatements = fPreferenceStore.getBoolean(property);
+ return;
+ }
+ }
+
+}
Modified: trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/ui/PreferenceConstants.java
===================================================================
--- trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/ui/PreferenceConstants.java 2007-03-14 20:52:14 UTC (rev 2176)
+++ trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/ui/PreferenceConstants.java 2007-03-15 12:46:53 UTC (rev 2177)
@@ -789,6 +789,7 @@
store.setDefault(PreferenceConstants.EDITOR_CLOSE_STRINGS, true);
store.setDefault(PreferenceConstants.EDITOR_CLOSE_BRACKETS, true);
store.setDefault(PreferenceConstants.EDITOR_CLOSE_BRACES, true);
+ store.setDefault(PreferenceConstants.EDITOR_END_STATEMENTS, true);
// mark occurrences
store.setDefault(PreferenceConstants.EDITOR_MARK_OCCURRENCES, true);
Modified: trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/ui/text/RubySourceViewerConfiguration.java
===================================================================
--- trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/ui/text/RubySourceViewerConfiguration.java 2007-03-14 20:52:14 UTC (rev 2176)
+++ trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/ui/text/RubySourceViewerConfiguration.java 2007-03-15 12:46:53 UTC (rev 2177)
@@ -42,7 +42,6 @@
import org.rubypeople.rdt.internal.ui.RubyPlugin;
import org.rubypeople.rdt.internal.ui.rubyeditor.IRubyScriptDocumentProvider;
import org.rubypeople.rdt.internal.ui.rubyeditor.RubyAbstractEditor;
-import org.rubypeople.rdt.internal.ui.rubyeditor.RubyAutoEditStrategy;
import org.rubypeople.rdt.internal.ui.text.ContentAssistPreference;
import org.rubypeople.rdt.internal.ui.text.HTMLTextPresenter;
import org.rubypeople.rdt.internal.ui.text.IRubyColorConstants;
@@ -58,6 +57,7 @@
import org.rubypeople.rdt.internal.ui.text.hyperlinks.RubyHyperLinkDetector;
import org.rubypeople.rdt.internal.ui.text.ruby.AbstractRubyScanner;
import org.rubypeople.rdt.internal.ui.text.ruby.AbstractRubyTokenScanner;
+import org.rubypeople.rdt.internal.ui.text.ruby.RubyAutoIndentStrategy;
import org.rubypeople.rdt.internal.ui.text.ruby.RubyCompletionProcessor;
import org.rubypeople.rdt.internal.ui.text.ruby.RubyFormattingStrategy;
import org.rubypeople.rdt.internal.ui.text.ruby.RubyReconcilingStrategy;
@@ -321,27 +321,19 @@
}
public IAutoEditStrategy[] getAutoEditStrategies(
- ISourceViewer sourceViewer, String contentType) {
- String partitioning = getConfiguredDocumentPartitioning(sourceViewer);
- if (IRubyPartitions.RUBY_SINGLE_LINE_COMMENT.equals(contentType)) {
- return new IAutoEditStrategy[] { new RubyCommentAutoIndentStrategy(
- partitioning) };
- } else if (IDocument.DEFAULT_CONTENT_TYPE.equals(contentType)) {
- IAutoEditStrategy[] strategies = super.getAutoEditStrategies(
- sourceViewer, contentType);
- IAutoEditStrategy[] newStrategies = new IAutoEditStrategy[strategies.length + 1];
- System
- .arraycopy(strategies, 0, newStrategies, 0,
- strategies.length);
- newStrategies[newStrategies.length - 1] = new RubyAutoEditStrategy(
- partitioning, sourceViewer, fRubyCp);
- strategies = newStrategies;
- return strategies;
- } else {
- return super.getAutoEditStrategies(sourceViewer, contentType);
- }
- }
+ ISourceViewer sourceViewer, String contentType) {
+ String partitioning = getConfiguredDocumentPartitioning(sourceViewer);
+ if (IRubyPartitions.RUBY_SINGLE_LINE_COMMENT.equals(contentType)) {
+ return new IAutoEditStrategy[] { new RubyCommentAutoIndentStrategy(
+ partitioning) };
+ } else if ( IDocument.DEFAULT_CONTENT_TYPE.equals(contentType)) {
+ return new IAutoEditStrategy[] { new RubyAutoIndentStrategy(partitioning, getProject()) };
+ } else {
+ return super.getAutoEditStrategies (sourceViewer, contentType);
+ }
+ }
+
/*
* @see SourceViewerConfiguration#getInformationControlCreator(ISourceViewer)
* @since 2.0
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|