Skip to content

Commit

Permalink
Add support for completion requests (en, de)
Browse files Browse the repository at this point in the history
  • Loading branch information
valentjn committed Oct 15, 2021
1 parent 03b8261 commit dec5962
Show file tree
Hide file tree
Showing 11 changed files with 382 additions and 3 deletions.
24 changes: 23 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ jobs:
with:
java-version: "11.0.9"

- name: "Set up Python"
uses: "actions/setup-python@v2"
with:
python-version: "3.9.0"

- name: "Create Completion Lists"
run: "python -u tools/createCompletionLists.py"

- name: "Build LTeX LS"
run: "mvn -B -e verify"

Expand All @@ -59,6 +67,9 @@ jobs:
with:
python-version: "3.9.0"

- name: "Create Completion Lists"
run: "python -u tools/createCompletionLists.py"

- name: "Build LTeX LS"
run: "mvn -B -e verify"

Expand Down Expand Up @@ -116,6 +127,14 @@ jobs:
with:
java-version: "11.0.9"

- name: "Set up Python"
uses: "actions/setup-python@v2"
with:
python-version: "3.9.0"

- name: "Create Completion Lists"
run: "python -u tools/createCompletionLists.py"

- name: "Build LTeX LS"
run: "mvn -B -e verify"

Expand Down Expand Up @@ -166,11 +185,14 @@ jobs:
- name: "Set LTEX_LS_CHANGELOG"
run: "if [ \"$LTEX_LS_IS_PRERELEASE\" = \"false\" ]; then echo \"LTEX_LS_CHANGELOG<<EOF\" >> $GITHUB_ENV; python -u tools/convertChangelog.py --xml-file changelog.xml --version latest >> $GITHUB_ENV; echo \"EOF\" >> $GITHUB_ENV; else echo \"LTEX_LS_CHANGELOG=This is a pre-release. Use at your own risk.\" >> $GITHUB_ENV; fi"

- name: "Create Completion Lists"
run: "python -u tools/createCompletionLists.py"

- name: "Build LTeX LS"
run: "mvn -B -e package"

- name: "Create Binary Archives"
run: "python tools/createBinaryArchives.py"
run: "python -u tools/createBinaryArchives.py"

- name: "Create GitHub Release"
uses: "softprops/[email protected]"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
run: "mvn -B -e package"

- name: "Create Binary Archives"
run: "python tools/createBinaryArchives.py"
run: "python -u tools/createBinaryArchives.py"

- name: "Delete Old Nightly Releases"
uses: "dev-drprasad/[email protected]"
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@

/.vscode/ltex.*.txt
/.vscode/settings.json

