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

lsp: Add support for CompletionContext #1997

Merged
merged 3 commits into from
Jan 6, 2024
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,7 @@
.. _#1997: https://github.com/fox0430/moe/pull/1997

Added
.....

- `#1997`_ lsp: Add support for CompletionContext

17 changes: 4 additions & 13 deletions src/moepkg/autocomplete.nim
Original file line number Diff line number Diff line change
Expand Up @@ -142,20 +142,11 @@ proc collectSuggestions*(

result.add pairs.sortedByIt(it.val).reversed.mapIt(it.key.toRunes)

proc collectLspSuggestions*(
list: CompletionList,
word: Runes): seq[Runes] {.inline.} =
## Collect words for suggestion from `CompletionList`.
## TODO: Rewrite
proc collectLspSuggestions*( list: CompletionList,): seq[Runes] {.inline.}=
## Collect words for suggestion from `CompletionList`.
## TODO: Rewrite

result = collect:
for item in list.items:
if contains($item.insertText, $word): item.insertText

proc cmpByName(x, y: Runes): int = cmp($x, $y)

result.sort(cmpByName)
result.reverse
result = list.items.mapIt(it.insertText)

proc getTextInBuffers*(
bufStatus: seq[BufferStatus],
Expand Down
52 changes: 29 additions & 23 deletions src/moepkg/insertmode.nim
Original file line number Diff line number Diff line change
Expand Up @@ -89,28 +89,34 @@ proc deleteCurrentCursor(status: var EditorStatus) {.inline.} =
currentMainWindowNode.currentColumn,
status.settings.standard.autoDeleteParen)

proc sendCompletionRequest(status: var EditorStatus): Result[(), string] =
## Send didChange and completion requests to the LSP server.

block:
currentBufStatus.version.inc

let err = lspClient.textDocumentDidChange(
currentBufStatus.version,
$currentBufStatus.path.absolutePath,
currentBufStatus.buffer.toString)
if err.isErr:
return Result[(), string ].err err.error

block:
let err = lspClient.textDocumentCompletion(
currentBufStatus.id,
$currentBufStatus.path.absolutePath,
currentMainWindowNode.bufferPosition)
if err.isErr:
return Result[(), string ].err err.error

return Result[(), string ].ok ()
proc sendCompletionRequest(
status: var EditorStatus,
r: Rune): Result[(), string] =
## Send didChange and completion requests to the LSP server.

block:
currentBufStatus.version.inc

let err = lspClient.textDocumentDidChange(
currentBufStatus.version,
$currentBufStatus.path.absolutePath,
currentBufStatus.buffer.toString)
if err.isErr:
return Result[(), string ].err err.error

block:
let isIncompleteTrigger = status.suggestionwindow.isSome

let err = lspClient.textDocumentCompletion(
currentBufStatus.id,
$currentBufStatus.path.absolutePath,
currentMainWindowNode.bufferPosition,
isIncompleteTrigger,
$r)
if err.isErr:
return Result[(), string ].err err.error

return Result[(), string ].ok ()

proc insertToBuffer(status: var EditorStatus, r: Rune) {.inline.} =
if currentBufStatus.isInsertMultiMode:
Expand All @@ -126,7 +132,7 @@ proc insertToBuffer(status: var EditorStatus, r: Rune) {.inline.} =
r)

if status.lspClients.contains(currentBufStatus.langId):
let err = status.sendCompletionRequest
let err = status.sendCompletionRequest(r)
if err.isErr:
error err.error

Expand Down
44 changes: 30 additions & 14 deletions src/moepkg/lsp/client.nim
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ type

LspCapabilities* = object
hover*: bool
completion*: bool
completion*: Option[CompletionOptions]

LspProgressTable* = Table[ProgressToken, ProgressReport]

