Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a Scala auto-edit strategy for scaladoc. #204

Merged
merged 1 commit into from
Oct 11, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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)
}

}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 =>
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alignement should depend on the user formatting preferences. You don't have to fix it now, maybe open a ticket if you agree ;)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true. I added a ticket: #1001273

* - 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)
}