Add choice combinator to FeatureProvider

This commit is contained in:
Eric Förster 2019-03-06 18:29:44 +01:00
parent e9162d9ac9
commit b75577981a
19 changed files with 127 additions and 89 deletions

View file

@ -165,8 +165,8 @@ class TextDocumentServiceImpl(val workspaceActor: WorkspaceActor) : CustomTextDo
LatexCitationSymbolProvider,
BibtexEntrySymbolProvider)
private val renameProvider: FeatureProvider<RenameParams, List<WorkspaceEdit>> =
FeatureProvider.concat(
private val renameProvider: FeatureProvider<RenameParams, WorkspaceEdit?> =
FeatureProvider.choice(
LatexCommandRenamer,
LatexEnvironmentRenamer,
LatexLabelRenamer,
@ -189,14 +189,14 @@ class TextDocumentServiceImpl(val workspaceActor: WorkspaceActor) : CustomTextDo
private val highlightProvider: FeatureProvider<TextDocumentPositionParams, List<DocumentHighlight>> =
FeatureProvider.concat(LatexLabelHighlightProvider)
private val hoverProvider: FeatureProvider<TextDocumentPositionParams, List<Hover>> =
FeatureProvider.concat(
private val hoverProvider: FeatureProvider<TextDocumentPositionParams, Hover?> =
FeatureProvider.choice(
LatexComponentHoverProvider,
LatexCitationHoverProvider,
LatexMathEnvironmentHoverProvider,
LatexMathEquationHoverProvider,
LatexMathInlineHoverProvider,
DeferredProvider(::LatexCommandHoverProvider, componentDatabase, emptyList()),
DeferredProvider(::LatexCommandHoverProvider, componentDatabase, null),
BibtexEntryTypeHoverProvider,
BibtexFieldHoverProvider)
@ -290,7 +290,7 @@ class TextDocumentServiceImpl(val workspaceActor: WorkspaceActor) : CustomTextDo
}
override fun rename(params: RenameParams): CompletableFuture<WorkspaceEdit?> = future {
runFeature(renameProvider, params.textDocument, params).firstOrNull()
runFeature(renameProvider, params.textDocument, params)
}
override fun documentLink(params: DocumentLinkParams)
@ -351,7 +351,7 @@ class TextDocumentServiceImpl(val workspaceActor: WorkspaceActor) : CustomTextDo
override fun hover(params: TextDocumentPositionParams)
: CompletableFuture<Hover?> = future {
runFeature(hoverProvider, params.textDocument, params).firstOrNull()
runFeature(hoverProvider, params.textDocument, params)
}
override fun formatting(params: DocumentFormattingParams)
@ -432,9 +432,9 @@ class TextDocumentServiceImpl(val workspaceActor: WorkspaceActor) : CustomTextDo
}
}
private suspend fun <T, R> runFeature(provider: FeatureProvider<T, List<R>>,
private suspend fun <T, R> runFeature(provider: FeatureProvider<T, R>,
document: TextDocumentIdentifier,
params: T): List<R> {
params: T): R {
return workspaceActor.withWorkspace { workspace ->
val uri = URIHelper.parse(document.uri)
val request = FeatureRequest(uri, workspace, params, logger)

View file

@ -9,21 +9,20 @@ import texlab.provider.FeatureProvider
import texlab.provider.FeatureRequest
import texlab.syntax.bibtex.BibtexEntrySyntax
object BibtexEntryTypeHoverProvider : FeatureProvider<TextDocumentPositionParams, List<Hover>> {
override suspend fun get(request: FeatureRequest<TextDocumentPositionParams>): List<Hover> {
object BibtexEntryTypeHoverProvider : FeatureProvider<TextDocumentPositionParams, Hover?> {
override suspend fun get(request: FeatureRequest<TextDocumentPositionParams>): Hover? {
if (request.document !is BibtexDocument) {
return emptyList()
return null
}
val name = request.document.tree.root.children
.filterIsInstance<BibtexEntrySyntax>()
.firstOrNull { it.type.range.contains(request.params.position) }
?.type?.text?.substring(1)?.toLowerCase()
?: return emptyList()
?: return null
val metadata = BibtexEntryTypeMetadataProvider.getMetadata(name)
val documentation = metadata?.documentation ?: return emptyList()
return listOf(Hover(documentation))
val documentation = metadata?.documentation ?: return null
return Hover(documentation)
}
}

View file

@ -11,22 +11,22 @@ import texlab.provider.FeatureProvider
import texlab.provider.FeatureRequest
import texlab.syntax.bibtex.BibtexFieldSyntax
object BibtexFieldHoverProvider : FeatureProvider<TextDocumentPositionParams, List<Hover>> {
override suspend fun get(request: FeatureRequest<TextDocumentPositionParams>): List<Hover> {
object BibtexFieldHoverProvider : FeatureProvider<TextDocumentPositionParams, Hover?> {
override suspend fun get(request: FeatureRequest<TextDocumentPositionParams>): Hover? {
if (request.document !is BibtexDocument) {
return emptyList()
return null
}
val fieldNode = request.document.tree.root.descendants()
.filterIsInstance<BibtexFieldSyntax>()
.firstOrNull { it.name.range.contains(request.params.position) }
?: return emptyList()
?: return null
val fieldName = BibtexField.parse(fieldNode.name.text) ?: return emptyList()
val fieldName = BibtexField.parse(fieldNode.name.text) ?: return null
val markup = MarkupContent().apply {
kind = MarkupKind.MARKDOWN
value = fieldName.documentation()
}
return listOf(Hover(markup))
return Hover(markup)
}
}

View file

@ -15,23 +15,21 @@ import texlab.provider.FeatureRequest
import texlab.syntax.bibtex.BibtexEntrySyntax
@ObsoleteCoroutinesApi
object LatexCitationHoverProvider : FeatureProvider<TextDocumentPositionParams, List<Hover>> {
override suspend fun get(request: FeatureRequest<TextDocumentPositionParams>): List<Hover> {
val key = getKey(request) ?: return emptyList()
object LatexCitationHoverProvider : FeatureProvider<TextDocumentPositionParams, Hover?> {
override suspend fun get(request: FeatureRequest<TextDocumentPositionParams>): Hover? {
val key = getKey(request) ?: return null
val entry = request.relatedDocuments
.filterIsInstance<BibtexDocument>()
.flatMap { it.tree.root.children.filterIsInstance<BibtexEntrySyntax>() }
.firstOrNull { it.name?.text == key }
?: return emptyList()
?: return null
val formatter = BibtexFormatter(insertSpaces = true, tabSize = 4, lineLength = -1)
val hover = Hover(MarkupContent().apply {
val markup = MarkupContent().apply {
kind = MarkupKind.MARKDOWN
value = BibtexCitationActor.cite(formatter.format(entry))
})
return listOf(hover)
}
return Hover(markup)
}
private fun getKey(request: FeatureRequest<TextDocumentPositionParams>): String? {

View file

@ -12,26 +12,27 @@ import texlab.provider.FeatureRequest
import texlab.syntax.latex.LatexCommandSyntax
class LatexCommandHoverProvider(private val database: LatexComponentSource) :
FeatureProvider<TextDocumentPositionParams, List<Hover>> {
override suspend fun get(request: FeatureRequest<TextDocumentPositionParams>): List<Hover> {
FeatureProvider<TextDocumentPositionParams, Hover?> {
override suspend fun get(request: FeatureRequest<TextDocumentPositionParams>): Hover? {
if (request.document !is LatexDocument) {
return emptyList()
return null
}
val command = request.document.tree.root
.descendants()
.filterIsInstance<LatexCommandSyntax>()
.firstOrNull { it.name.range.contains(request.params.position) }
?: return emptyList()
?: return null
val components = database.getRelatedComponents(request.relatedDocuments)
.filter { it.commands.contains(command.name.text.substring(1)) }
.flatMap { it.fileNames }
val separator = System.lineSeparator().repeat(2)
return listOf(Hover(MarkupContent().apply {
val markup = MarkupContent().apply {
kind = MarkupKind.MARKDOWN
value = components.joinToString(separator)
}))
}
return Hover(markup)
}
}

View file

@ -8,19 +8,19 @@ import texlab.metadata.LatexComponentMetadataProvider
import texlab.provider.FeatureProvider
import texlab.provider.FeatureRequest
object LatexComponentHoverProvider : FeatureProvider<TextDocumentPositionParams, List<Hover>> {
override suspend fun get(request: FeatureRequest<TextDocumentPositionParams>): List<Hover> {
object LatexComponentHoverProvider : FeatureProvider<TextDocumentPositionParams, Hover?> {
override suspend fun get(request: FeatureRequest<TextDocumentPositionParams>): Hover? {
if (request.document !is LatexDocument) {
return emptyList()
return null
}
val name = request.document.tree.includes
.filter { it.isUnitImport }
.firstOrNull { it.command.range.contains(request.params.position) }
?.path ?: return emptyList()
?.path ?: return null
val metadata = LatexComponentMetadataProvider.getMetadata(name)
val documentation = metadata?.documentation ?: return emptyList()
return listOf(Hover(documentation))
val documentation = metadata?.documentation ?: return null
return Hover(documentation)
}
}

View file

@ -9,16 +9,16 @@ import texlab.provider.FeatureProvider
import texlab.provider.FeatureRequest
abstract class LatexMathHoverProvider : FeatureProvider<TextDocumentPositionParams, List<Hover>> {
override suspend fun get(request: FeatureRequest<TextDocumentPositionParams>): List<Hover> {
abstract class LatexMathHoverProvider : FeatureProvider<TextDocumentPositionParams, Hover?> {
override suspend fun get(request: FeatureRequest<TextDocumentPositionParams>): Hover? {
if (request.document !is LatexDocument) {
return emptyList()
return null
}
val range = getCodeRange(request.document, request.params.position) ?: return emptyList()
val range = getCodeRange(request.document, request.params.position) ?: return null
val code = request.document.tree.extract(range)
val icon = LatexFormulaRenderer.render(code) ?: return emptyList()
return listOf(Hover(icon))
val icon = LatexFormulaRenderer.render(code) ?: return null
return Hover(icon)
}
protected abstract fun getCodeRange(document: LatexDocument, position: Position): Range?

View file

@ -21,5 +21,18 @@ interface FeatureProvider<T, R> {
providers.flatMap { it.get(request) }
}
}
fun <T, R> choice(vararg providers: FeatureProvider<T, R?>): FeatureProvider<T, R?> {
return create { request ->
for (provider in providers) {
val result = provider.get(request)
if (result != null) {
return@create result
}
}
null
}
}
}
}

View file

@ -12,12 +12,12 @@ import texlab.provider.FeatureRequest
import texlab.syntax.Token
import texlab.syntax.bibtex.BibtexEntrySyntax
object BibtexEntryRenamer : FeatureProvider<RenameParams, List<WorkspaceEdit>> {
override suspend fun get(request: FeatureRequest<RenameParams>): List<WorkspaceEdit> {
object BibtexEntryRenamer : FeatureProvider<RenameParams, WorkspaceEdit?> {
override suspend fun get(request: FeatureRequest<RenameParams>): WorkspaceEdit? {
val token = when (request.document) {
is BibtexDocument -> findEntry(request.document, request.params.position)
is LatexDocument -> findCitation(request.document, request.params.position)
} ?: return emptyList()
} ?: return null
val changes = mutableMapOf<String, List<TextEdit>>()
for (document in request.relatedDocuments) {
@ -39,7 +39,7 @@ object BibtexEntryRenamer : FeatureProvider<RenameParams, List<WorkspaceEdit>> {
changes[document.uri.toString()] = edits
}
return listOf(WorkspaceEdit(changes))
return WorkspaceEdit(changes)
}
private fun findEntry(document: BibtexDocument, position: Position): Token? {

View file

@ -9,16 +9,16 @@ import texlab.provider.FeatureProvider
import texlab.provider.FeatureRequest
import texlab.syntax.latex.LatexCommandSyntax
object LatexCommandRenamer : FeatureProvider<RenameParams, List<WorkspaceEdit>> {
override suspend fun get(request: FeatureRequest<RenameParams>): List<WorkspaceEdit> {
object LatexCommandRenamer : FeatureProvider<RenameParams, WorkspaceEdit?> {
override suspend fun get(request: FeatureRequest<RenameParams>): WorkspaceEdit? {
if (request.document !is LatexDocument) {
return emptyList()
return null
}
val command = request.document.tree.root
.descendants()
.filterIsInstance<LatexCommandSyntax>()
.firstOrNull { it.name.range.contains(request.params.position) } ?: return emptyList()
.firstOrNull { it.name.range.contains(request.params.position) } ?: return null
val changes = mutableMapOf<String, List<TextEdit>>()
for (document in request.relatedDocuments.filterIsInstance<LatexDocument>()) {
@ -29,6 +29,6 @@ object LatexCommandRenamer : FeatureProvider<RenameParams, List<WorkspaceEdit>>
changes[document.uri.toString()] = edits
}
return listOf(WorkspaceEdit(changes))
return WorkspaceEdit(changes)
}
}

View file

@ -8,10 +8,10 @@ import texlab.contains
import texlab.provider.FeatureProvider
import texlab.provider.FeatureRequest
object LatexEnvironmentRenamer : FeatureProvider<RenameParams, List<WorkspaceEdit>> {
override suspend fun get(request: FeatureRequest<RenameParams>): List<WorkspaceEdit> {
object LatexEnvironmentRenamer : FeatureProvider<RenameParams, WorkspaceEdit?> {
override suspend fun get(request: FeatureRequest<RenameParams>): WorkspaceEdit? {
if (request.document !is LatexDocument) {
return emptyList()
return null
}
for (environment in request.document.tree.environments) {
@ -21,10 +21,10 @@ object LatexEnvironmentRenamer : FeatureProvider<RenameParams, List<WorkspaceEdi
val edits = listOf(
TextEdit(environment.beginNameRange, request.params.newName),
TextEdit(environment.endNameRange, request.params.newName))
return listOf(WorkspaceEdit(mutableMapOf(request.uri.toString() to edits)))
return WorkspaceEdit(mapOf(request.uri.toString() to edits))
}
}
return emptyList()
return null
}
}

View file

@ -8,17 +8,17 @@ import texlab.contains
import texlab.provider.FeatureProvider
import texlab.provider.FeatureRequest
object LatexLabelRenamer : FeatureProvider<RenameParams, List<WorkspaceEdit>> {
override suspend fun get(request: FeatureRequest<RenameParams>): List<WorkspaceEdit> {
object LatexLabelRenamer : FeatureProvider<RenameParams, WorkspaceEdit?> {
override suspend fun get(request: FeatureRequest<RenameParams>): WorkspaceEdit? {
if (request.document !is LatexDocument) {
return emptyList()
return null
}
val label = request.document.tree
.labelReferences
.plus(request.document.tree.labelDefinitions)
.firstOrNull { it.name.range.contains(request.params.position) }
?: return emptyList()
?: return null
val changes = mutableMapOf<String, List<TextEdit>>()
for (document in request.relatedDocuments.filterIsInstance<LatexDocument>()) {
@ -29,6 +29,6 @@ object LatexLabelRenamer : FeatureProvider<RenameParams, List<WorkspaceEdit>> {
changes[document.uri.toString()] = edits
}
return listOf(WorkspaceEdit(changes))
return WorkspaceEdit(changes)
}
}

View file

@ -1,7 +1,8 @@
package texlab.hover
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test
import texlab.WorkspaceBuilder
@ -12,7 +13,7 @@ class BibtexEntryTypeHoverProviderTests {
.document("foo.bib", "@article")
.hover("foo.bib", 0, 2)
.let { BibtexEntryTypeHoverProvider.get(it) }
.also { assertTrue(it.isNotEmpty()) }
.also { assertNotNull(it) }
}
@Test
@ -21,7 +22,7 @@ class BibtexEntryTypeHoverProviderTests {
.document("foo.bib", "@article{foo, bar = {baz}}")
.hover("foo.bib", 0, 10)
.let { BibtexEntryTypeHoverProvider.get(it) }
.also { assertTrue(it.isEmpty()) }
.also { assertNull(it) }
}
@Test
@ -30,6 +31,6 @@ class BibtexEntryTypeHoverProviderTests {
.document("foo.tex", "")
.hover("foo.tex", 0, 0)
.let { BibtexEntryTypeHoverProvider.get(it) }
.also { assertTrue(it.isEmpty()) }
.also { assertNull(it) }
}
}

View file

@ -1,7 +1,8 @@
package texlab.hover
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test
import texlab.WorkspaceBuilder
@ -12,7 +13,7 @@ class BibtexFieldHoverProviderTests {
.document("foo.bib", "@article{foo, author = }")
.hover("foo.bib", 0, 15)
.let { BibtexFieldHoverProvider.get(it) }
.also { assertTrue(it.isNotEmpty()) }
.also { assertNotNull(it) }
}
@Test
@ -21,7 +22,7 @@ class BibtexFieldHoverProviderTests {
.document("foo.bib", "@article{foo, author = {bar}}")
.hover("foo.bib", 0, 5)
.let { BibtexFieldHoverProvider.get(it) }
.also { assertTrue(it.isEmpty()) }
.also { assertNull(it) }
}
@Test
@ -30,6 +31,6 @@ class BibtexFieldHoverProviderTests {
.document("foo.tex", "")
.hover("foo.tex", 0, 0)
.let { BibtexFieldHoverProvider.get(it) }
.also { assertTrue(it.isEmpty()) }
.also { assertNull(it) }
}
}

View file

@ -1,8 +1,7 @@
package texlab.provider
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import texlab.WorkspaceBuilder
@ -51,4 +50,30 @@ class FeatureProviderTests {
assertEquals(firstProvider.number, result[0])
assertEquals(secondProvider.number, result[1])
}
@Test
fun `it should return a result when a provider has a result`() = runBlocking {
val request = WorkspaceBuilder()
.document("foo.tex", "")
.request("foo.tex") {}
val firstProvider = NumberProvider(null)
val secondProvider = NumberProvider(2)
val provider = FeatureProvider.choice(firstProvider, secondProvider)
val result = provider.get(request)
assertEquals(result, secondProvider.number)
}
@Test
fun `it should return nothing when no provider has a result`() = runBlocking {
val request = WorkspaceBuilder()
.document("foo.tex", "")
.request("foo.tex") {}
val firstProvider = NumberProvider(null)
val secondProvider = NumberProvider(null)
val provider = FeatureProvider.choice(firstProvider, secondProvider)
val result = provider.get(request)
assertNull(result)
}
}

View file

@ -20,7 +20,7 @@ class BibtexEntryRenamerTests {
val edit = builder
.rename(document, line, character, "qux")
.let { BibtexEntryRenamer.get(it).first() }
.let { BibtexEntryRenamer.get(it)!! }
assertEquals(2, edit.changes.size)
@ -42,7 +42,7 @@ class BibtexEntryRenamerTests {
WorkspaceBuilder()
.document("foo.bib", "@article{foo, bar = baz}")
.rename("foo.bib", 0, 14, "qux")
.let { BibtexEntryRenamer.get(it).firstOrNull() }
.let { BibtexEntryRenamer.get(it) }
.also { assertNull(it) }
}
@ -51,7 +51,7 @@ class BibtexEntryRenamerTests {
WorkspaceBuilder()
.document("foo.tex", "")
.rename("foo.tex", 0, 0, "bar")
.let { BibtexEntryRenamer.get(it).firstOrNull() }
.let { BibtexEntryRenamer.get(it) }
.also { assertNull(it) }
}
}

View file

@ -17,7 +17,7 @@ class LatexCommandRenamerTests {
val edit = builder
.rename("foo.tex", 1, 2, "qux")
.let { LatexCommandRenamer.get(it).first() }
.let { LatexCommandRenamer.get(it)!! }
assertEquals(2, edit.changes.size)
@ -39,7 +39,7 @@ class LatexCommandRenamerTests {
WorkspaceBuilder()
.document("foo.bib", "\\foo \\bar")
.rename("foo.bib", 0, 1, "baz")
.let { LatexCommandRenamer.get(it).firstOrNull() }
.let { LatexCommandRenamer.get(it) }
.also { Assertions.assertNull(it) }
}
}

View file

@ -14,7 +14,7 @@ class LatexEnvironmentRenamerTests {
val edit = WorkspaceBuilder()
.document("foo.tex", "\\begin{foo}\n\\end{bar}")
.rename("foo.tex", 0, 8, "baz")
.let { LatexEnvironmentRenamer.get(it).first() }
.let { LatexEnvironmentRenamer.get(it)!! }
assertEquals(1, edit.changes.keys.size)
val changes = edit.changes.getValue(edit.changes.keys.first())
@ -30,7 +30,7 @@ class LatexEnvironmentRenamerTests {
WorkspaceBuilder()
.document("foo.tex", "\\begin{foo}\n\\end{bar}")
.rename("foo.tex", 0, 5, "baz")
.let { LatexEnvironmentRenamer.get(it).firstOrNull() }
.let { LatexEnvironmentRenamer.get(it) }
.also { assertNull(it) }
}
@ -39,7 +39,7 @@ class LatexEnvironmentRenamerTests {
WorkspaceBuilder()
.document("foo.bib", "\\begin{foo}\n\\end{bar}")
.rename("foo.bib", 0, 8, "baz")
.let { LatexEnvironmentRenamer.get(it).firstOrNull() }
.let { LatexEnvironmentRenamer.get(it) }
.also { assertNull(it) }
}
}

View file

@ -20,7 +20,7 @@ class LatexLabelRenamerTests {
.document("bar.tex", "\\ref{foo}")
val edit = builder
.rename(document, line, character, "bar")
.let { LatexLabelRenamer.get(it).first() }
.let { LatexLabelRenamer.get(it)!! }
Assertions.assertEquals(2, edit.changes.size)
@ -42,7 +42,7 @@ class LatexLabelRenamerTests {
WorkspaceBuilder()
.document("foo.tex", "\\foo{bar}")
.rename("foo.tex", 0, 5, "baz")
.let { LatexLabelRenamer.get(it).firstOrNull() }
.let { LatexLabelRenamer.get(it) }
.also { assertNull(it) }
}
@ -51,7 +51,7 @@ class LatexLabelRenamerTests {
WorkspaceBuilder()
.document("foo.bib", "")
.rename("foo.bib", 0, 0, "bar")
.let { LatexLabelRenamer.get(it).firstOrNull() }
.let { LatexLabelRenamer.get(it) }
.also { assertNull(it) }
}
}