Expand Down Expand Up @@ -336,7 +336,7 @@ proc initInitializeParams*(
commitCharactersSupport: some(false),
deprecatedSupport: some(false)
)),
contextSupport: some(false)
contextSupport: some(true)
))
)),
window: some(WindowCapabilities(
Expand All @@ -362,7 +362,7 @@ proc setCapabilities(c: var LspClient, initResult: InitializeResult) =
capabilities.hover = true

if initResult.capabilities.completionProvider.isSome:
capabilities.completion = true
capabilities.completion = initResult.capabilities.completionProvider

c.capabilities = some(capabilities)

Expand Down Expand Up @@ -605,33 +605,44 @@ proc textDocumentHover*(

return R[(), string].ok ()

template isTriggerCharacter(options: CompletionOptions, ch: string): bool =
options.triggerCharacters.isSome and
options.triggerCharacters.get.contains(character)

proc initCompletionParams*(
path: string,
position: BufferPosition,
triggerCharacter: string = ""): CompletionParams =
options: CompletionOptions,
isIncompleteTrigger: bool,
character: string): CompletionParams =

let
triggerKind =
if triggerCharacter.len > 0: 2
else: 1

trirgerChar =
if triggerCharacter.len > 0: some(triggerCharacter)
triggerChar =
if isTriggerCharacter(options, character): some(character)
else: none(string)

triggerKind =
if triggerChar.isSome:
CompletionTriggerKind.TriggerCharacter.int
elif isIncompleteTrigger:
CompletionTriggerKind.TriggerForIncompleteCompletions.int
else:
CompletionTriggerKind.Invoked.int

return CompletionParams(
textDocument: TextDocumentIdentifier(uri: path.pathToUri),
position: position.toLspPosition,
context: some(CompletionContext(
triggerKind: triggerKind,
triggerCharacter: trirgerChar)))
triggerCharacter: triggerChar)))

proc textDocumentCompletion*(
c: var LspClient,
id: int,
path: string,
position: BufferPosition,
triggerCharacter: string = ""): LspSendRequestResult =
isIncompleteTrigger: bool,
character: string): LspSendRequestResult =
## Send a textDocument/completion request to the server.
## https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion

