Add LaTeX linter

This commit is contained in:
Patrick Förster 2019-03-07 16:21:49 +01:00
parent ff91582269
commit 4580f4eade
4 changed files with 98 additions and 2 deletions

View file

@ -42,7 +42,7 @@ class LanguageServerImpl : LanguageServer, CoroutineScope {
val capabilities = ServerCapabilities().apply {
val syncOptions = TextDocumentSyncOptions().apply {
openClose = true
save = SaveOptions(false)
save = SaveOptions(true)
change = TextDocumentSyncKind.Full
}
textDocumentSync = Either.forRight(syncOptions)

View file

@ -25,6 +25,7 @@ import texlab.completion.latex.data.symbols.LatexSymbolDatabase
import texlab.definition.BibtexEntryDefinitionProvider
import texlab.definition.LatexLabelDefinitionProvider
import texlab.diagnostics.BibtexEntryDiagnosticsProvider
import texlab.diagnostics.LatexDiagnosticsProvider
import texlab.diagnostics.ManualDiagnosticsProvider
import texlab.folding.BibtexDeclarationFoldingProvider
import texlab.folding.LatexEnvironmentFoldingProvider
@ -204,11 +205,13 @@ class TextDocumentServiceImpl(val workspaceActor: WorkspaceActor) : CustomTextDo
BibtexEntryReferenceProvider)
val buildDiagnosticsProvider: ManualDiagnosticsProvider = ManualDiagnosticsProvider()
private val latexDiagnosticsProvider: LatexDiagnosticsProvider = LatexDiagnosticsProvider()
private val diagnosticsProvider: FeatureProvider<Unit, List<Diagnostic>> =
FeatureProvider.concat(
buildDiagnosticsProvider,
BibtexEntryDiagnosticsProvider)
BibtexEntryDiagnosticsProvider,
latexDiagnosticsProvider)
fun connect(client: CustomLanguageClient) {
this.client = client
@ -239,6 +242,7 @@ class TextDocumentServiceImpl(val workspaceActor: WorkspaceActor) : CustomTextDo
workspaceActor.put { Document.create(uri, text, language) }
launch {
latexDiagnosticsProvider.update(uri, text)
publishDiagnostics(uri)
resolveIncludes()
}
@ -267,6 +271,9 @@ class TextDocumentServiceImpl(val workspaceActor: WorkspaceActor) : CustomTextDo
override fun didSave(params: DidSaveTextDocumentParams) {
launch {
val uri = URIHelper.parse(params.textDocument.uri)
latexDiagnosticsProvider.update(uri, params.text)
publishDiagnostics(uri)
val config = client.configuration<BuildConfig>("latex.build", uri)
if (config.onSave) {
workspaceActor.withWorkspace { workspace ->

View file

@ -0,0 +1,23 @@
package texlab.diagnostics
import org.eclipse.lsp4j.Diagnostic
import texlab.LatexDocument
import texlab.provider.FeatureProvider
import texlab.provider.FeatureRequest
import java.net.URI
class LatexDiagnosticsProvider : FeatureProvider<Unit, List<Diagnostic>> {
private val diagnosticsByUri = mutableMapOf<URI, List<Diagnostic>>()
override suspend fun get(request: FeatureRequest<Unit>): List<Diagnostic> {
if (request.document !is LatexDocument) {
return emptyList()
}
return diagnosticsByUri[request.uri].orEmpty()
}
suspend fun update(uri: URI, text: String) {
diagnosticsByUri[uri] = LatexLinter.lint(text)
}
}

View file

@ -0,0 +1,66 @@
package texlab.diagnostics
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.future.await
import kotlinx.coroutines.withContext
import org.eclipse.lsp4j.Diagnostic
import org.eclipse.lsp4j.DiagnosticSeverity
import org.eclipse.lsp4j.Position
import org.eclipse.lsp4j.Range
import java.io.IOException
import java.io.InputStream
import java.util.concurrent.CompletableFuture
object LatexLinter {
private val LINE_REGEX = Regex("""(\d+):(\d+):(\d+):(\w+):(\w)+:(.*)""")
suspend fun lint(text: String): List<Diagnostic> {
try {
val process = withContext(Dispatchers.IO) {
ProcessBuilder("chktex", "-I0", "-f%l:%c:%d:%k:%n:%m\n")
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.redirectInput(ProcessBuilder.Redirect.PIPE)
.start()
}
val stderr = readTextAsync(process.errorStream)
val stdout = readTextAsync(process.inputStream)
withContext(Dispatchers.IO) {
process.outputStream.write(text.toByteArray())
process.outputStream.flush()
process.outputStream.close()
process.waitFor()
}
stderr.await()
return stdout.await()
.lines()
.filter { it.isNotBlank() }
.mapNotNull { parse(it) }
} catch (e: IOException) {
return emptyList()
}
}
private fun readTextAsync(stream: InputStream) = CompletableFuture.supplyAsync {
stream.bufferedReader(Charsets.UTF_8).readText()
}
private fun parse(text: String): Diagnostic? {
val match = LINE_REGEX.matchEntire(text) ?: return null
val line = match.groupValues[1].toInt() - 1
val character = match.groupValues[2].toInt() - 1
val digit = match.groupValues[3].toInt()
val kind = match.groupValues[4]
val number = match.groupValues[5]
val message = match.groupValues[6]
val range = Range(Position(line, character), Position(line, character + digit))
val severity = when (kind) {
"Message" -> DiagnosticSeverity.Information
"Warning" -> DiagnosticSeverity.Warning
else -> DiagnosticSeverity.Error
}
return Diagnostic(range, message, severity, "chktex", number)
}
}