mirror of
https://github.com/Myzel394/config-lsp.git
synced 2025-06-18 23:15:26 +02:00
feat: Add textsync parser; Improvements
This commit is contained in:
parent
c129b6507a
commit
da4577ac22
2
.gitignore
vendored
2
.gitignore
vendored
@ -23,3 +23,5 @@ go.work.sum
|
||||
|
||||
# env file
|
||||
.env
|
||||
|
||||
test.lua
|
||||
|
154
common/parser.go
Normal file
154
common/parser.go
Normal 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{}
|
||||
}
|
||||
|
26
handlers/openssh/shared.go
Normal file
26
handlers/openssh/shared.go
Normal 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()
|
||||
|
@ -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(rootOption), nil
|
||||
return getOptionCompletions(option), nil
|
||||
}
|
||||
} else if errors.Is(err, common.LineNotFoundError{}) {
|
||||
return getRootCompletions(), 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]
|
||||
}
|
||||
|
||||
|
16
handlers/openssh/text-document-did-change.go
Normal file
16
handlers/openssh/text-document-did-change.go
Normal 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
|
||||
}
|
26
handlers/openssh/text-document-did-open.go
Normal file
26
handlers/openssh/text-document-did-open.go
Normal 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
|
||||
}
|
||||
|
||||
|
5
main.go
5
main.go
@ -1,8 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
openssh "config-lsp/handlers/openssh"
|
||||
|
||||
"github.com/tliron/commonlog"
|
||||
@ -31,6 +29,8 @@ func main() {
|
||||
Initialized: initialized,
|
||||
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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user