Permalink
Browse files

Merge pull request #204 from dragos/issue/scaladoc-auto-edits-1001263

Added a Scala auto-edit strategy for scaladoc. Refs #1001273.
  • Loading branch information...
2 parents bf872af + e6d2109 commit 58d1572631597a5bbda146ad572b7dbaf7f0ae6e @dragos dragos committed Oct 11, 2012
@@ -0,0 +1,25 @@
+package scala.tools.eclipse.ui
+
+import org.junit.Assert._
+import org.eclipse.jface.text.DocumentCommand
+
+object AutoEditStrategyTests {
+ class TestCommand(cOffset: Int, cLength: Int, cText: String, cCaretOffset: Int, cShiftsCaret: Boolean, cDoIt: Boolean) extends DocumentCommand {
+ caretOffset = cCaretOffset
+ doit = cDoIt
+ length = cLength
+ offset = cOffset
+ text = cText
+ shiftsCaret = cShiftsCaret
+ }
+
+ def checkCommand(offset: Int, length: Int, text: String, caretOffset: Int, shiftsCaret: Boolean, doit: Boolean, command: DocumentCommand) {
+ assertEquals("Bad resulting offset", offset, command.offset)
+ assertEquals("Bad resulting lenght", length, command.length)
+ assertEquals("Bad resulting text", text, command.text)
+ assertEquals("Bad resulting carretOffset", caretOffset, command.caretOffset)
+ assertEquals("Bad resulting shiftsCaret", shiftsCaret, command.shiftsCaret)
+ assertEquals("Bad resulting doit", doit, command.doit)
+ }
+
+}
@@ -0,0 +1,240 @@
+package scala.tools.eclipse.ui
+
+import org.eclipse.jdt.internal.core.util.SimpleDocument
+import org.junit.Test
+import AutoEditStrategyTests._
+import org.eclipse.jface.text.Document
+import scala.tools.eclipse.lexical.ScalaDocumentPartitioner
+import org.eclipse.jface.text.IDocument
+import org.eclipse.jdt.ui.text.IJavaPartitions
+
+class ScaladocAutoEditStrategyTest {
+
+ val strategy = new ScaladocAutoIndentStrategy(IJavaPartitions.JAVA_PARTITIONING)
+
+ @Test
+ def openDocComment_topLevel() {
+ val input =
+ """
+/**^
+class Foo
+"""
+ val cmd = testCommand(input)
+ strategy.customizeDocumentCommand(testDocument(input), cmd)
+ checkCommand(cmd.offset, 0, "\n * \n */", cmd.offset + 4, false, true, cmd)
+ }
+
+ @Test
+ def openDocComment_topLevel_with_nested() {
+ val input =
+ """
+/**^
+class Foo {
+ /** blah */
+ def foo() {}
+}
+"""
+ val cmd = testCommand(input)
+ strategy.customizeDocumentCommand(testDocument(input), cmd)
+ checkCommand(cmd.offset, 0, "\n * \n */", cmd.offset + 4, false, true, cmd)
+ }
+
+ @Test
+ def openDocComment_topLevel_with_stringLit() {
+ val input =
+ """
+/**^
+class Foo {
+ def foo() {
+ "/* */" // tricky, this trips the Java auto-edit :-D
+ }
+}
+"""
+ val cmd = testCommand(input)
+ strategy.customizeDocumentCommand(testDocument(input), cmd)
+ checkCommand(cmd.offset, 0, "\n * \n */", cmd.offset + 4, false, true, cmd)
+ }
+
+ @Test
+ def openDocComment_nested() {
+ val input =
+ """
+/** blah */
+class Foo {
+ /**^
+ def foo() {
+ }
+}
+"""
+ val cmd = testCommand(input)
+ strategy.customizeDocumentCommand(testDocument(input), cmd)
+ checkCommand(cmd.offset, 0, "\n * \n */", cmd.offset + 7, false, true, cmd)
+ }
+
+ @Test
+ def openDocComment_nested_with_other_docs() {
+ val input =
+ """
+/** blah */
+class Foo {
+ /**^
+ def foo() {
+ }
+ /** */
+ def bar
+}
+"""
+ val cmd = testCommand(input)
+ strategy.customizeDocumentCommand(testDocument(input), cmd)
+ checkCommand(cmd.offset, 0, "\n * \n */", cmd.offset + 7, false, true, cmd)
+ }
+
+ @Test
+ def closedDocComment_topLevel() {
+ val input =
+ """
+/** ^blah */
+class Foo {
+}
+"""
+ val cmd = testCommand(input)
+ strategy.customizeDocumentCommand(testDocument(input), cmd)
+ checkCommand(cmd.offset, 0, "\n * ", -1, false, true, cmd)
+ }
+
+ @Test
+ def closedDocComment_topLevel_nested() {
+ val input =
+ """
+/** blah */
+class Foo {
+ /**^*/
+ def foo() {
+ }
+ /** */
+ def bar
+}
+"""
+
+ val cmd = testCommand(input)
+ strategy.customizeDocumentCommand(testDocument(input), cmd)
+ checkCommand(cmd.offset, 0, "\n * ", -1, false, true, cmd)
+ }
+
+ @Test
+ def openDocComment_at_beginning() {
+ val input =
+ """/**^class Foo {
+ def foo() {
+ }
+ /** */
+ def bar
+}
+"""
+
+ val cmd = testCommand(input)
+ strategy.customizeDocumentCommand(testDocument(input), cmd)
+ checkCommand(cmd.offset, 0, "\n * \n */", cmd.offset + 4, false, true, cmd)
+ }
+
+ @Test
+ def openDocComment_at_end() {
+ val input =
+ """
+class Foo {
+}/**^"""
+
+ val cmd = testCommand(input)
+ strategy.customizeDocumentCommand(testDocument(input), cmd)
+ checkCommand(cmd.offset, 0, "\n* ", -1, false, true, cmd)
+ }
+
+ @Test
+ def closedDocComment_first_char_of_line() {
+ val input =
+ """
+/**
+^
+*/
+class Foo {
+}"""
+
+ val cmd = testCommand(input)
+ strategy.customizeDocumentCommand(testDocument(input), cmd)
+ checkCommand(cmd.offset, 0, "\n* ", -1, false, true, cmd)
+ }
+
+ @Test
+ def closedDocComment_line_break() {
+ val input =
+ """
+/** one^two
+ */
+class Foo {
+}"""
+
+ val cmd = testCommand(input)
+ strategy.customizeDocumentCommand(testDocument(input), cmd)
+ checkCommand(cmd.offset, 0, "\n * ", -1, false, true, cmd)
+ }
+
+ @Test
+ def closedDocComment_line_break_nested() {
+ val input =
+ """
+class Foo {
+ /** one^two
+ */
+ def meth() {}
+
+}"""
+
+ val cmd = testCommand(input)
+ strategy.customizeDocumentCommand(testDocument(input), cmd)
+ checkCommand(cmd.offset, 0, "\n * ", -1, false, true, cmd)
+ }
+
+ @Test
+ def closedDocComment_nop_end() {
+ val input =
+ """
+class Foo {
+ /** one two *^/
+ def meth() {}
+
+}"""
+
+ val cmd = testCommand(input)
+ strategy.customizeDocumentCommand(testDocument(input), cmd)
+ checkCommand(cmd.offset, 0, "\n * ", -1, false, true, cmd)
+ }
+
+ @Test
+ def closedDocComment_nop_beginning() {
+ val input =
+ """
+class Foo {
+ /^** one two */
+ def meth() {}
+
+}"""
+
+ val cmd = testCommand(input)
+ strategy.customizeDocumentCommand(testDocument(input), cmd)
+ checkCommand(cmd.offset, 0, "\n * ", -1, false, true, cmd)
+ }
+
+ def testDocument(input: String): IDocument = {
+ val rawInput = input.filterNot(_ == '^')
+ val doc = new Document(rawInput)
+ val partitioner = new ScalaDocumentPartitioner
+ doc.setDocumentPartitioner(IJavaPartitions.JAVA_PARTITIONING, partitioner)
+ partitioner.connect(doc)
+ doc
+ }
+
+ def testCommand(input: String): TestCommand = {
+ val pos = input.indexOf('^')
+ new TestCommand(pos, 0, "\n", -1, false, true)
+ }
+}
@@ -7,15 +7,7 @@ import org.eclipse.jface.text.DocumentCommand
import org.eclipse.jface.text.IDocument
import org.eclipse.jface.text.TextViewer
-class TestCommand(cOffset: Int, cLength: Int, cText: String, cCaretOffset: Int, cShiftsCaret: Boolean, cDoIt: Boolean) extends DocumentCommand {
- caretOffset = cCaretOffset
- doit = cDoIt
- length = cLength
- offset = cOffset
- text = cText
- shiftsCaret = cShiftsCaret
-
-}
+import AutoEditStrategyTests._
/**
* Those are not real test (does not check the document after applying the change), just regression tests.
@@ -99,14 +91,4 @@ class TestBracketStrategy {
checkCommand(6, 1, "", -1, true, true, command)
}
-
- def checkCommand(offset: Int, length: Int, text: String, caretOffset: Int, shiftsCaret: Boolean, doit: Boolean, command: DocumentCommand) {
- assertEquals("Bad resulting offset", offset, command.offset)
- assertEquals("Bad resulting lenght", length, command.length)
- assertEquals("Bad resulting text", text, command.text)
- assertEquals("Bad resulting carretOffset", caretOffset, command.caretOffset)
- assertEquals("Bad resulting shiftsCaret", shiftsCaret, command.shiftsCaret)
- assertEquals("Bad resulting doit", doit, command.doit)
- }
-
}
@@ -39,6 +39,7 @@ import scala.tools.eclipse.hyperlink.text.detector.{CompositeHyperlinkDetector,
import scalariform.ScalaVersions
import org.eclipse.jface.text.DefaultTextHover
import scala.tools.eclipse.javaelements.ScalaCompilationUnit
+import scala.tools.eclipse.ui.ScaladocAutoIndentStrategy
class ScalaSourceViewerConfiguration(store: IPreferenceStore, scalaPreferenceStore: IPreferenceStore, editor: ITextEditor)
extends JavaSourceViewerConfiguration(JavaPlugin.getDefault.getJavaTextTools.getColorManager, store, editor, IJavaPartitions.JAVA_PARTITIONING) {
@@ -123,7 +124,7 @@ class ScalaSourceViewerConfiguration(store: IPreferenceStore, scalaPreferenceSto
val partitioning = getConfiguredDocumentPartitioning(sourceViewer)
contentType match {
case IJavaPartitions.JAVA_DOC | IJavaPartitions.JAVA_MULTI_LINE_COMMENT =>
- Array(new JavaDocAutoIndentStrategy(partitioning))
+ Array(new ScaladocAutoIndentStrategy(partitioning))
case IJavaPartitions.JAVA_STRING =>
Array(new SmartSemicolonAutoEditStrategy(partitioning), new JavaStringAutoIndentStrategy(partitioning))
case IJavaPartitions.JAVA_CHARACTER | IDocument.DEFAULT_CONTENT_TYPE =>
@@ -0,0 +1,72 @@
+package scala.tools.eclipse.ui
+
+import org.eclipse.jface.text.DefaultIndentLineAutoEditStrategy
+import org.eclipse.jface.text.DocumentCommand
+import org.eclipse.jface.text.IDocument
+import org.eclipse.jface.text.TextUtilities
+import scala.tools.eclipse.lexical.ScalaPartitions
+import org.eclipse.jdt.ui.text.IJavaPartitions
+import org.eclipse.jface.text.BadLocationException
+import scala.tools.eclipse.logging.HasLogger
+
+/** A Scaladoc auto-edit strategy that does the following:
+ *
+ * - adds '*' and left-aligns them when pressing enter
+ * - auto-closes an open ScalaDoc and places the cursor in between
+ */
+class ScaladocAutoIndentStrategy(partitioning: String) extends DefaultIndentLineAutoEditStrategy with HasLogger {
+
+ override def customizeDocumentCommand(doc: IDocument, cmd: DocumentCommand) {
+ if (cmd.offset == -1 || doc.getLength() == 0) return // don't spend time on invalid docs
+
+ try {
+ if (cmd.length == 0 && cmd.text != null && TextUtilities.endsWith(doc.getLegalLineDelimiters(), cmd.text) != -1) {
+ val shouldClose = shouldCloseDocComment(doc, cmd.offset)
+
+ val (indent, rest) = breakLine(doc, cmd.offset)
+ val buf = new StringBuilder(cmd.text)
+ buf.append(indent)
+ val docStart = rest.length > 0 && rest(0) == '/'
+
+ // align the star under the other star
+ if (docStart) buf.append(" * ") else buf.append("* ")
+
+ if (shouldClose) {
+ // we want the caret before the closing comment
+ cmd.caretOffset = cmd.offset + buf.length
+ buf append ("\n" + indent)
+ buf append (if (docStart) " */" else "*/")
+ cmd.shiftsCaret = false
+ }
+ cmd.text = buf.toString
+ }
+ } catch {
+ case e: Exception =>
+ // don't break typing under any circumstances
+ eclipseLog.warn("Error in scaladoc autoedit", e)
+ }
+ }
+
+ /** Return the whitespace prefix (indentation) and the rest of the line
+ * for the given offset.
+ */
+ private def breakLine(doc: IDocument, offset: Int): (String, String) = {
+ // indent up to the previous line
+ val lineInfo = doc.getLineInformationOfOffset(offset)
+ val endOfWS = findEndOfWhiteSpace(doc, lineInfo.getOffset(), offset)
+ (doc.get(lineInfo.getOffset, endOfWS - lineInfo.getOffset), doc.get(endOfWS, lineInfo.getOffset + lineInfo.getLength() - endOfWS))
+ }
+
+ /** Heuristics for when to close a scaladoc. Returns `true` when the offset is inside a scaladoc
+ * that runs to the end of the document. This handles nested comments pretty well because
+ * it uses the Scala document partitioner.
+ */
+ private def shouldCloseDocComment(doc: IDocument, offset: Int): Boolean = {
+ val partition = TextUtilities.getPartition(doc, partitioning, offset, true)
+ val partitionEnd = partition.getOffset() + partition.getLength()
+ (scaladocPartitions(partition.getType())
+ && partitionEnd == doc.getLength())
+ }
+
+ private val scaladocPartitions = Set(IJavaPartitions.JAVA_DOC, IJavaPartitions.JAVA_MULTI_LINE_COMMENT)
+}

0 comments on commit 58d1572

Please sign in to comment.