mirror of
https://github.com/latex-lsp/texlab.git
synced 2025-12-23 09:19:21 +00:00
Add LaTeX linter
This commit is contained in:
parent
ff91582269
commit
4580f4eade
4 changed files with 98 additions and 2 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 ->
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
66
src/main/kotlin/texlab/diagnostics/LatexLinter.kt
Normal file
66
src/main/kotlin/texlab/diagnostics/LatexLinter.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue