feat: Add textsync parser; Improvements

This commit is contained in:
Myzel394 2024-07-28 12:59:20 +02:00
parent c129b6507a
commit da4577ac22
No known key found for this signature in database
GPG Key ID: DEC4AAB876F73185
7 changed files with 240 additions and 30 deletions

2
.gitignore vendored
View File

@ -23,3 +23,5 @@ go.work.sum
# env file
.env
test.lua

154
common/parser.go Normal file
View File

@ -0,0 +1,154 @@
package common
import (
"fmt"
"regexp"
"strings"
)
type SimpleConfigPosition struct {
Line int
}
type SimpleConfigLine struct {
Value string
Position SimpleConfigPosition
}
func (l SimpleConfigLine) IsCursorAtRootOption(cursor int) bool {
if cursor <= len(l.Value) {
return true
}
return false
}
type SimpleConfigOptions struct {
Separator string
IgnorePattern regexp.Regexp
AvailableOptions *map[string]Option
}
type SimpleConfigParser struct {
Lines map[string]SimpleConfigLine
Options SimpleConfigOptions
}
type OptionAlreadyExistsError struct {
Option string
}
func (e OptionAlreadyExistsError) Error() string {
return fmt.Sprintf("Option %s already exists", e.Option)
}
type OptionUnknownError struct {
Option string
}
func (e OptionUnknownError) Error() string {
return fmt.Sprintf("Option '%s' does not exist", e.Option)
}
type MalformedLineError struct {
Line string
}
func (e MalformedLineError) Error() string {
return fmt.Sprintf("Malformed line: %s", e.Line)
}
type LineNotFoundError struct {}
func (e LineNotFoundError) Error() string {
return "Line not found"
}
func (p *SimpleConfigParser) AddLine(line string, lineNumber int) error {
parts := strings.SplitN(line, p.Options.Separator, 2)
if len(parts) == 0 {
return MalformedLineError{
Line: line,
}
}
option := parts[0]
if _, exists := (*p.Options.AvailableOptions)[option]; !exists {
return OptionUnknownError{
Option: option,
}
}
value := ""
if len(parts) > 1 {
value = parts[1]
}
if _, exists := p.Lines[option]; exists {
return OptionAlreadyExistsError{
Option: option,
}
}
p.Lines[option] = SimpleConfigLine{
Value: value,
Position: SimpleConfigPosition{
Line: lineNumber,
},
}
return nil
}
func (p *SimpleConfigParser) ReplaceOption(option string, value string) {
p.Lines[option] = SimpleConfigLine{
Value: value,
Position: SimpleConfigPosition{
Line: p.Lines[option].Position.Line,
},
}
}
func (p *SimpleConfigParser) RemoveOption(option string) {
delete(p.Lines, option)
}
func (p *SimpleConfigParser) UpsertOption(option string, value string) {
if _, exists := p.Lines[option]; exists {
p.ReplaceOption(option, value)
} else {
p.AddLine(option + p.Options.Separator + value, len(p.Lines))
}
}
func (p *SimpleConfigParser) ParseFromFile(content string) []error {
lines := strings.Split(content, "\n")
errors := make([]error, 0)
for index, line := range lines {
if p.Options.IgnorePattern.MatchString(line) {
continue
}
err := p.AddLine(line, index)
if err != nil {
errors = append(errors, err)
}
}
return errors
}
func (p *SimpleConfigParser) Clear() {
clear(p.Lines)
}
// TODO: Use better approach: Store an extra array of lines in order; with references to the SimpleConfigLine
func (p SimpleConfigParser) FindByLineNumber(lineNumber int) (string, SimpleConfigLine, error) {
for option, line := range p.Lines {
if line.Position.Line == lineNumber {
return option, line, nil
}
}
return "", SimpleConfigLine{Value: "", Position: SimpleConfigPosition{Line: 0}}, LineNotFoundError{}
}

View File

@ -0,0 +1,26 @@
package handlers
import (
"config-lsp/common"
"regexp"
)
func createOpenSSHConfigParser() common.SimpleConfigParser {
pattern, err := regexp.Compile(`^(?:#|\s*$)`)
if (err != nil) {
panic(err)
}
return common.SimpleConfigParser{
Lines: make(map[string]common.SimpleConfigLine),
Options: common.SimpleConfigOptions{
Separator: " ",
IgnorePattern: *pattern,
AvailableOptions: &Options,
},
}
}
var Parser = createOpenSSHConfigParser()

View File

@ -2,7 +2,7 @@ package handlers
import (
"config-lsp/common"
"strings"
"errors"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
@ -12,20 +12,19 @@ import (
)
func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (interface{}, error) {
line, err := common.GetLine(params.TextDocument.URI, int(params.Position.Line))
option, line, err := Parser.FindByLineNumber(int(params.Position.Line))
if err != nil {
return [...]protocol.CompletionItem{}, err
}
rootOption := getCurrentOption(line, int(params.Position.Character))
if (rootOption == "") {
if err == nil {
if line.IsCursorAtRootOption(int(params.Position.Character)) {
return getRootCompletions(), nil
} else {
return getOptionCompletions(option), nil
}
} else if errors.Is(err, common.LineNotFoundError{}) {
return getRootCompletions(), nil
} else {
return getOptionCompletions(rootOption), nil
}
return nil, err
}
func getRootCompletions() []protocol.CompletionItem {
@ -73,17 +72,3 @@ func getOptionCompletions(optionName string) []protocol.CompletionItem {
return []protocol.CompletionItem{}
}
func getCurrentOption(line string, position int) string {
words := strings.Split(line, " ")
if len(words) == 0 {
return ""
}
if (position <= len(words[0])) {
return ""
}
return words[0]
}

View File

@ -0,0 +1,16 @@
package handlers
import (
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
// Todo: Implement incremental parsing
func TextDocumentDidChange(context *glsp.Context, params *protocol.DidChangeTextDocumentParams) error {
content := params.ContentChanges[0].(protocol.TextDocumentContentChangeEventWhole).Text
Parser.Clear()
Parser.ParseFromFile(content)
return nil
}

View File

@ -0,0 +1,26 @@
package handlers
import (
"os"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func TextDocumentDidOpen(context *glsp.Context, params *protocol.DidOpenTextDocumentParams) error {
readBytes, err := os.ReadFile(params.TextDocument.URI[len("file://"):])
if err != nil {
return err
}
errors := Parser.ParseFromFile(string(readBytes))
if len(errors) > 0 {
return errors[0]
}
return nil
}

11
main.go
View File

@ -1,8 +1,6 @@
package main
import (
"fmt"
openssh "config-lsp/handlers/openssh"
"github.com/tliron/commonlog"
@ -27,10 +25,12 @@ func main() {
commonlog.Configure(1, nil)
handler = protocol.Handler{
Initialize: initialize,
Initialize: initialize,
Initialized: initialized,
Shutdown: shutdown,
SetTrace: setTrace,
Shutdown: shutdown,
SetTrace: setTrace,
TextDocumentDidOpen: openssh.TextDocumentDidOpen,
TextDocumentDidChange: openssh.TextDocumentDidChange,
TextDocumentCompletion: openssh.TextDocumentCompletion,
}
@ -41,6 +41,7 @@ func main() {
func initialize(context *glsp.Context, params *protocol.InitializeParams) (any, error) {
capabilities := handler.CreateServerCapabilities()
capabilities.TextDocumentSync = protocol.TextDocumentSyncKindFull
return protocol.InitializeResult{
Capabilities: capabilities,