/src/main/resources/completionList.*.txt
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Find more information about LT<sub>E</sub>X at the [website of vscode-ltex](http
- **Offline checking:** Does not upload anything to the internet
- Supports **over 20 languages:** English, French, German, Dutch, Chinese, Russian, etc.
- **Replacement suggestions** via quick fixes
- **Completion support** for English and German
- **User dictionaries**
- **Multilingual support** with babel commands or magic comments
- Possibility to use **external LanguageTool servers**
Expand Down
5 changes: 5 additions & 0 deletions changelog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
<author>Julian Valentin, LTeX Development Community</author>
</properties>
<body>
<release version="14.1.0" date="upcoming">
<action type="add" issue="vscode-ltex#409">
Add support for completion requests for English and German
</action>
</release>
<release version="14.0.0" date="2021-10-14">
<action type="update">
Update LanguageTool to 5.5 (see [LT 5.5 release notes](https:/languagetool-org/languagetool/blob/v5.5/languagetool-standalone/CHANGES.md#55-2021-10-02))
Expand Down
151 changes: 151 additions & 0 deletions src/main/kotlin/org/bsplines/ltexls/server/CompletionListProvider.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

package org.bsplines.ltexls.server

import org.bsplines.ltexls.parsing.AnnotatedTextFragment
import org.bsplines.ltexls.parsing.CodeAnnotatedTextBuilder
import org.bsplines.ltexls.parsing.CodeFragment
import org.bsplines.ltexls.parsing.CodeFragmentizer
import org.bsplines.ltexls.settings.SettingsManager
import org.eclipse.lsp4j.CompletionItem
import org.eclipse.lsp4j.CompletionList
import org.eclipse.lsp4j.Position
import org.languagetool.Language
import org.languagetool.language.LanguageIdentifier
import org.languagetool.markup.AnnotatedText

class CompletionListProvider(
val settingsManager: SettingsManager,
) {
private val fullCompletionListMap: MutableMap<String, List<String>> = HashMap()
private val languageIdentifier = LanguageIdentifier()

fun createCompletionList(document: LtexTextDocumentItem, position: Position): CompletionList {
val codeFragmentPositionPair: Pair<CodeFragment, Int> =
getCodeFragmentFromPosition(document, position) ?: return CompletionList(emptyList())

val annotatedTextFragment: AnnotatedTextFragment =
buildAnnotatedTextFragment(document, codeFragmentPositionPair.first)

val languageShortCode: String = getLanguageShortCode(annotatedTextFragment) ?:
return CompletionList(emptyList())

val prefix: String = getPrefixFromPosition(
annotatedTextFragment.codeFragment.code, codeFragmentPositionPair.second
)
if (prefix.isEmpty()) return CompletionList(emptyList())

val fullCompletionList: List<String> = getFullCompletionList(languageShortCode)
if (fullCompletionList.isEmpty()) return CompletionList(emptyList())

val completionList = ArrayList<CompletionItem>()

for (entry: String in annotatedTextFragment.codeFragment.settings.dictionary) {
if (entry.startsWith(prefix)) completionList.add(CompletionItem(entry))
}

for (entry: String in fullCompletionList) {
if (entry.startsWith(prefix)) completionList.add(CompletionItem(entry))
}

return CompletionList(completionList)
}

private fun getCodeFragmentFromPosition(
document: LtexTextDocumentItem,
position: Position,
): Pair<CodeFragment, Int>? {
val codeFragmentizer: CodeFragmentizer = CodeFragmentizer.create(document.languageId)
val code: String = document.text
val codeFragments: List<CodeFragment> =
codeFragmentizer.fragmentize(code, this.settingsManager.settings)
val pos: Int = document.convertPosition(position)

var matchingCodeFragment: CodeFragment? = null

for (codeFragment: CodeFragment in codeFragments) {
if (
(codeFragment.fromPos <= pos) && (pos < codeFragment.fromPos + codeFragment.code.length)
) {
if (
(matchingCodeFragment == null)
|| (codeFragment.fromPos > matchingCodeFragment.fromPos)
) {
matchingCodeFragment = codeFragment
}
}
}

return if (matchingCodeFragment != null) {
Pair(matchingCodeFragment, pos - matchingCodeFragment.fromPos)
} else {
null
}
}

private fun buildAnnotatedTextFragment(
document: LtexTextDocumentItem,
codeFragment: CodeFragment,
): AnnotatedTextFragment {
val builder: CodeAnnotatedTextBuilder = CodeAnnotatedTextBuilder.create(
codeFragment.codeLanguageId)
builder.setSettings(codeFragment.settings)
builder.addCode(codeFragment.code)
val annotatedText: AnnotatedText = builder.build()
return AnnotatedTextFragment(annotatedText, codeFragment, document)
}

private fun getLanguageShortCode(
annotatedTextFragment: AnnotatedTextFragment,
): String? {
return if (annotatedTextFragment.codeFragment.settings.languageShortCode == "auto") {
val cleanText: String = this.languageIdentifier.cleanAndShortenText(
annotatedTextFragment.annotatedText.plainText
)
val language: Language? = this.languageIdentifier.detectLanguage(cleanText)
language?.shortCodeWithCountryAndVariant
} else {
annotatedTextFragment.codeFragment.settings.languageShortCode
}
}

private fun getPrefixFromPosition(code: String, pos: Int): String {
if (pos >= code.length) return ""

for (curPos: Int in pos - 1 downTo 0) {
val character: Char = code[curPos]
if (!character.isLetter() && (character != '-')) return code.substring(curPos + 1, pos)
}

return code.substring(0, pos)
}

private fun getFullCompletionList(languageShortCode: String): List<String> {
var fullCompletionList: List<String>? = fullCompletionListMap[languageShortCode]
if (fullCompletionList != null) return fullCompletionList

if (!LANGUAGE_SHORT_CODE_REGEX.matches(languageShortCode)) return emptyList()

val completionListText: String =
javaClass.getResource("/completionList.$languageShortCode.txt")?.readText()?.trim() ?: ""

fullCompletionList = if (completionListText.isNotEmpty()) {
completionListText.split('\n')
} else {
emptyList()
}

fullCompletionListMap[languageShortCode] = fullCompletionList

return fullCompletionList
}

companion object {
private val LANGUAGE_SHORT_CODE_REGEX = Regex("^[-A-Za-z]+$")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import org.bsplines.ltexls.tools.I18n
import org.bsplines.ltexls.tools.Logging
import org.eclipse.lsp4j.ClientCapabilities
import org.eclipse.lsp4j.CodeActionOptions
import org.eclipse.lsp4j.CompletionOptions
import org.eclipse.lsp4j.ExecuteCommandOptions
import org.eclipse.lsp4j.InitializeParams
import org.eclipse.lsp4j.InitializeResult
import org.eclipse.lsp4j.ServerCapabilities
import org.eclipse.lsp4j.TextDocumentSyncKind
import org.eclipse.lsp4j.WindowClientCapabilities
import org.eclipse.lsp4j.jsonrpc.messages.Either
import org.eclipse.lsp4j.services.LanguageClient
import org.eclipse.lsp4j.services.LanguageClientAware
import org.eclipse.lsp4j.services.LanguageServer
Expand All @@ -39,6 +41,7 @@ class LtexLanguageServer : LanguageServer, LanguageClientAware {
val settingsManager = SettingsManager()
val documentChecker = DocumentChecker(this.settingsManager)
val codeActionProvider = CodeActionProvider(this.settingsManager)
val completionListProvider = CompletionListProvider(this.settingsManager)
val ltexTextDocumentService = LtexTextDocumentService(this)
val ltexWorkspaceService = LtexWorkspaceService(this)
val startupInstant: Instant = Instant.now()
Expand Down Expand Up @@ -93,6 +96,7 @@ class LtexLanguageServer : LanguageServer, LanguageClientAware {

serverCapabilities.codeActionProvider =
Either.forRight(CodeActionOptions(CodeActionProvider.getCodeActionKinds()))
serverCapabilities.completionProvider = CompletionOptions()
serverCapabilities.executeCommandProvider =
ExecuteCommandOptions(LtexWorkspaceService.getCommandNames())
serverCapabilities.textDocumentSync = Either.forLeft(TextDocumentSyncKind.Full)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,18 @@ class LtexTextDocumentService(

override fun completion(params: CompletionParams):
CompletableFuture<Either<List<CompletionItem>, CompletionList>> {
return CompletableFuture.completedFuture(Either.forLeft(emptyList()))
val uri: String = params.textDocument?.uri ?:
return CompletableFuture.completedFuture(Either.forLeft(emptyList()))
val document: LtexTextDocumentItem = getDocument(uri) ?: run {
Logging.logger.warning(I18n.format("couldNotFindDocumentWithUri", uri))
return CompletableFuture.completedFuture(Either.forLeft(emptyList()))
}

return CompletableFuture.completedFuture(
Either.forRight(
languageServer.completionListProvider.createCompletionList(document, params.position)
)
)
}

override fun resolveCompletionItem(completionItem: CompletionItem):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

package org.bsplines.ltexls.server

import org.eclipse.lsp4j.CompletionItem
import org.eclipse.lsp4j.CompletionList
import org.eclipse.lsp4j.Position
import kotlin.test.Test
import kotlin.test.assertTrue

class CompletionListProviderTest {
@Test
fun testCreateCompletionList() {
val languageServer = LtexLanguageServer()
languageServer.settingsManager.settings = languageServer.settingsManager.settings.copy(
_allDictionaries = mapOf(Pair("en-US", setOf("testfoobar")))
)

val document =
LtexTextDocumentItem(languageServer, "untitled:test.md", "markdown", 1, "This is a test.\n")
val completionList: CompletionList =
languageServer.completionListProvider.createCompletionList(document, Position(0, 14))

assertTrue(completionList.items.size >= 10)
var containsDictionaryWord = false

for (completionItem: CompletionItem in completionList.items) {
val entry: String = completionItem.label
assertTrue(entry.startsWith("test"))
if (entry == "testfoobar") containsDictionaryWord = true
}

assertTrue(containsDictionaryWord)
}
}
1 change: 1 addition & 0 deletions src/test/resources/LtexLanguageServerTestLog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ Params: {
Result: {
"capabilities": {
"textDocumentSync": 1,
"completionProvider": {},
"codeActionProvider": {
"codeActionKinds": [
"quickfix.ltex.acceptSuggestions"
Expand Down
Loading

0 comments on commit dec5962

Please sign in to comment.