Expand All @@ -641,10 +652,15 @@ proc textDocumentCompletion*(
if not c.isInitialized:
return R[(), string].err "lsp unavailable"

if not c.capabilities.get.completion:
if not c.capabilities.get.completion.isSome:
return R[(), string].err "textDocument/completion unavailable"

let params = %* initCompletionParams(path, position, triggerCharacter)
let params = %* initCompletionParams(
path,
position,
c.capabilities.get.completion.get,
isIncompleteTrigger,
character)

let r = c.request(id, LspMethod.textDocumentCompletion, params)
if r.isErr:
Expand Down
1 change: 0 additions & 1 deletion src/moepkg/lsp/jsonrpc.nim
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ proc readFrame(s: OutputStream): ReadFrameResult =
if contentLen != -1:
let str = s.readStr(contentLen)
if str.isErr:
debug str.error
return ReadFrameResult.err fmt"readStr failed: {str.error}"
debugLog(MessageType.read, "Response: {str.get}")
return ReadFrameResult.ok str.get
Expand Down
2 changes: 2 additions & 0 deletions src/moepkg/lsp/utils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type
LspCompletionItem* = types.CompletionItem
LspCompletionList* = types.CompletionList

LspCompletionTriggerKind* = enums.CompletionTriggerKind

LanguageId* = string

LspMethod* {.pure.} = enum
Expand Down
87 changes: 43 additions & 44 deletions src/moepkg/mainloop.nim
Original file line number Diff line number Diff line change
Expand Up @@ -726,53 +726,52 @@ proc editorMainLoop*(status: var EditorStatus) =

if k.isSome:
key = k.get
else:
continue

if status.suggestionWindow.isSome:
if canHandleInSuggestionWindow(key):
status.suggestionWindow.get.handleKeyInSuggestionWindow(
currentBufStatus,
currentMainWindowNode,
key)
continue
else:
status.updateAfterInsertFromSuggestion
if status.suggestionWindow.isSome:
status.suggestionWindow.close

if isResizeKey(key):
updateTerminalSize()
status.resize
continue
elif isPasteKey(key):
let pasteBuffer = getPasteBuffer()
if pasteBuffer.isSome: status.insertPasteBuffer(pasteBuffer.get)
continue

command.add key

let inputState = invokeCommand(
currentBufStatus.mode,
command,
status.recodingOperationRegister)
case inputState:
of InputState.Continue:
continue
of InputState.Valid:
let interruptKey = status.execEditorCommand(command)
if interruptKey.isSome:
key = interruptKey.get
isSkipGetKey = true

command.clear
if canHandleInSuggestionWindow(key):
status.suggestionWindow.get.handleKeyInSuggestionWindow(
currentBufStatus,
currentMainWindowNode,
key)
continue
else:
if isEnterKey(key):
status.updateAfterInsertFromSuggestion
elif isEscKey(key):
status.suggestionWindow.close

if isResizeKey(key):
updateTerminalSize()
status.resize
continue
else:
command.clear
of InputState.Invalid, InputState.Cancel:
command.clear
currentBufStatus.cmdLoop = 0
continue
elif isPasteKey(key):
let pasteBuffer = getPasteBuffer()
if pasteBuffer.isSome: status.insertPasteBuffer(pasteBuffer.get)
continue

command.add key

let inputState = invokeCommand(
currentBufStatus.mode,
command,
status.recodingOperationRegister)
case inputState:
of InputState.Continue:
continue
of InputState.Valid:
let interruptKey = status.execEditorCommand(command)
if interruptKey.isSome:
key = interruptKey.get
isSkipGetKey = true

command.clear
continue
else:
command.clear
of InputState.Invalid, InputState.Cancel:
command.clear
currentBufStatus.cmdLoop = 0
continue

if status.isOpenSuggestWindow:
status.tryOpenSuggestWindow
Expand Down
28 changes: 6 additions & 22 deletions src/moepkg/suggestionwindow.nim
Original file line number Diff line number Diff line change
Expand Up @@ -166,19 +166,18 @@ proc initSuggestionWindow*(

proc initLspSuggestionWindow*(
completionList: CompletionList,
word, currentLineText: Runes,
firstColumn, lastColumn: int): Option[SuggestionWindow] =
currentLineText: Runes,
firstColumn: int): Option[SuggestionWindow] =
## Suggestions are get from `CompletionList`.
## Ignore `WordDictionary`.
## `word` is the inputted text.

var suggestionWindow: SuggestionWindow
suggestionWindow.inputWord = word
suggestionWindow.firstColumn = firstColumn
suggestionWindow.lastColumn = lastColumn
suggestionWindow.lastColumn = -1
suggestionWindow.isPath = false

suggestionWindow.suggestoins = completionList.collectLspSuggestions(word)
suggestionWindow.suggestoins = completionList.collectLspSuggestions

if suggestionWindow.suggestoins.len > 0:
suggestionWindow.selectedSuggestion = -1
Expand Down Expand Up @@ -311,24 +310,10 @@ proc buildLspSuggestionWindow*(
currenWindowNode: WindowNode): Option[SuggestionWindow] =
## Build a suggestoin window from `CompletionList`.

let
(word, firstColumn, lastColumn) = extractWordBeforeCursor(
bufStatus,
currenWindowNode).get

# Eliminate the word on the cursor.
firstDeletedIndex = bufStatus.buffer.calcIndexInEntireBuffer(
currenWindowNode.currentLine,
firstColumn,
true)
lastDeletedIndex = firstDeletedIndex + word.high

initLspSuggestionWindow(
bufStatus.completionList,
word,
bufStatus.buffer[currenWindowNode.currentLine],
firstColumn,
lastColumn)
currenWindowNode.currentColumn)

proc tryOpenSuggestionWindow*(
wordDictionary: var WordDictionary,
Expand All @@ -348,8 +333,7 @@ proc tryOpenLspSuggestionWindow*(
bufStatus: BufferStatus,
currenWindowNode: WindowNode): Option[SuggestionWindow] =

if wordExistsBeforeCursor(bufStatus, currenWindowNode):
return buildLspSuggestionWindow(bufStatus, currenWindowNode)
return buildLspSuggestionWindow(bufStatus, currenWindowNode)

proc calcSuggestionWindowPosition*(
suggestionWindow: SuggestionWindow,
Expand Down
13 changes: 12 additions & 1 deletion tests/tlspclient.nim
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,17 @@ suite "lsp: Send requests":
let err = client.textDocumentDidOpen(path, LanguageId, Text)
assert err.isOk

block:
const SecondVersion = 2
let changedText = "echo 1\ne"
check client.textDocumentDidChange(SecondVersion, path, changedText).isOk

let position = BufferPosition(line: 1, column: 0)
check client.textDocumentCompletion(Id, path, position).isOk
const IsIncompleteTrigger = false
check client.textDocumentCompletion(
Id,
path,
position,
IsIncompleteTrigger,
"e").isOk
check client.waitingResponse.get == LspMethod.textDocumentCompletion