mirror of
https://github.com/Myzel394/config-lsp.git
synced 2025-06-18 23:15:26 +02:00
commit
6c3e0d9d23
25
common/errors.go
Normal file
25
common/errors.go
Normal file
@ -0,0 +1,25 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
type LSPError struct {
|
||||
Range LocationRange
|
||||
Err error
|
||||
}
|
||||
|
||||
func (l LSPError) ToDiagnostic() protocol.Diagnostic {
|
||||
return protocol.Diagnostic{
|
||||
Range: l.Range.ToLSPRange(),
|
||||
Message: l.Err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
type SyntaxError struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
func (s SyntaxError) Error() string {
|
||||
return s.Message
|
||||
}
|
65
common/location.go
Normal file
65
common/location.go
Normal file
@ -0,0 +1,65 @@
|
||||
package common
|
||||
|
||||
import protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
|
||||
type Location struct {
|
||||
Line uint32
|
||||
Character uint32
|
||||
}
|
||||
|
||||
type LocationRange struct {
|
||||
Start Location
|
||||
End Location
|
||||
}
|
||||
|
||||
func (l LocationRange) ToLSPRange() protocol.Range {
|
||||
return protocol.Range{
|
||||
Start: protocol.Position{
|
||||
Line: l.Start.Line,
|
||||
Character: l.Start.Character,
|
||||
},
|
||||
End: protocol.Position{
|
||||
Line: l.End.Line,
|
||||
Character: l.End.Character,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LocationRange) ChangeBothLines(newLine uint32) {
|
||||
l.Start.Line = newLine
|
||||
l.End.Line = newLine
|
||||
}
|
||||
|
||||
func (l LocationRange) ContainsCursor(line uint32, character uint32) bool {
|
||||
return line == l.Start.Line && character >= l.Start.Character && character <= l.End.Character
|
||||
}
|
||||
|
||||
func (l LocationRange) ContainsCursorByCharacter(character uint32) bool {
|
||||
return character >= l.Start.Character && character <= l.End.Character
|
||||
}
|
||||
|
||||
func CreateFullLineRange(line uint32) LocationRange {
|
||||
return LocationRange{
|
||||
Start: Location{
|
||||
Line: line,
|
||||
Character: 0,
|
||||
},
|
||||
End: Location{
|
||||
Line: line,
|
||||
Character: 999999,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func CreateSingleCharRange(line uint32, character uint32) LocationRange {
|
||||
return LocationRange{
|
||||
Start: Location{
|
||||
Line: line,
|
||||
Character: character,
|
||||
},
|
||||
End: Location{
|
||||
Line: line,
|
||||
Character: character,
|
||||
},
|
||||
}
|
||||
}
|
BIN
config-lsp
BIN
config-lsp
Binary file not shown.
@ -3,6 +3,7 @@ package docvalues
|
||||
import (
|
||||
"config-lsp/utils"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -173,3 +174,9 @@ func SingleEnumValue(value string) EnumValue {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func DomainValue() Value {
|
||||
return RegexValue{
|
||||
Regex: *regexp.MustCompile(`^.+?\..+$`),
|
||||
}
|
||||
}
|
||||
|
1
go.mod
1
go.mod
@ -8,6 +8,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/iancoleman/strcase v0.3.0 // indirect
|
||||
|
2
go.sum
2
go.sum
@ -1,3 +1,5 @@
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
|
126
handlers/hosts/Hosts.g4
Normal file
126
handlers/hosts/Hosts.g4
Normal file
@ -0,0 +1,126 @@
|
||||
grammar Hosts;
|
||||
|
||||
lineStatement
|
||||
: SEPARATOR? entry SEPARATOR? leadingComment? EOF
|
||||
;
|
||||
|
||||
entry
|
||||
: ipAddress SEPARATOR hostname (SEPARATOR aliases)?
|
||||
;
|
||||
|
||||
aliases
|
||||
: (alias SEPARATOR?)+
|
||||
;
|
||||
|
||||
alias
|
||||
: DOMAIN
|
||||
;
|
||||
|
||||
hostname
|
||||
: domain
|
||||
;
|
||||
|
||||
domain
|
||||
: DOMAIN
|
||||
;
|
||||
|
||||
ipAddress
|
||||
: (ipv4Address | ipv6Address)
|
||||
;
|
||||
|
||||
ipv4Address
|
||||
: singleIPv4Address
|
||||
// Allow optional range to tell user ranges are not allowed
|
||||
ipRange?
|
||||
;
|
||||
|
||||
singleIPv4Address
|
||||
: ipv4Digit DOT ipv4Digit DOT ipv4Digit DOT ipv4Digit
|
||||
;
|
||||
|
||||
// This is not correct but fits for now
|
||||
ipv6Address
|
||||
: singleIPv6Address
|
||||
// Allow optional range to tell user ranges are not allowed
|
||||
ipRange?
|
||||
;
|
||||
|
||||
singleIPv6Address
|
||||
: (ipv6Octet COLON)+ ipv6Octet
|
||||
;
|
||||
|
||||
ipv4Digit
|
||||
: DIGITS
|
||||
;
|
||||
|
||||
ipv6Octet
|
||||
: OCTETS
|
||||
;
|
||||
|
||||
ipRange
|
||||
: SLASH ipRangeBits
|
||||
;
|
||||
|
||||
ipRangeBits
|
||||
: DIGITS
|
||||
;
|
||||
|
||||
comment
|
||||
: COMMENTLINE
|
||||
;
|
||||
|
||||
leadingComment
|
||||
: COMMENTLINE
|
||||
;
|
||||
|
||||
COMMENTLINE
|
||||
: HASHTAG ~[\r\n]+
|
||||
;
|
||||
|
||||
SLASH
|
||||
: '/'
|
||||
;
|
||||
|
||||
DOT
|
||||
: '.'
|
||||
;
|
||||
|
||||
COLON
|
||||
: ':'
|
||||
;
|
||||
|
||||
HASHTAG
|
||||
: '#'
|
||||
;
|
||||
|
||||
SEPARATOR
|
||||
: [ \t]+
|
||||
;
|
||||
|
||||
NEWLINE
|
||||
: [\r]? [\n]
|
||||
;
|
||||
|
||||
DIGITS
|
||||
: DIGIT+
|
||||
;
|
||||
|
||||
fragment DIGIT
|
||||
: [0-9]
|
||||
;
|
||||
|
||||
OCTETS
|
||||
: OCTET+
|
||||
;
|
||||
|
||||
fragment OCTET
|
||||
: [0-9a-fA-F]
|
||||
;
|
||||
|
||||
DOMAIN
|
||||
: ((STRING)+ (DOT [a-zA-Z]+)*)
|
||||
;
|
||||
|
||||
fragment STRING
|
||||
: ~(' ' | '\t' | '\n' | '\r' | '#' | '.')
|
||||
;
|
13
handlers/hosts/fields/documentation-fields.go
Normal file
13
handlers/hosts/fields/documentation-fields.go
Normal file
@ -0,0 +1,13 @@
|
||||
package fields
|
||||
|
||||
import docvalues "config-lsp/doc-values"
|
||||
|
||||
var IPAddressField = docvalues.IPAddressValue{
|
||||
AllowIPv4: true,
|
||||
AllowIPv6: true,
|
||||
}
|
||||
|
||||
var HostnameField = docvalues.DocumentationValue{
|
||||
Documentation: `Host names may contain only alphanumeric characters, minus signs ("-"), and periods ("."). They must begin with an alphabetic character and end with an alphanumeric character. Optional aliases provide for name changes, alternate spellings, shorter hostnames, or generic hostnames (for example, localhost).`,
|
||||
Value: docvalues.DomainValue(),
|
||||
}
|
42
handlers/hosts/handlers/analyzer/analyzer.go
Normal file
42
handlers/hosts/handlers/analyzer/analyzer.go
Normal file
@ -0,0 +1,42 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/utils"
|
||||
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func Analyze(parser *HostsParser) []protocol.Diagnostic {
|
||||
errors := analyzeEntriesSetCorrectly(*parser)
|
||||
|
||||
if len(errors) > 0 {
|
||||
return utils.Map(
|
||||
errors,
|
||||
func(err common.LSPError) protocol.Diagnostic {
|
||||
return err.ToDiagnostic()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
errors = analyzeEntriesAreValid(*parser)
|
||||
|
||||
if len(errors) > 0 {
|
||||
return utils.Map(
|
||||
errors,
|
||||
func(err common.LSPError) protocol.Diagnostic {
|
||||
return err.ToDiagnostic()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
errors = append(errors, analyzeDoubleIPs(parser)...)
|
||||
errors = append(errors, analyzeDoubleHostNames(parser)...)
|
||||
|
||||
return utils.Map(
|
||||
errors,
|
||||
func(err common.LSPError) protocol.Diagnostic {
|
||||
return err.ToDiagnostic()
|
||||
},
|
||||
)
|
||||
}
|
39
handlers/hosts/handlers/analyzer/double_ips.go
Normal file
39
handlers/hosts/handlers/analyzer/double_ips.go
Normal file
@ -0,0 +1,39 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"net"
|
||||
)
|
||||
|
||||
func ipToString(ip net.IPAddr) string {
|
||||
return ip.IP.String()
|
||||
}
|
||||
|
||||
func analyzeDoubleIPs(p *HostsParser) []common.LSPError {
|
||||
errors := make([]common.LSPError, 0)
|
||||
ips := make(map[string]uint32)
|
||||
|
||||
p.DoubleIPs = make(map[uint32]DuplicateIPDeclaration)
|
||||
|
||||
for lineNumber, entry := range p.Tree.Entries {
|
||||
if entry.IPAddress != nil {
|
||||
key := ipToString(entry.IPAddress.Value)
|
||||
|
||||
if foundLine, found := ips[key]; found {
|
||||
err := DuplicateIPDeclaration{
|
||||
AlreadyFoundAt: foundLine,
|
||||
}
|
||||
|
||||
p.DoubleIPs[lineNumber] = err
|
||||
errors = append(errors, common.LSPError{
|
||||
Range: entry.IPAddress.Location,
|
||||
Err: err,
|
||||
})
|
||||
} else {
|
||||
ips[key] = lineNumber
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
61
handlers/hosts/handlers/analyzer/double_ips_test.go
Normal file
61
handlers/hosts/handlers/analyzer/double_ips_test.go
Normal file
@ -0,0 +1,61 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/utils"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWorksWithNonDoubleIPs(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
1.2.3.4 hello.com
|
||||
5.5.5.5 world.com
|
||||
1.2.3.5 foo.com
|
||||
1.2.3.6 bar.com
|
||||
`)
|
||||
|
||||
parser := CreateNewHostsParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Fatalf("PARER FAILED! Expected no errors, but got %v", errors)
|
||||
}
|
||||
|
||||
errors = analyzeDoubleIPs(&parser)
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Errorf("Expected no errors, but got %v", errors)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorksWithDoubleIPs(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
1.2.3.4 hello.com
|
||||
5.5.5.5 world.com
|
||||
1.2.3.4 foo.com
|
||||
`)
|
||||
|
||||
parser := CreateNewHostsParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Fatalf("PARER FAILED! Expected no errors, but got %v", errors)
|
||||
}
|
||||
|
||||
errors = analyzeDoubleIPs(&parser)
|
||||
|
||||
if !(len(errors) == 1) {
|
||||
t.Errorf("Expected 1 error, but got %v", len(errors))
|
||||
}
|
||||
|
||||
if !(errors[0].Range.Start.Line == 2) {
|
||||
t.Errorf("Expected error on line 2, but got %v", errors[0].Range.Start.Line)
|
||||
}
|
||||
|
||||
if !(errors[0].Err.(DuplicateIPDeclaration).AlreadyFoundAt == 0) {
|
||||
t.Errorf("Expected error on line 0, but got %v", errors[0].Err.(DuplicateIPDeclaration).AlreadyFoundAt)
|
||||
}
|
||||
}
|
20
handlers/hosts/handlers/analyzer/errors.go
Normal file
20
handlers/hosts/handlers/analyzer/errors.go
Normal file
@ -0,0 +1,20 @@
|
||||
package analyzer
|
||||
|
||||
import "fmt"
|
||||
|
||||
type DuplicateHostEntry struct {
|
||||
AlreadyFoundAt uint32
|
||||
Hostname string
|
||||
}
|
||||
|
||||
func (d DuplicateHostEntry) Error() string {
|
||||
return fmt.Sprintf("'%s' already defined on line %d", d.Hostname, d.AlreadyFoundAt+1)
|
||||
}
|
||||
|
||||
type DuplicateIPDeclaration struct {
|
||||
AlreadyFoundAt uint32
|
||||
}
|
||||
|
||||
func (d DuplicateIPDeclaration) Error() string {
|
||||
return fmt.Sprintf("This IP address is already defined on line %d", d.AlreadyFoundAt+1)
|
||||
}
|
82
handlers/hosts/handlers/analyzer/handler.go
Normal file
82
handlers/hosts/handlers/analyzer/handler.go
Normal file
@ -0,0 +1,82 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/hosts/parser"
|
||||
"config-lsp/utils"
|
||||
"regexp"
|
||||
|
||||
"github.com/antlr4-go/antlr/v4"
|
||||
)
|
||||
|
||||
func (p *HostsParser) Clear() {
|
||||
p.Tree = HostsTree{
|
||||
Entries: make(map[uint32]*HostsEntry),
|
||||
}
|
||||
p.CommentLines = make(map[uint32]struct{})
|
||||
}
|
||||
|
||||
var commentPattern = regexp.MustCompile(`^\s*#.*$`)
|
||||
var emptyPattern = regexp.MustCompile(`^\s*$`)
|
||||
|
||||
func (p *HostsParser) parseStatement(
|
||||
line uint32,
|
||||
input string,
|
||||
) []common.LSPError {
|
||||
stream := antlr.NewInputStream(input)
|
||||
|
||||
errorListener := createErrorListener(line)
|
||||
lexer := parser.NewHostsLexer(stream)
|
||||
lexer.RemoveErrorListeners()
|
||||
lexer.AddErrorListener(&errorListener)
|
||||
|
||||
errors := errorListener.Errors
|
||||
|
||||
errorListener = createErrorListener(line)
|
||||
tokenStream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel)
|
||||
antlrParser := parser.NewHostsParser(tokenStream)
|
||||
antlrParser.RemoveErrorListeners()
|
||||
antlrParser.AddErrorListener(&errorListener)
|
||||
|
||||
listener := createHostsFileListener(p, line)
|
||||
antlr.ParseTreeWalkerDefault.Walk(
|
||||
&listener,
|
||||
antlrParser.LineStatement(),
|
||||
)
|
||||
|
||||
errors = append(errors, errorListener.Errors...)
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
func (p *HostsParser) Parse(input string) []common.LSPError {
|
||||
errors := make([]common.LSPError, 0)
|
||||
lines := utils.SplitIntoLines(input)
|
||||
|
||||
for rawLineNumber, line := range lines {
|
||||
lineNumber := uint32(rawLineNumber)
|
||||
|
||||
if commentPattern.MatchString(line) {
|
||||
p.CommentLines[lineNumber] = struct{}{}
|
||||
continue
|
||||
}
|
||||
|
||||
if emptyPattern.MatchString(line) {
|
||||
continue
|
||||
}
|
||||
|
||||
errors = append(
|
||||
errors,
|
||||
p.parseStatement(lineNumber, line)...,
|
||||
)
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
func CreateNewHostsParser() HostsParser {
|
||||
p := HostsParser{}
|
||||
p.Clear()
|
||||
|
||||
return p
|
||||
}
|
144
handlers/hosts/handlers/analyzer/handler_test.go
Normal file
144
handlers/hosts/handlers/analyzer/handler_test.go
Normal file
@ -0,0 +1,144 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/utils"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidSimpleExampleWorks(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
1.2.3.4 hello.com
|
||||
`)
|
||||
|
||||
parser := CreateNewHostsParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Errorf("Expected no errors, but got %v", errors)
|
||||
}
|
||||
|
||||
if !(len(parser.Tree.Entries) == 1) {
|
||||
t.Errorf("Expected 1 entry, but got %v", len(parser.Tree.Entries))
|
||||
}
|
||||
|
||||
if parser.Tree.Entries[0].IPAddress == nil {
|
||||
t.Errorf("Expected IP address to be present, but got nil")
|
||||
}
|
||||
|
||||
if !(parser.Tree.Entries[0].IPAddress.Value.String() == net.ParseIP("1.2.3.4").String()) {
|
||||
t.Errorf("Expected IP address to be 1.2.3.4, but got %v", parser.Tree.Entries[0].IPAddress.Value)
|
||||
}
|
||||
|
||||
if !(parser.Tree.Entries[0].Hostname.Value == "hello.com") {
|
||||
t.Errorf("Expected hostname to be hello.com, but got %v", parser.Tree.Entries[0].Hostname.Value)
|
||||
}
|
||||
|
||||
if !(parser.Tree.Entries[0].Aliases == nil) {
|
||||
t.Errorf("Expected no aliases, but got %v", parser.Tree.Entries[0].Aliases)
|
||||
}
|
||||
|
||||
if !(parser.Tree.Entries[0].Location.Start.Line == 0) {
|
||||
t.Errorf("Expected line to be 1, but got %v", parser.Tree.Entries[0].Location.Start.Line)
|
||||
}
|
||||
|
||||
if !(parser.Tree.Entries[0].Location.Start.Character == 0) {
|
||||
t.Errorf("Expected start to be 0, but got %v", parser.Tree.Entries[0].Location.Start)
|
||||
}
|
||||
|
||||
if !(parser.Tree.Entries[0].Location.End.Character == 17) {
|
||||
t.Errorf("Expected end to be 17, but got %v", parser.Tree.Entries[0].Location.End.Character)
|
||||
}
|
||||
|
||||
if !(parser.Tree.Entries[0].IPAddress.Location.Start.Line == 0) {
|
||||
t.Errorf("Expected IP address line to be 1, but got %v", parser.Tree.Entries[0].IPAddress.Location.Start.Line)
|
||||
}
|
||||
|
||||
if !(parser.Tree.Entries[0].IPAddress.Location.Start.Character == 0) {
|
||||
t.Errorf("Expected IP address start to be 0, but got %v", parser.Tree.Entries[0].IPAddress.Location.Start.Character)
|
||||
}
|
||||
|
||||
if !(parser.Tree.Entries[0].IPAddress.Location.End.Character == 7) {
|
||||
t.Errorf("Expected IP address end to be 7, but got %v", parser.Tree.Entries[0].IPAddress.Location.End.Character)
|
||||
}
|
||||
|
||||
if !(len(parser.CommentLines) == 0) {
|
||||
t.Errorf("Expected no comment lines, but got %v", len(parser.CommentLines))
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidComplexExampleWorks(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
|
||||
# This is a comment
|
||||
1.2.3.4 hello.com test.com # This is another comment
|
||||
5.5.5.5 test.com
|
||||
1.2.3.4 example.com check.com
|
||||
`)
|
||||
|
||||
parser := CreateNewHostsParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Errorf("Expected no errors, but got %v", errors)
|
||||
}
|
||||
|
||||
if !(len(parser.Tree.Entries) == 3) {
|
||||
t.Errorf("Expected 3 entries, but got %v", len(parser.Tree.Entries))
|
||||
}
|
||||
|
||||
if parser.Tree.Entries[2].IPAddress == nil {
|
||||
t.Errorf("Expected IP address to be present, but got nil")
|
||||
}
|
||||
|
||||
if !(parser.Tree.Entries[2].IPAddress.Value.String() == net.ParseIP("1.2.3.4").String()) {
|
||||
t.Errorf("Expected IP address to be 1.2.3.4, but got %v", parser.Tree.Entries[2].IPAddress.Value)
|
||||
}
|
||||
|
||||
if !(len(parser.CommentLines) == 1) {
|
||||
t.Errorf("Expected 1 comment line, but got %v", len(parser.CommentLines))
|
||||
}
|
||||
|
||||
if !(utils.KeyExists(parser.CommentLines, 1)) {
|
||||
t.Errorf("Expected comment line 2 to exist, but it does not")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidExampleWorks(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
1.2.3.4
|
||||
`)
|
||||
|
||||
parser := CreateNewHostsParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) == 0 {
|
||||
t.Errorf("Expected errors, but got none")
|
||||
}
|
||||
|
||||
if !(len(parser.Tree.Entries) == 1) {
|
||||
t.Errorf("Expected 1 entries, but got %v", len(parser.Tree.Entries))
|
||||
}
|
||||
|
||||
if !(len(parser.CommentLines) == 0) {
|
||||
t.Errorf("Expected no comment lines, but got %v", len(parser.CommentLines))
|
||||
}
|
||||
|
||||
if !(parser.Tree.Entries[0].IPAddress.Value.String() == net.ParseIP("1.2.3.4").String()) {
|
||||
t.Errorf("Expected IP address to be nil, but got %v", parser.Tree.Entries[0].IPAddress)
|
||||
}
|
||||
|
||||
if !(parser.Tree.Entries[0].Hostname == nil) {
|
||||
t.Errorf("Expected hostname to be nil, but got %v", parser.Tree.Entries[0].Hostname)
|
||||
}
|
||||
|
||||
if !(parser.Tree.Entries[0].Aliases == nil) {
|
||||
t.Errorf("Expected aliases to be nil, but got %v", parser.Tree.Entries[0].Aliases)
|
||||
}
|
||||
}
|
77
handlers/hosts/handlers/analyzer/hosts.go
Normal file
77
handlers/hosts/handlers/analyzer/hosts.go
Normal file
@ -0,0 +1,77 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/antlr4-go/antlr/v4"
|
||||
)
|
||||
|
||||
func characterRangeFromCtx(
|
||||
ctx antlr.BaseParserRuleContext,
|
||||
) common.LocationRange {
|
||||
line := uint32(ctx.GetStart().GetLine())
|
||||
start := uint32(ctx.GetStart().GetStart())
|
||||
end := uint32(ctx.GetStop().GetStop())
|
||||
|
||||
return common.LocationRange{
|
||||
Start: common.Location{
|
||||
Line: line,
|
||||
Character: start,
|
||||
},
|
||||
End: common.Location{
|
||||
Line: line,
|
||||
Character: end + 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type HostsParser struct {
|
||||
Tree HostsTree
|
||||
CommentLines map[uint32]struct{}
|
||||
Resolver *Resolver
|
||||
// [line]error
|
||||
DoubleIPs map[uint32]DuplicateIPDeclaration
|
||||
}
|
||||
|
||||
type HostsTree struct {
|
||||
// [line]entry
|
||||
Entries map[uint32]*HostsEntry
|
||||
}
|
||||
|
||||
type HostsEntry struct {
|
||||
Location common.LocationRange
|
||||
|
||||
IPAddress *HostsIPAddress
|
||||
Hostname *HostsHostname
|
||||
Aliases []*HostsHostname
|
||||
}
|
||||
|
||||
func (p HostsEntry) String() string {
|
||||
str := fmt.Sprintf("HostsEntry(%v)", p.Location)
|
||||
|
||||
if p.IPAddress != nil {
|
||||
str += " " + p.IPAddress.Value.String()
|
||||
}
|
||||
|
||||
if p.Hostname != nil {
|
||||
str += " " + p.Hostname.Value
|
||||
}
|
||||
|
||||
if p.Aliases != nil {
|
||||
str += " " + fmt.Sprintf("%v", p.Aliases)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
type HostsIPAddress struct {
|
||||
Location common.LocationRange
|
||||
Value net.IPAddr
|
||||
}
|
||||
|
||||
type HostsHostname struct {
|
||||
Location common.LocationRange
|
||||
Value string
|
||||
}
|
152
handlers/hosts/handlers/analyzer/listener.go
Normal file
152
handlers/hosts/handlers/analyzer/listener.go
Normal file
@ -0,0 +1,152 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
docvalues "config-lsp/doc-values"
|
||||
"config-lsp/handlers/hosts/parser"
|
||||
"net"
|
||||
|
||||
"github.com/antlr4-go/antlr/v4"
|
||||
)
|
||||
|
||||
type hostsListenerContext struct {
|
||||
line uint32
|
||||
}
|
||||
|
||||
type hostsParserListener struct {
|
||||
*parser.BaseHostsListener
|
||||
Parser *HostsParser
|
||||
Errors []common.LSPError
|
||||
hostsContext hostsListenerContext
|
||||
}
|
||||
|
||||
func (s *hostsParserListener) EnterComment(ctx *parser.CommentContext) {
|
||||
line := uint32(s.hostsContext.line)
|
||||
s.Parser.CommentLines[line] = struct{}{}
|
||||
}
|
||||
|
||||
func (s *hostsParserListener) EnterEntry(ctx *parser.EntryContext) {
|
||||
location := characterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.hostsContext.line)
|
||||
|
||||
s.Parser.Tree.Entries[location.Start.Line] = &HostsEntry{
|
||||
Location: location,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *hostsParserListener) EnterIpAddress(ctx *parser.IpAddressContext) {
|
||||
location := characterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.hostsContext.line)
|
||||
|
||||
ip := net.ParseIP(ctx.GetText())
|
||||
|
||||
if ip == nil {
|
||||
s.Errors = append(s.Errors, common.LSPError{
|
||||
Range: location,
|
||||
Err: docvalues.InvalidIPAddress{},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ipAddr, err := net.ResolveIPAddr("ip", ip.String())
|
||||
|
||||
if err != nil {
|
||||
s.Errors = append(s.Errors, common.LSPError{
|
||||
Range: location,
|
||||
Err: docvalues.InvalidIPAddress{},
|
||||
})
|
||||
}
|
||||
|
||||
entry := s.Parser.Tree.Entries[location.Start.Line]
|
||||
|
||||
entry.IPAddress = &HostsIPAddress{
|
||||
Location: location,
|
||||
Value: *ipAddr,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *hostsParserListener) EnterHostname(ctx *parser.HostnameContext) {
|
||||
location := characterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.hostsContext.line)
|
||||
|
||||
entry := s.Parser.Tree.Entries[location.Start.Line]
|
||||
|
||||
entry.Hostname = &HostsHostname{
|
||||
Location: location,
|
||||
Value: ctx.GetText(),
|
||||
}
|
||||
|
||||
s.Parser.Tree.Entries[location.Start.Line] = entry
|
||||
}
|
||||
|
||||
func (s *hostsParserListener) EnterAliases(ctx *parser.AliasesContext) {
|
||||
location := characterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.hostsContext.line)
|
||||
|
||||
entry := s.Parser.Tree.Entries[location.Start.Line]
|
||||
|
||||
aliases := make([]*HostsHostname, 0)
|
||||
|
||||
entry.Aliases = aliases
|
||||
}
|
||||
|
||||
func (s *hostsParserListener) EnterAlias(ctx *parser.AliasContext) {
|
||||
location := characterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.hostsContext.line)
|
||||
|
||||
entry := s.Parser.Tree.Entries[location.Start.Line]
|
||||
|
||||
alias := HostsHostname{
|
||||
Location: location,
|
||||
Value: ctx.GetText(),
|
||||
}
|
||||
|
||||
entry.Aliases = append(entry.Aliases, &alias)
|
||||
}
|
||||
|
||||
func createHostsFileListener(
|
||||
parser *HostsParser,
|
||||
line uint32,
|
||||
) hostsParserListener {
|
||||
return hostsParserListener{
|
||||
Parser: parser,
|
||||
hostsContext: hostsListenerContext{
|
||||
line: line,
|
||||
},
|
||||
Errors: make([]common.LSPError, 0),
|
||||
}
|
||||
}
|
||||
|
||||
type errorListener struct {
|
||||
*antlr.DefaultErrorListener
|
||||
Errors []common.LSPError
|
||||
hostsContext hostsListenerContext
|
||||
}
|
||||
|
||||
func createErrorListener(
|
||||
line uint32,
|
||||
) errorListener {
|
||||
return errorListener{
|
||||
Errors: make([]common.LSPError, 0),
|
||||
hostsContext: hostsListenerContext{
|
||||
line: line,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (d *errorListener) SyntaxError(
|
||||
recognizer antlr.Recognizer,
|
||||
offendingSymbol interface{},
|
||||
_ int,
|
||||
character int,
|
||||
message string,
|
||||
error antlr.RecognitionException,
|
||||
) {
|
||||
line := d.hostsContext.line
|
||||
d.Errors = append(d.Errors, common.LSPError{
|
||||
Range: common.CreateSingleCharRange(uint32(line), uint32(character)),
|
||||
Err: common.SyntaxError{
|
||||
Message: message,
|
||||
},
|
||||
})
|
||||
}
|
108
handlers/hosts/handlers/analyzer/resolver.go
Normal file
108
handlers/hosts/handlers/analyzer/resolver.go
Normal file
@ -0,0 +1,108 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/utils"
|
||||
"net"
|
||||
)
|
||||
|
||||
type ResolverEntry struct {
|
||||
IPv4Address net.IP
|
||||
IPv6Address net.IP
|
||||
Line uint32
|
||||
}
|
||||
|
||||
func (e ResolverEntry) GetInfo() string {
|
||||
if e.IPv4Address != nil {
|
||||
return e.IPv4Address.String()
|
||||
}
|
||||
|
||||
return e.IPv6Address.String()
|
||||
}
|
||||
|
||||
type Resolver struct {
|
||||
Entries map[string]ResolverEntry
|
||||
}
|
||||
|
||||
func createEntry(
|
||||
line uint32,
|
||||
ip net.IP,
|
||||
) ResolverEntry {
|
||||
entry := ResolverEntry{
|
||||
Line: line,
|
||||
}
|
||||
|
||||
if ipv4 := ip.To4(); ipv4 != nil {
|
||||
entry.IPv4Address = ipv4
|
||||
} else if ipv6 := ip.To16(); ipv6 != nil {
|
||||
entry.IPv6Address = ipv6
|
||||
}
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
type hostnameEntry struct {
|
||||
Location common.LocationRange
|
||||
HostName string
|
||||
}
|
||||
|
||||
func createResolverFromParser(p HostsParser) (Resolver, []common.LSPError) {
|
||||
errors := make([]common.LSPError, 0)
|
||||
resolver := Resolver{
|
||||
Entries: make(map[string]ResolverEntry),
|
||||
}
|
||||
|
||||
for lineNumber, entry := range p.Tree.Entries {
|
||||
if entry.IPAddress != nil && entry.Hostname != nil {
|
||||
hostNames := append(
|
||||
[]hostnameEntry{
|
||||
{
|
||||
Location: entry.Hostname.Location,
|
||||
HostName: entry.Hostname.Value,
|
||||
},
|
||||
},
|
||||
utils.Map(
|
||||
entry.Aliases,
|
||||
func(alias *HostsHostname) hostnameEntry {
|
||||
return hostnameEntry{
|
||||
Location: alias.Location,
|
||||
HostName: alias.Value,
|
||||
}
|
||||
},
|
||||
)...,
|
||||
)
|
||||
|
||||
for _, hostName := range hostNames {
|
||||
entry := createEntry(
|
||||
lineNumber,
|
||||
entry.IPAddress.Value.IP,
|
||||
)
|
||||
|
||||
if resolv, found := resolver.Entries[hostName.HostName]; found {
|
||||
errors = append(
|
||||
errors,
|
||||
common.LSPError{
|
||||
Range: hostName.Location,
|
||||
Err: DuplicateHostEntry{
|
||||
AlreadyFoundAt: resolv.Line,
|
||||
Hostname: hostName.HostName,
|
||||
},
|
||||
},
|
||||
)
|
||||
} else {
|
||||
resolver.Entries[hostName.HostName] = entry
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resolver, errors
|
||||
}
|
||||
|
||||
func analyzeDoubleHostNames(p *HostsParser) []common.LSPError {
|
||||
resolver, errors := createResolverFromParser(*p)
|
||||
|
||||
p.Resolver = &resolver
|
||||
|
||||
return errors
|
||||
}
|
116
handlers/hosts/handlers/analyzer/resolver_test.go
Normal file
116
handlers/hosts/handlers/analyzer/resolver_test.go
Normal file
@ -0,0 +1,116 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/utils"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestResolverEntriesWorksWithNonOverlapping(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
1.2.3.4 hello.com
|
||||
5.5.5.5 world.com
|
||||
`)
|
||||
|
||||
parser := CreateNewHostsParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Fatalf("PARER FAILED! Expected no errors, but got %v", errors)
|
||||
}
|
||||
|
||||
resolver, errors := createResolverFromParser(parser)
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Errorf("Expected no errors, but got %v", errors)
|
||||
}
|
||||
|
||||
if len(resolver.Entries) != 2 {
|
||||
t.Errorf("Expected 2 entries, but got %v", len(resolver.Entries))
|
||||
}
|
||||
|
||||
if !(resolver.Entries["hello.com"].IPv4Address.String() == "1.2.3.4") {
|
||||
t.Errorf("Expected hello.com to be 1.2.3.4, but got %v", resolver.Entries["hello.com"].IPv4Address)
|
||||
}
|
||||
|
||||
if !(resolver.Entries["world.com"].IPv4Address.String() == "5.5.5.5") {
|
||||
t.Errorf("Expected world.com to be 5.5.5.5, but got %v", resolver.Entries["world.com"].IPv4Address)
|
||||
}
|
||||
|
||||
if !(resolver.Entries["hello.com"].IPv6Address == nil) {
|
||||
t.Errorf("Expected hello.com to have no IPv6 address, but got %v", resolver.Entries["hello.com"].IPv6Address)
|
||||
}
|
||||
|
||||
if !(resolver.Entries["world.com"].IPv6Address == nil) {
|
||||
t.Errorf("Expected world.com to have no IPv6 address, but got %v", resolver.Entries["world.com"].IPv6Address)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolverEntriesWithSimpleOverlapping(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
1.2.3.4 hello.com
|
||||
5.5.5.5 hello.com
|
||||
`)
|
||||
|
||||
parser := CreateNewHostsParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Fatalf("PARER FAILED! Expected no errors, but got %v", errors)
|
||||
}
|
||||
|
||||
resolver, errors := createResolverFromParser(parser)
|
||||
|
||||
if !(len(errors) == 1) {
|
||||
t.Errorf("Expected 1 error, but got %v", len(errors))
|
||||
}
|
||||
|
||||
if len(resolver.Entries) != 1 {
|
||||
t.Errorf("Expected 1 entry, but got %v", len(resolver.Entries))
|
||||
}
|
||||
|
||||
if !(resolver.Entries["hello.com"].IPv4Address.String() == "1.2.3.4") {
|
||||
t.Errorf("Expected hello.com to be 1.2.3.4, but got %v", resolver.Entries["hello.com"].IPv4Address)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolverEntriesWithComplexOverlapping(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
1.2.3.4 hello.com test.com
|
||||
5.5.5.5 check.com test.com
|
||||
`)
|
||||
|
||||
parser := CreateNewHostsParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Fatalf("PARER FAILED! Expected no errors, but got %v", errors)
|
||||
}
|
||||
|
||||
resolver, errors := createResolverFromParser(parser)
|
||||
|
||||
if !(len(errors) == 1) {
|
||||
t.Errorf("Expected 1 error, but got %v", len(errors))
|
||||
}
|
||||
|
||||
if len(resolver.Entries) != 3 {
|
||||
t.Errorf("Expected 3 entries, but got %v", len(resolver.Entries))
|
||||
}
|
||||
|
||||
if !(resolver.Entries["hello.com"].IPv4Address.String() == "1.2.3.4") {
|
||||
t.Errorf("Expected hello.com to be 1.2.3.4, but got %v", resolver.Entries["hello.com"].IPv4Address)
|
||||
}
|
||||
|
||||
if !(resolver.Entries["check.com"].IPv4Address.String() == "5.5.5.5") {
|
||||
t.Errorf("Expected check.com to be 5.5.5.5, but got %v", resolver.Entries["check.com"].IPv4Address)
|
||||
}
|
||||
|
||||
if !(resolver.Entries["test.com"].IPv4Address.String() == "1.2.3.4") {
|
||||
t.Errorf("Expected test.com to have no IPv4 address, but got %v", resolver.Entries["test.com"].IPv4Address)
|
||||
}
|
||||
}
|
86
handlers/hosts/handlers/analyzer/values.go
Normal file
86
handlers/hosts/handlers/analyzer/values.go
Normal file
@ -0,0 +1,86 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
docvalues "config-lsp/doc-values"
|
||||
"config-lsp/handlers/hosts/fields"
|
||||
"config-lsp/utils"
|
||||
"errors"
|
||||
)
|
||||
|
||||
func analyzeEntriesSetCorrectly(
|
||||
parser HostsParser,
|
||||
) []common.LSPError {
|
||||
err := make([]common.LSPError, 0)
|
||||
|
||||
for lineNumber, entry := range parser.Tree.Entries {
|
||||
if entry.IPAddress == nil {
|
||||
err = append(err, common.LSPError{
|
||||
Range: common.CreateFullLineRange(lineNumber),
|
||||
Err: errors.New("IP Address is required"),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if entry.Hostname == nil {
|
||||
err = append(err, common.LSPError{
|
||||
Range: common.CreateFullLineRange(lineNumber),
|
||||
Err: errors.New("Hostname is required"),
|
||||
})
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func analyzeEntriesAreValid(
|
||||
parser HostsParser,
|
||||
) []common.LSPError {
|
||||
err := make([]common.LSPError, 0)
|
||||
|
||||
for _, entry := range parser.Tree.Entries {
|
||||
err = append(
|
||||
err,
|
||||
utils.Map(
|
||||
fields.IPAddressField.CheckIsValid(entry.IPAddress.Value.String()),
|
||||
func(val *docvalues.InvalidValue) common.LSPError {
|
||||
return common.LSPError{
|
||||
Range: entry.IPAddress.Location,
|
||||
Err: val.Err,
|
||||
}
|
||||
},
|
||||
)...,
|
||||
)
|
||||
|
||||
err = append(
|
||||
err,
|
||||
utils.Map(
|
||||
fields.HostnameField.CheckIsValid(entry.Hostname.Value),
|
||||
func(val *docvalues.InvalidValue) common.LSPError {
|
||||
return common.LSPError{
|
||||
Range: entry.Hostname.Location,
|
||||
Err: val.Err,
|
||||
}
|
||||
},
|
||||
)...,
|
||||
)
|
||||
|
||||
for _, alias := range entry.Aliases {
|
||||
err = append(
|
||||
err,
|
||||
utils.Map(
|
||||
fields.HostnameField.CheckIsValid(alias.Value),
|
||||
func(val *docvalues.InvalidValue) common.LSPError {
|
||||
return common.LSPError{
|
||||
Range: alias.Location,
|
||||
Err: val.Err,
|
||||
}
|
||||
},
|
||||
)...,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
105
handlers/hosts/handlers/code-actions.go
Normal file
105
handlers/hosts/handlers/code-actions.go
Normal file
@ -0,0 +1,105 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/hosts/handlers/analyzer"
|
||||
"config-lsp/utils"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
type CodeActionName string
|
||||
|
||||
const (
|
||||
CodeActionInlineAliases CodeActionName = "inlineAliases"
|
||||
)
|
||||
|
||||
type CodeAction interface {
|
||||
RunCommand(analyzer.HostsParser) (*protocol.ApplyWorkspaceEditParams, error)
|
||||
}
|
||||
|
||||
type CodeActionArgs interface{}
|
||||
|
||||
type CodeActionInlineAliasesArgs struct {
|
||||
URI protocol.DocumentUri
|
||||
FromLine uint32
|
||||
ToLine uint32
|
||||
}
|
||||
|
||||
func CodeActionInlineAliasesArgsFromArguments(arguments map[string]any) CodeActionInlineAliasesArgs {
|
||||
return CodeActionInlineAliasesArgs{
|
||||
URI: arguments["URI"].(protocol.DocumentUri),
|
||||
FromLine: uint32(arguments["FromLine"].(float64)),
|
||||
ToLine: uint32(arguments["ToLine"].(float64)),
|
||||
}
|
||||
}
|
||||
|
||||
func (args CodeActionInlineAliasesArgs) RunCommand(hostsParser analyzer.HostsParser) (*protocol.ApplyWorkspaceEditParams, error) {
|
||||
fromEntry := hostsParser.Tree.Entries[args.FromLine]
|
||||
toEntry := hostsParser.Tree.Entries[args.ToLine]
|
||||
|
||||
if fromEntry == nil || toEntry == nil {
|
||||
// Weird
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var insertCharacter uint32
|
||||
|
||||
if toEntry.Aliases != nil {
|
||||
insertCharacter = toEntry.Aliases[len(toEntry.Aliases)-1].Location.End.Character
|
||||
} else {
|
||||
insertCharacter = toEntry.Hostname.Location.End.Character
|
||||
}
|
||||
|
||||
hostnames := append(
|
||||
[]string{
|
||||
fromEntry.Hostname.Value,
|
||||
},
|
||||
utils.Map(
|
||||
fromEntry.Aliases,
|
||||
func(alias *analyzer.HostsHostname) string {
|
||||
return alias.Value
|
||||
},
|
||||
)...,
|
||||
)
|
||||
|
||||
label := fmt.Sprintf("Inline aliases from %d to %d", args.FromLine, args.ToLine)
|
||||
return &protocol.ApplyWorkspaceEditParams{
|
||||
Label: &label,
|
||||
Edit: protocol.WorkspaceEdit{
|
||||
Changes: map[protocol.DocumentUri][]protocol.TextEdit{
|
||||
args.URI: {
|
||||
// Delete old line
|
||||
{
|
||||
Range: protocol.Range{
|
||||
Start: protocol.Position{
|
||||
Line: args.FromLine,
|
||||
Character: 0,
|
||||
},
|
||||
End: protocol.Position{
|
||||
Line: args.FromLine + 1,
|
||||
Character: 0,
|
||||
},
|
||||
},
|
||||
NewText: "",
|
||||
},
|
||||
// Insert aliases
|
||||
{
|
||||
Range: protocol.Range{
|
||||
Start: protocol.Position{
|
||||
Line: args.ToLine,
|
||||
Character: insertCharacter,
|
||||
},
|
||||
End: protocol.Position{
|
||||
Line: args.ToLine,
|
||||
Character: insertCharacter,
|
||||
},
|
||||
},
|
||||
NewText: " " + strings.Join(hostnames, " "),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
37
handlers/hosts/handlers/fetch-code-actions.go
Normal file
37
handlers/hosts/handlers/fetch-code-actions.go
Normal file
@ -0,0 +1,37 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/hosts/handlers/analyzer"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func GetInlineAliasesCodeAction(
|
||||
p analyzer.HostsParser,
|
||||
params *protocol.CodeActionParams,
|
||||
) []protocol.CodeAction {
|
||||
line := params.Range.Start.Line
|
||||
|
||||
if duplicateInfo, found := p.DoubleIPs[line]; found {
|
||||
commandID := "hosts." + CodeActionInlineAliases
|
||||
command := protocol.Command{
|
||||
Title: "Inline Aliases",
|
||||
Command: string(commandID),
|
||||
Arguments: []any{
|
||||
CodeActionInlineAliasesArgs{
|
||||
URI: params.TextDocument.URI,
|
||||
FromLine: line,
|
||||
ToLine: duplicateInfo.AlreadyFoundAt,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return []protocol.CodeAction{
|
||||
{
|
||||
Title: "Inline Aliases",
|
||||
Command: &command,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return []protocol.CodeAction{}
|
||||
}
|
50
handlers/hosts/handlers/hover.go
Normal file
50
handlers/hosts/handlers/hover.go
Normal file
@ -0,0 +1,50 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/hosts/handlers/analyzer"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type HoverTarget string
|
||||
|
||||
const (
|
||||
HoverTargetIPAddress HoverTarget = "ip_address"
|
||||
HoverTargetHostname HoverTarget = "hostname"
|
||||
HoverTargetAlias HoverTarget = "alias"
|
||||
)
|
||||
|
||||
func GetHoverTargetInEntry(
|
||||
e analyzer.HostsEntry,
|
||||
cursor uint32,
|
||||
) *HoverTarget {
|
||||
if e.IPAddress != nil && e.IPAddress.Location.ContainsCursorByCharacter(cursor) {
|
||||
target := HoverTargetIPAddress
|
||||
return &target
|
||||
}
|
||||
|
||||
if e.Hostname != nil && e.Hostname.Location.ContainsCursorByCharacter(cursor) {
|
||||
target := HoverTargetHostname
|
||||
return &target
|
||||
}
|
||||
|
||||
for _, alias := range e.Aliases {
|
||||
if alias.Location.ContainsCursorByCharacter(cursor) {
|
||||
target := HoverTargetAlias
|
||||
return &target
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetHoverInfoForHostname(
|
||||
parser analyzer.HostsParser,
|
||||
hostname analyzer.HostsHostname,
|
||||
cursor uint32,
|
||||
) []string {
|
||||
ipAddress := parser.Resolver.Entries[hostname.Value]
|
||||
|
||||
return []string{
|
||||
fmt.Sprintf("**%s** maps to _%s_", hostname.Value, ipAddress.GetInfo()),
|
||||
}
|
||||
}
|
9
handlers/hosts/lsp/shared.go
Normal file
9
handlers/hosts/lsp/shared.go
Normal file
@ -0,0 +1,9 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/hosts/handlers/analyzer"
|
||||
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
var documentParserMap = map[protocol.DocumentUri]*analyzer.HostsParser{}
|
22
handlers/hosts/lsp/text-document-code-action.go
Normal file
22
handlers/hosts/lsp/text-document-code-action.go
Normal file
@ -0,0 +1,22 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/hosts/handlers"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func TextDocumentCodeAction(context *glsp.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) {
|
||||
parser := documentParserMap[params.TextDocument.URI]
|
||||
|
||||
actions := make([]protocol.CodeAction, 0, 1)
|
||||
|
||||
actions = append(actions, handlers.GetInlineAliasesCodeAction(*parser, params)...)
|
||||
|
||||
if len(actions) > 0 {
|
||||
return actions, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
12
handlers/hosts/lsp/text-document-completion.go
Normal file
12
handlers/hosts/lsp/text-document-completion.go
Normal file
@ -0,0 +1,12 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) {
|
||||
// p := documentParserMap[params.TextDocument.URI]
|
||||
|
||||
return nil, nil
|
||||
}
|
41
handlers/hosts/lsp/text-document-did-change.go
Normal file
41
handlers/hosts/lsp/text-document-did-change.go
Normal file
@ -0,0 +1,41 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/hosts/handlers/analyzer"
|
||||
"config-lsp/utils"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func TextDocumentDidChange(
|
||||
context *glsp.Context,
|
||||
params *protocol.DidChangeTextDocumentParams,
|
||||
) error {
|
||||
content := params.ContentChanges[0].(protocol.TextDocumentContentChangeEventWhole).Text
|
||||
common.ClearDiagnostics(context, params.TextDocument.URI)
|
||||
|
||||
parser := documentParserMap[params.TextDocument.URI]
|
||||
parser.Clear()
|
||||
|
||||
diagnostics := make([]protocol.Diagnostic, 0)
|
||||
errors := parser.Parse(content)
|
||||
|
||||
if len(errors) > 0 {
|
||||
diagnostics = append(diagnostics, utils.Map(
|
||||
errors,
|
||||
func(err common.LSPError) protocol.Diagnostic {
|
||||
return err.ToDiagnostic()
|
||||
},
|
||||
)...)
|
||||
}
|
||||
|
||||
diagnostics = append(diagnostics, analyzer.Analyze(parser)...)
|
||||
|
||||
if len(diagnostics) > 0 {
|
||||
common.SendDiagnostics(context, params.TextDocument.URI, diagnostics)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
12
handlers/hosts/lsp/text-document-did-close.go
Normal file
12
handlers/hosts/lsp/text-document-did-close.go
Normal file
@ -0,0 +1,12 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func TextDocumentDidClose(context *glsp.Context, params *protocol.DidCloseTextDocumentParams) error {
|
||||
delete(documentParserMap, params.TextDocument.URI)
|
||||
|
||||
return nil
|
||||
}
|
40
handlers/hosts/lsp/text-document-did-open.go
Normal file
40
handlers/hosts/lsp/text-document-did-open.go
Normal file
@ -0,0 +1,40 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/hosts/handlers/analyzer"
|
||||
"config-lsp/utils"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func TextDocumentDidOpen(
|
||||
context *glsp.Context,
|
||||
params *protocol.DidOpenTextDocumentParams,
|
||||
) error {
|
||||
common.ClearDiagnostics(context, params.TextDocument.URI)
|
||||
|
||||
parser := analyzer.CreateNewHostsParser()
|
||||
documentParserMap[params.TextDocument.URI] = &parser
|
||||
|
||||
errors := parser.Parse(params.TextDocument.Text)
|
||||
|
||||
diagnostics := utils.Map(
|
||||
errors,
|
||||
func(err common.LSPError) protocol.Diagnostic {
|
||||
return err.ToDiagnostic()
|
||||
},
|
||||
)
|
||||
|
||||
diagnostics = append(
|
||||
diagnostics,
|
||||
analyzer.Analyze(&parser)...,
|
||||
)
|
||||
|
||||
if len(diagnostics) > 0 {
|
||||
common.SendDiagnostics(context, params.TextDocument.URI, diagnostics)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
89
handlers/hosts/lsp/text-document-hover.go
Normal file
89
handlers/hosts/lsp/text-document-hover.go
Normal file
@ -0,0 +1,89 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/hosts/fields"
|
||||
"config-lsp/handlers/hosts/handlers"
|
||||
"config-lsp/handlers/hosts/handlers/analyzer"
|
||||
"strings"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func TextDocumentHover(
|
||||
context *glsp.Context,
|
||||
params *protocol.HoverParams,
|
||||
) (*protocol.Hover, error) {
|
||||
parser := documentParserMap[params.TextDocument.URI]
|
||||
|
||||
line := params.Position.Line
|
||||
character := params.Position.Character
|
||||
|
||||
if _, found := parser.CommentLines[line]; found {
|
||||
// Comment
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
entry, found := parser.Tree.Entries[line]
|
||||
|
||||
if !found {
|
||||
// Empty line
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
target := handlers.GetHoverTargetInEntry(*entry, character)
|
||||
|
||||
var hostname *analyzer.HostsHostname
|
||||
|
||||
switch *target {
|
||||
case handlers.HoverTargetIPAddress:
|
||||
relativeCursor := character - entry.IPAddress.Location.Start.Character
|
||||
hover := fields.IPAddressField.FetchHoverInfo(entry.IPAddress.Value.String(), relativeCursor)
|
||||
|
||||
return &protocol.Hover{
|
||||
Contents: hover,
|
||||
}, nil
|
||||
case handlers.HoverTargetHostname:
|
||||
hostname = entry.Hostname
|
||||
case handlers.HoverTargetAlias:
|
||||
for _, alias := range entry.Aliases {
|
||||
if alias.Location.Start.Character <= character && character <= alias.Location.End.Character {
|
||||
hostname = alias
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hostname != nil {
|
||||
contents := []string{
|
||||
"## Hostname",
|
||||
}
|
||||
contents = append(
|
||||
contents,
|
||||
fields.HostnameField.GetTypeDescription()...,
|
||||
)
|
||||
contents = append(
|
||||
contents,
|
||||
[]string{
|
||||
"",
|
||||
}...,
|
||||
)
|
||||
contents = append(
|
||||
contents,
|
||||
fields.HostnameField.Documentation,
|
||||
)
|
||||
contents = append(
|
||||
contents,
|
||||
handlers.GetHoverInfoForHostname(*parser, *hostname, character)...,
|
||||
)
|
||||
|
||||
return &protocol.Hover{
|
||||
Contents: &protocol.MarkupContent{
|
||||
Kind: protocol.MarkupKindMarkdown,
|
||||
Value: strings.Join(contents, "\n"),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
24
handlers/hosts/lsp/workspace-execute-command.go
Normal file
24
handlers/hosts/lsp/workspace-execute-command.go
Normal file
@ -0,0 +1,24 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/hosts/handlers"
|
||||
"strings"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func WorkspaceExecuteCommand(context *glsp.Context, params *protocol.ExecuteCommandParams) (*protocol.ApplyWorkspaceEditParams, error) {
|
||||
_, command, _ := strings.Cut(params.Command, ".")
|
||||
|
||||
switch command {
|
||||
case string(handlers.CodeActionInlineAliases):
|
||||
args := handlers.CodeActionInlineAliasesArgsFromArguments(params.Arguments[0].(map[string]any))
|
||||
|
||||
parser := documentParserMap[args.URI]
|
||||
|
||||
return args.RunCommand(*parser)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
48
handlers/hosts/parser/Hosts.interp
Normal file
48
handlers/hosts/parser/Hosts.interp
Normal file
@ -0,0 +1,48 @@
|
||||
token literal names:
|
||||
null
|
||||
null
|
||||
'/'
|
||||
'.'
|
||||
':'
|
||||
'#'
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
|
||||
token symbolic names:
|
||||
null
|
||||
COMMENTLINE
|
||||
SLASH
|
||||
DOT
|
||||
COLON
|
||||
HASHTAG
|
||||
SEPARATOR
|
||||
NEWLINE
|
||||
DIGITS
|
||||
OCTETS
|
||||
DOMAIN
|
||||
|
||||
rule names:
|
||||
lineStatement
|
||||
entry
|
||||
aliases
|
||||
alias
|
||||
hostname
|
||||
domain
|
||||
ipAddress
|
||||
ipv4Address
|
||||
singleIPv4Address
|
||||
ipv6Address
|
||||
singleIPv6Address
|
||||
ipv4Digit
|
||||
ipv6Octet
|
||||
ipRange
|
||||
ipRangeBits
|
||||
comment
|
||||
leadingComment
|
||||
|
||||
|
||||
atn:
|
||||
[4, 1, 10, 110, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 1, 0, 3, 0, 36, 8, 0, 1, 0, 1, 0, 3, 0, 40, 8, 0, 1, 0, 3, 0, 43, 8, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 52, 8, 1, 1, 2, 1, 2, 3, 2, 56, 8, 2, 4, 2, 58, 8, 2, 11, 2, 12, 2, 59, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 3, 6, 70, 8, 6, 1, 7, 1, 7, 3, 7, 74, 8, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 3, 9, 86, 8, 9, 1, 10, 1, 10, 1, 10, 4, 10, 91, 8, 10, 11, 10, 12, 10, 92, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 0, 0, 17, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 0, 0, 102, 0, 35, 1, 0, 0, 0, 2, 46, 1, 0, 0, 0, 4, 57, 1, 0, 0, 0, 6, 61, 1, 0, 0, 0, 8, 63, 1, 0, 0, 0, 10, 65, 1, 0, 0, 0, 12, 69, 1, 0, 0, 0, 14, 71, 1, 0, 0, 0, 16, 75, 1, 0, 0, 0, 18, 83, 1, 0, 0, 0, 20, 90, 1, 0, 0, 0, 22, 96, 1, 0, 0, 0, 24, 98, 1, 0, 0, 0, 26, 100, 1, 0, 0, 0, 28, 103, 1, 0, 0, 0, 30, 105, 1, 0, 0, 0, 32, 107, 1, 0, 0, 0, 34, 36, 5, 6, 0, 0, 35, 34, 1, 0, 0, 0, 35, 36, 1, 0, 0, 0, 36, 37, 1, 0, 0, 0, 37, 39, 3, 2, 1, 0, 38, 40, 5, 6, 0, 0, 39, 38, 1, 0, 0, 0, 39, 40, 1, 0, 0, 0, 40, 42, 1, 0, 0, 0, 41, 43, 3, 32, 16, 0, 42, 41, 1, 0, 0, 0, 42, 43, 1, 0, 0, 0, 43, 44, 1, 0, 0, 0, 44, 45, 5, 0, 0, 1, 45, 1, 1, 0, 0, 0, 46, 47, 3, 12, 6, 0, 47, 48, 5, 6, 0, 0, 48, 51, 3, 8, 4, 0, 49, 50, 5, 6, 0, 0, 50, 52, 3, 4, 2, 0, 51, 49, 1, 0, 0, 0, 51, 52, 1, 0, 0, 0, 52, 3, 1, 0, 0, 0, 53, 55, 3, 6, 3, 0, 54, 56, 5, 6, 0, 0, 55, 54, 1, 0, 0, 0, 55, 56, 1, 0, 0, 0, 56, 58, 1, 0, 0, 0, 57, 53, 1, 0, 0, 0, 58, 59, 1, 0, 0, 0, 59, 57, 1, 0, 0, 0, 59, 60, 1, 0, 0, 0, 60, 5, 1, 0, 0, 0, 61, 62, 5, 10, 0, 0, 62, 7, 1, 0, 0, 0, 63, 64, 3, 10, 5, 0, 64, 9, 1, 0, 0, 0, 65, 66, 5, 10, 0, 0, 66, 11, 1, 0, 0, 0, 67, 70, 3, 14, 7, 0, 68, 70, 3, 18, 9, 0, 69, 67, 1, 0, 0, 0, 69, 68, 1, 0, 0, 0, 70, 13, 1, 0, 0, 0, 71, 73, 3, 16, 8, 0, 72, 74, 3, 26, 13, 0, 73, 72, 1, 0, 0, 0, 73, 74, 1, 0, 0, 0, 74, 15, 1, 0, 0, 0, 75, 76, 3, 22, 11, 0, 76, 77, 5, 3, 0, 0, 77, 78, 3, 22, 11, 0, 78, 79, 5, 3, 0, 0, 79, 80, 3, 22, 11, 0, 80, 81, 5, 3, 0, 0, 81, 82, 3, 22, 11, 0, 82, 17, 1, 0, 0, 0, 83, 85, 3, 20, 10, 0, 84, 86, 3, 26, 13, 0, 85, 84, 1, 0, 0, 0, 85, 86, 1, 0, 0, 0, 86, 19, 1, 0, 0, 0, 87, 88, 3, 24, 12, 0, 88, 89, 5, 4, 0, 0, 89, 91, 1, 0, 0, 0, 90, 87, 1, 0, 0, 0, 91, 92, 1, 0, 0, 0, 92, 90, 1, 0, 0, 0, 92, 93, 1, 0, 0, 0, 93, 94, 1, 0, 0, 0, 94, 95, 3, 24, 12, 0, 95, 21, 1, 0, 0, 0, 96, 97, 5, 8, 0, 0, 97, 23, 1, 0, 0, 0, 98, 99, 5, 9, 0, 0, 99, 25, 1, 0, 0, 0, 100, 101, 5, 2, 0, 0, 101, 102, 3, 28, 14, 0, 102, 27, 1, 0, 0, 0, 103, 104, 5, 8, 0, 0, 104, 29, 1, 0, 0, 0, 105, 106, 5, 1, 0, 0, 106, 31, 1, 0, 0, 0, 107, 108, 5, 1, 0, 0, 108, 33, 1, 0, 0, 0, 10, 35, 39, 42, 51, 55, 59, 69, 73, 85, 92]
|
14
handlers/hosts/parser/Hosts.tokens
Normal file
14
handlers/hosts/parser/Hosts.tokens
Normal file
@ -0,0 +1,14 @@
|
||||
COMMENTLINE=1
|
||||
SLASH=2
|
||||
DOT=3
|
||||
COLON=4
|
||||
HASHTAG=5
|
||||
SEPARATOR=6
|
||||
NEWLINE=7
|
||||
DIGITS=8
|
||||
OCTETS=9
|
||||
DOMAIN=10
|
||||
'/'=2
|
||||
'.'=3
|
||||
':'=4
|
||||
'#'=5
|
50
handlers/hosts/parser/HostsLexer.interp
Normal file
50
handlers/hosts/parser/HostsLexer.interp
Normal file
@ -0,0 +1,50 @@
|
||||
token literal names:
|
||||
null
|
||||
null
|
||||
'/'
|
||||
'.'
|
||||
':'
|
||||
'#'
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
|
||||
token symbolic names:
|
||||
null
|
||||
COMMENTLINE
|
||||
SLASH
|
||||
DOT
|
||||
COLON
|
||||
HASHTAG
|
||||
SEPARATOR
|
||||
NEWLINE
|
||||
DIGITS
|
||||
OCTETS
|
||||
DOMAIN
|
||||
|
||||
rule names:
|
||||
COMMENTLINE
|
||||
SLASH
|
||||
DOT
|
||||
COLON
|
||||
HASHTAG
|
||||
SEPARATOR
|
||||
NEWLINE
|
||||
DIGITS
|
||||
DIGIT
|
||||
OCTETS
|
||||
OCTET
|
||||
DOMAIN
|
||||
STRING
|
||||
|
||||
channel names:
|
||||
DEFAULT_TOKEN_CHANNEL
|
||||
HIDDEN
|
||||
|
||||
mode names:
|
||||
DEFAULT_MODE
|
||||
|
||||
atn:
|
||||
[4, 0, 10, 83, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 1, 0, 1, 0, 4, 0, 30, 8, 0, 11, 0, 12, 0, 31, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 4, 5, 43, 8, 5, 11, 5, 12, 5, 44, 1, 6, 3, 6, 48, 8, 6, 1, 6, 1, 6, 1, 7, 4, 7, 53, 8, 7, 11, 7, 12, 7, 54, 1, 8, 1, 8, 1, 9, 4, 9, 60, 8, 9, 11, 9, 12, 9, 61, 1, 10, 1, 10, 1, 11, 4, 11, 67, 8, 11, 11, 11, 12, 11, 68, 1, 11, 1, 11, 4, 11, 73, 8, 11, 11, 11, 12, 11, 74, 5, 11, 77, 8, 11, 10, 11, 12, 11, 80, 9, 11, 1, 12, 1, 12, 0, 0, 13, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 0, 19, 9, 21, 0, 23, 10, 25, 0, 1, 0, 8, 2, 0, 10, 10, 13, 13, 2, 0, 9, 9, 32, 32, 1, 0, 13, 13, 1, 0, 10, 10, 1, 0, 48, 57, 3, 0, 48, 57, 65, 70, 97, 102, 2, 0, 65, 90, 97, 122, 5, 0, 9, 10, 13, 13, 32, 32, 35, 35, 46, 46, 87, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 1, 27, 1, 0, 0, 0, 3, 33, 1, 0, 0, 0, 5, 35, 1, 0, 0, 0, 7, 37, 1, 0, 0, 0, 9, 39, 1, 0, 0, 0, 11, 42, 1, 0, 0, 0, 13, 47, 1, 0, 0, 0, 15, 52, 1, 0, 0, 0, 17, 56, 1, 0, 0, 0, 19, 59, 1, 0, 0, 0, 21, 63, 1, 0, 0, 0, 23, 66, 1, 0, 0, 0, 25, 81, 1, 0, 0, 0, 27, 29, 3, 9, 4, 0, 28, 30, 8, 0, 0, 0, 29, 28, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 29, 1, 0, 0, 0, 31, 32, 1, 0, 0, 0, 32, 2, 1, 0, 0, 0, 33, 34, 5, 47, 0, 0, 34, 4, 1, 0, 0, 0, 35, 36, 5, 46, 0, 0, 36, 6, 1, 0, 0, 0, 37, 38, 5, 58, 0, 0, 38, 8, 1, 0, 0, 0, 39, 40, 5, 35, 0, 0, 40, 10, 1, 0, 0, 0, 41, 43, 7, 1, 0, 0, 42, 41, 1, 0, 0, 0, 43, 44, 1, 0, 0, 0, 44, 42, 1, 0, 0, 0, 44, 45, 1, 0, 0, 0, 45, 12, 1, 0, 0, 0, 46, 48, 7, 2, 0, 0, 47, 46, 1, 0, 0, 0, 47, 48, 1, 0, 0, 0, 48, 49, 1, 0, 0, 0, 49, 50, 7, 3, 0, 0, 50, 14, 1, 0, 0, 0, 51, 53, 3, 17, 8, 0, 52, 51, 1, 0, 0, 0, 53, 54, 1, 0, 0, 0, 54, 52, 1, 0, 0, 0, 54, 55, 1, 0, 0, 0, 55, 16, 1, 0, 0, 0, 56, 57, 7, 4, 0, 0, 57, 18, 1, 0, 0, 0, 58, 60, 3, 21, 10, 0, 59, 58, 1, 0, 0, 0, 60, 61, 1, 0, 0, 0, 61, 59, 1, 0, 0, 0, 61, 62, 1, 0, 0, 0, 62, 20, 1, 0, 0, 0, 63, 64, 7, 5, 0, 0, 64, 22, 1, 0, 0, 0, 65, 67, 3, 25, 12, 0, 66, 65, 1, 0, 0, 0, 67, 68, 1, 0, 0, 0, 68, 66, 1, 0, 0, 0, 68, 69, 1, 0, 0, 0, 69, 78, 1, 0, 0, 0, 70, 72, 3, 5, 2, 0, 71, 73, 7, 6, 0, 0, 72, 71, 1, 0, 0, 0, 73, 74, 1, 0, 0, 0, 74, 72, 1, 0, 0, 0, 74, 75, 1, 0, 0, 0, 75, 77, 1, 0, 0, 0, 76, 70, 1, 0, 0, 0, 77, 80, 1, 0, 0, 0, 78, 76, 1, 0, 0, 0, 78, 79, 1, 0, 0, 0, 79, 24, 1, 0, 0, 0, 80, 78, 1, 0, 0, 0, 81, 82, 8, 7, 0, 0, 82, 26, 1, 0, 0, 0, 9, 0, 31, 44, 47, 54, 61, 68, 74, 78, 0]
|
14
handlers/hosts/parser/HostsLexer.tokens
Normal file
14
handlers/hosts/parser/HostsLexer.tokens
Normal file
@ -0,0 +1,14 @@
|
||||
COMMENTLINE=1
|
||||
SLASH=2
|
||||
DOT=3
|
||||
COLON=4
|
||||
HASHTAG=5
|
||||
SEPARATOR=6
|
||||
NEWLINE=7
|
||||
DIGITS=8
|
||||
OCTETS=9
|
||||
DOMAIN=10
|
||||
'/'=2
|
||||
'.'=3
|
||||
':'=4
|
||||
'#'=5
|
124
handlers/hosts/parser/hosts_base_listener.go
Normal file
124
handlers/hosts/parser/hosts_base_listener.go
Normal file
@ -0,0 +1,124 @@
|
||||
// Code generated from Hosts.g4 by ANTLR 4.13.0. DO NOT EDIT.
|
||||
|
||||
package parser // Hosts
|
||||
|
||||
import "github.com/antlr4-go/antlr/v4"
|
||||
|
||||
// BaseHostsListener is a complete listener for a parse tree produced by HostsParser.
|
||||
type BaseHostsListener struct{}
|
||||
|
||||
var _ HostsListener = &BaseHostsListener{}
|
||||
|
||||
// VisitTerminal is called when a terminal node is visited.
|
||||
func (s *BaseHostsListener) VisitTerminal(node antlr.TerminalNode) {}
|
||||
|
||||
// VisitErrorNode is called when an error node is visited.
|
||||
func (s *BaseHostsListener) VisitErrorNode(node antlr.ErrorNode) {}
|
||||
|
||||
// EnterEveryRule is called when any rule is entered.
|
||||
func (s *BaseHostsListener) EnterEveryRule(ctx antlr.ParserRuleContext) {}
|
||||
|
||||
// ExitEveryRule is called when any rule is exited.
|
||||
func (s *BaseHostsListener) ExitEveryRule(ctx antlr.ParserRuleContext) {}
|
||||
|
||||
// EnterLineStatement is called when production lineStatement is entered.
|
||||
func (s *BaseHostsListener) EnterLineStatement(ctx *LineStatementContext) {}
|
||||
|
||||
// ExitLineStatement is called when production lineStatement is exited.
|
||||
func (s *BaseHostsListener) ExitLineStatement(ctx *LineStatementContext) {}
|
||||
|
||||
// EnterEntry is called when production entry is entered.
|
||||
func (s *BaseHostsListener) EnterEntry(ctx *EntryContext) {}
|
||||
|
||||
// ExitEntry is called when production entry is exited.
|
||||
func (s *BaseHostsListener) ExitEntry(ctx *EntryContext) {}
|
||||
|
||||
// EnterAliases is called when production aliases is entered.
|
||||
func (s *BaseHostsListener) EnterAliases(ctx *AliasesContext) {}
|
||||
|
||||
// ExitAliases is called when production aliases is exited.
|
||||
func (s *BaseHostsListener) ExitAliases(ctx *AliasesContext) {}
|
||||
|
||||
// EnterAlias is called when production alias is entered.
|
||||
func (s *BaseHostsListener) EnterAlias(ctx *AliasContext) {}
|
||||
|
||||
// ExitAlias is called when production alias is exited.
|
||||
func (s *BaseHostsListener) ExitAlias(ctx *AliasContext) {}
|
||||
|
||||
// EnterHostname is called when production hostname is entered.
|
||||
func (s *BaseHostsListener) EnterHostname(ctx *HostnameContext) {}
|
||||
|
||||
// ExitHostname is called when production hostname is exited.
|
||||
func (s *BaseHostsListener) ExitHostname(ctx *HostnameContext) {}
|
||||
|
||||
// EnterDomain is called when production domain is entered.
|
||||
func (s *BaseHostsListener) EnterDomain(ctx *DomainContext) {}
|
||||
|
||||
// ExitDomain is called when production domain is exited.
|
||||
func (s *BaseHostsListener) ExitDomain(ctx *DomainContext) {}
|
||||
|
||||
// EnterIpAddress is called when production ipAddress is entered.
|
||||
func (s *BaseHostsListener) EnterIpAddress(ctx *IpAddressContext) {}
|
||||
|
||||
// ExitIpAddress is called when production ipAddress is exited.
|
||||
func (s *BaseHostsListener) ExitIpAddress(ctx *IpAddressContext) {}
|
||||
|
||||
// EnterIpv4Address is called when production ipv4Address is entered.
|
||||
func (s *BaseHostsListener) EnterIpv4Address(ctx *Ipv4AddressContext) {}
|
||||
|
||||
// ExitIpv4Address is called when production ipv4Address is exited.
|
||||
func (s *BaseHostsListener) ExitIpv4Address(ctx *Ipv4AddressContext) {}
|
||||
|
||||
// EnterSingleIPv4Address is called when production singleIPv4Address is entered.
|
||||
func (s *BaseHostsListener) EnterSingleIPv4Address(ctx *SingleIPv4AddressContext) {}
|
||||
|
||||
// ExitSingleIPv4Address is called when production singleIPv4Address is exited.
|
||||
func (s *BaseHostsListener) ExitSingleIPv4Address(ctx *SingleIPv4AddressContext) {}
|
||||
|
||||
// EnterIpv6Address is called when production ipv6Address is entered.
|
||||
func (s *BaseHostsListener) EnterIpv6Address(ctx *Ipv6AddressContext) {}
|
||||
|
||||
// ExitIpv6Address is called when production ipv6Address is exited.
|
||||
func (s *BaseHostsListener) ExitIpv6Address(ctx *Ipv6AddressContext) {}
|
||||
|
||||
// EnterSingleIPv6Address is called when production singleIPv6Address is entered.
|
||||
func (s *BaseHostsListener) EnterSingleIPv6Address(ctx *SingleIPv6AddressContext) {}
|
||||
|
||||
// ExitSingleIPv6Address is called when production singleIPv6Address is exited.
|
||||
func (s *BaseHostsListener) ExitSingleIPv6Address(ctx *SingleIPv6AddressContext) {}
|
||||
|
||||
// EnterIpv4Digit is called when production ipv4Digit is entered.
|
||||
func (s *BaseHostsListener) EnterIpv4Digit(ctx *Ipv4DigitContext) {}
|
||||
|
||||
// ExitIpv4Digit is called when production ipv4Digit is exited.
|
||||
func (s *BaseHostsListener) ExitIpv4Digit(ctx *Ipv4DigitContext) {}
|
||||
|
||||
// EnterIpv6Octet is called when production ipv6Octet is entered.
|
||||
func (s *BaseHostsListener) EnterIpv6Octet(ctx *Ipv6OctetContext) {}
|
||||
|
||||
// ExitIpv6Octet is called when production ipv6Octet is exited.
|
||||
func (s *BaseHostsListener) ExitIpv6Octet(ctx *Ipv6OctetContext) {}
|
||||
|
||||
// EnterIpRange is called when production ipRange is entered.
|
||||
func (s *BaseHostsListener) EnterIpRange(ctx *IpRangeContext) {}
|
||||
|
||||
// ExitIpRange is called when production ipRange is exited.
|
||||
func (s *BaseHostsListener) ExitIpRange(ctx *IpRangeContext) {}
|
||||
|
||||
// EnterIpRangeBits is called when production ipRangeBits is entered.
|
||||
func (s *BaseHostsListener) EnterIpRangeBits(ctx *IpRangeBitsContext) {}
|
||||
|
||||
// ExitIpRangeBits is called when production ipRangeBits is exited.
|
||||
func (s *BaseHostsListener) ExitIpRangeBits(ctx *IpRangeBitsContext) {}
|
||||
|
||||
// EnterComment is called when production comment is entered.
|
||||
func (s *BaseHostsListener) EnterComment(ctx *CommentContext) {}
|
||||
|
||||
// ExitComment is called when production comment is exited.
|
||||
func (s *BaseHostsListener) ExitComment(ctx *CommentContext) {}
|
||||
|
||||
// EnterLeadingComment is called when production leadingComment is entered.
|
||||
func (s *BaseHostsListener) EnterLeadingComment(ctx *LeadingCommentContext) {}
|
||||
|
||||
// ExitLeadingComment is called when production leadingComment is exited.
|
||||
func (s *BaseHostsListener) ExitLeadingComment(ctx *LeadingCommentContext) {}
|
145
handlers/hosts/parser/hosts_lexer.go
Normal file
145
handlers/hosts/parser/hosts_lexer.go
Normal file
@ -0,0 +1,145 @@
|
||||
// Code generated from Hosts.g4 by ANTLR 4.13.0. DO NOT EDIT.
|
||||
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/antlr4-go/antlr/v4"
|
||||
"sync"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Suppress unused import error
|
||||
var _ = fmt.Printf
|
||||
var _ = sync.Once{}
|
||||
var _ = unicode.IsLetter
|
||||
|
||||
type HostsLexer struct {
|
||||
*antlr.BaseLexer
|
||||
channelNames []string
|
||||
modeNames []string
|
||||
// TODO: EOF string
|
||||
}
|
||||
|
||||
var HostsLexerLexerStaticData struct {
|
||||
once sync.Once
|
||||
serializedATN []int32
|
||||
ChannelNames []string
|
||||
ModeNames []string
|
||||
LiteralNames []string
|
||||
SymbolicNames []string
|
||||
RuleNames []string
|
||||
PredictionContextCache *antlr.PredictionContextCache
|
||||
atn *antlr.ATN
|
||||
decisionToDFA []*antlr.DFA
|
||||
}
|
||||
|
||||
func hostslexerLexerInit() {
|
||||
staticData := &HostsLexerLexerStaticData
|
||||
staticData.ChannelNames = []string{
|
||||
"DEFAULT_TOKEN_CHANNEL", "HIDDEN",
|
||||
}
|
||||
staticData.ModeNames = []string{
|
||||
"DEFAULT_MODE",
|
||||
}
|
||||
staticData.LiteralNames = []string{
|
||||
"", "", "'/'", "'.'", "':'", "'#'",
|
||||
}
|
||||
staticData.SymbolicNames = []string{
|
||||
"", "COMMENTLINE", "SLASH", "DOT", "COLON", "HASHTAG", "SEPARATOR",
|
||||
"NEWLINE", "DIGITS", "OCTETS", "DOMAIN",
|
||||
}
|
||||
staticData.RuleNames = []string{
|
||||
"COMMENTLINE", "SLASH", "DOT", "COLON", "HASHTAG", "SEPARATOR", "NEWLINE",
|
||||
"DIGITS", "DIGIT", "OCTETS", "OCTET", "DOMAIN", "STRING",
|
||||
}
|
||||
staticData.PredictionContextCache = antlr.NewPredictionContextCache()
|
||||
staticData.serializedATN = []int32{
|
||||
4, 0, 10, 83, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2,
|
||||
4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2,
|
||||
10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 1, 0, 1, 0, 4, 0, 30, 8, 0, 11,
|
||||
0, 12, 0, 31, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 4,
|
||||
5, 43, 8, 5, 11, 5, 12, 5, 44, 1, 6, 3, 6, 48, 8, 6, 1, 6, 1, 6, 1, 7,
|
||||
4, 7, 53, 8, 7, 11, 7, 12, 7, 54, 1, 8, 1, 8, 1, 9, 4, 9, 60, 8, 9, 11,
|
||||
9, 12, 9, 61, 1, 10, 1, 10, 1, 11, 4, 11, 67, 8, 11, 11, 11, 12, 11, 68,
|
||||
1, 11, 1, 11, 4, 11, 73, 8, 11, 11, 11, 12, 11, 74, 5, 11, 77, 8, 11, 10,
|
||||
11, 12, 11, 80, 9, 11, 1, 12, 1, 12, 0, 0, 13, 1, 1, 3, 2, 5, 3, 7, 4,
|
||||
9, 5, 11, 6, 13, 7, 15, 8, 17, 0, 19, 9, 21, 0, 23, 10, 25, 0, 1, 0, 8,
|
||||
2, 0, 10, 10, 13, 13, 2, 0, 9, 9, 32, 32, 1, 0, 13, 13, 1, 0, 10, 10, 1,
|
||||
0, 48, 57, 3, 0, 48, 57, 65, 70, 97, 102, 2, 0, 65, 90, 97, 122, 5, 0,
|
||||
9, 10, 13, 13, 32, 32, 35, 35, 46, 46, 87, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0,
|
||||
0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0,
|
||||
0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 23, 1,
|
||||
0, 0, 0, 1, 27, 1, 0, 0, 0, 3, 33, 1, 0, 0, 0, 5, 35, 1, 0, 0, 0, 7, 37,
|
||||
1, 0, 0, 0, 9, 39, 1, 0, 0, 0, 11, 42, 1, 0, 0, 0, 13, 47, 1, 0, 0, 0,
|
||||
15, 52, 1, 0, 0, 0, 17, 56, 1, 0, 0, 0, 19, 59, 1, 0, 0, 0, 21, 63, 1,
|
||||
0, 0, 0, 23, 66, 1, 0, 0, 0, 25, 81, 1, 0, 0, 0, 27, 29, 3, 9, 4, 0, 28,
|
||||
30, 8, 0, 0, 0, 29, 28, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 29, 1, 0, 0,
|
||||
0, 31, 32, 1, 0, 0, 0, 32, 2, 1, 0, 0, 0, 33, 34, 5, 47, 0, 0, 34, 4, 1,
|
||||
0, 0, 0, 35, 36, 5, 46, 0, 0, 36, 6, 1, 0, 0, 0, 37, 38, 5, 58, 0, 0, 38,
|
||||
8, 1, 0, 0, 0, 39, 40, 5, 35, 0, 0, 40, 10, 1, 0, 0, 0, 41, 43, 7, 1, 0,
|
||||
0, 42, 41, 1, 0, 0, 0, 43, 44, 1, 0, 0, 0, 44, 42, 1, 0, 0, 0, 44, 45,
|
||||
1, 0, 0, 0, 45, 12, 1, 0, 0, 0, 46, 48, 7, 2, 0, 0, 47, 46, 1, 0, 0, 0,
|
||||
47, 48, 1, 0, 0, 0, 48, 49, 1, 0, 0, 0, 49, 50, 7, 3, 0, 0, 50, 14, 1,
|
||||
0, 0, 0, 51, 53, 3, 17, 8, 0, 52, 51, 1, 0, 0, 0, 53, 54, 1, 0, 0, 0, 54,
|
||||
52, 1, 0, 0, 0, 54, 55, 1, 0, 0, 0, 55, 16, 1, 0, 0, 0, 56, 57, 7, 4, 0,
|
||||
0, 57, 18, 1, 0, 0, 0, 58, 60, 3, 21, 10, 0, 59, 58, 1, 0, 0, 0, 60, 61,
|
||||
1, 0, 0, 0, 61, 59, 1, 0, 0, 0, 61, 62, 1, 0, 0, 0, 62, 20, 1, 0, 0, 0,
|
||||
63, 64, 7, 5, 0, 0, 64, 22, 1, 0, 0, 0, 65, 67, 3, 25, 12, 0, 66, 65, 1,
|
||||
0, 0, 0, 67, 68, 1, 0, 0, 0, 68, 66, 1, 0, 0, 0, 68, 69, 1, 0, 0, 0, 69,
|
||||
78, 1, 0, 0, 0, 70, 72, 3, 5, 2, 0, 71, 73, 7, 6, 0, 0, 72, 71, 1, 0, 0,
|
||||
0, 73, 74, 1, 0, 0, 0, 74, 72, 1, 0, 0, 0, 74, 75, 1, 0, 0, 0, 75, 77,
|
||||
1, 0, 0, 0, 76, 70, 1, 0, 0, 0, 77, 80, 1, 0, 0, 0, 78, 76, 1, 0, 0, 0,
|
||||
78, 79, 1, 0, 0, 0, 79, 24, 1, 0, 0, 0, 80, 78, 1, 0, 0, 0, 81, 82, 8,
|
||||
7, 0, 0, 82, 26, 1, 0, 0, 0, 9, 0, 31, 44, 47, 54, 61, 68, 74, 78, 0,
|
||||
}
|
||||
deserializer := antlr.NewATNDeserializer(nil)
|
||||
staticData.atn = deserializer.Deserialize(staticData.serializedATN)
|
||||
atn := staticData.atn
|
||||
staticData.decisionToDFA = make([]*antlr.DFA, len(atn.DecisionToState))
|
||||
decisionToDFA := staticData.decisionToDFA
|
||||
for index, state := range atn.DecisionToState {
|
||||
decisionToDFA[index] = antlr.NewDFA(state, index)
|
||||
}
|
||||
}
|
||||
|
||||
// HostsLexerInit initializes any static state used to implement HostsLexer. By default the
|
||||
// static state used to implement the lexer is lazily initialized during the first call to
|
||||
// NewHostsLexer(). You can call this function if you wish to initialize the static state ahead
|
||||
// of time.
|
||||
func HostsLexerInit() {
|
||||
staticData := &HostsLexerLexerStaticData
|
||||
staticData.once.Do(hostslexerLexerInit)
|
||||
}
|
||||
|
||||
// NewHostsLexer produces a new lexer instance for the optional input antlr.CharStream.
|
||||
func NewHostsLexer(input antlr.CharStream) *HostsLexer {
|
||||
HostsLexerInit()
|
||||
l := new(HostsLexer)
|
||||
l.BaseLexer = antlr.NewBaseLexer(input)
|
||||
staticData := &HostsLexerLexerStaticData
|
||||
l.Interpreter = antlr.NewLexerATNSimulator(l, staticData.atn, staticData.decisionToDFA, staticData.PredictionContextCache)
|
||||
l.channelNames = staticData.ChannelNames
|
||||
l.modeNames = staticData.ModeNames
|
||||
l.RuleNames = staticData.RuleNames
|
||||
l.LiteralNames = staticData.LiteralNames
|
||||
l.SymbolicNames = staticData.SymbolicNames
|
||||
l.GrammarFileName = "Hosts.g4"
|
||||
// TODO: l.EOF = antlr.TokenEOF
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// HostsLexer tokens.
|
||||
const (
|
||||
HostsLexerCOMMENTLINE = 1
|
||||
HostsLexerSLASH = 2
|
||||
HostsLexerDOT = 3
|
||||
HostsLexerCOLON = 4
|
||||
HostsLexerHASHTAG = 5
|
||||
HostsLexerSEPARATOR = 6
|
||||
HostsLexerNEWLINE = 7
|
||||
HostsLexerDIGITS = 8
|
||||
HostsLexerOCTETS = 9
|
||||
HostsLexerDOMAIN = 10
|
||||
)
|
112
handlers/hosts/parser/hosts_listener.go
Normal file
112
handlers/hosts/parser/hosts_listener.go
Normal file
@ -0,0 +1,112 @@
|
||||
// Code generated from Hosts.g4 by ANTLR 4.13.0. DO NOT EDIT.
|
||||
|
||||
package parser // Hosts
|
||||
|
||||
import "github.com/antlr4-go/antlr/v4"
|
||||
|
||||
// HostsListener is a complete listener for a parse tree produced by HostsParser.
|
||||
type HostsListener interface {
|
||||
antlr.ParseTreeListener
|
||||
|
||||
// EnterLineStatement is called when entering the lineStatement production.
|
||||
EnterLineStatement(c *LineStatementContext)
|
||||
|
||||
// EnterEntry is called when entering the entry production.
|
||||
EnterEntry(c *EntryContext)
|
||||
|
||||
// EnterAliases is called when entering the aliases production.
|
||||
EnterAliases(c *AliasesContext)
|
||||
|
||||
// EnterAlias is called when entering the alias production.
|
||||
EnterAlias(c *AliasContext)
|
||||
|
||||
// EnterHostname is called when entering the hostname production.
|
||||
EnterHostname(c *HostnameContext)
|
||||
|
||||
// EnterDomain is called when entering the domain production.
|
||||
EnterDomain(c *DomainContext)
|
||||
|
||||
// EnterIpAddress is called when entering the ipAddress production.
|
||||
EnterIpAddress(c *IpAddressContext)
|
||||
|
||||
// EnterIpv4Address is called when entering the ipv4Address production.
|
||||
EnterIpv4Address(c *Ipv4AddressContext)
|
||||
|
||||
// EnterSingleIPv4Address is called when entering the singleIPv4Address production.
|
||||
EnterSingleIPv4Address(c *SingleIPv4AddressContext)
|
||||
|
||||
// EnterIpv6Address is called when entering the ipv6Address production.
|
||||
EnterIpv6Address(c *Ipv6AddressContext)
|
||||
|
||||
// EnterSingleIPv6Address is called when entering the singleIPv6Address production.
|
||||
EnterSingleIPv6Address(c *SingleIPv6AddressContext)
|
||||
|
||||
// EnterIpv4Digit is called when entering the ipv4Digit production.
|
||||
EnterIpv4Digit(c *Ipv4DigitContext)
|
||||
|
||||
// EnterIpv6Octet is called when entering the ipv6Octet production.
|
||||
EnterIpv6Octet(c *Ipv6OctetContext)
|
||||
|
||||
// EnterIpRange is called when entering the ipRange production.
|
||||
EnterIpRange(c *IpRangeContext)
|
||||
|
||||
// EnterIpRangeBits is called when entering the ipRangeBits production.
|
||||
EnterIpRangeBits(c *IpRangeBitsContext)
|
||||
|
||||
// EnterComment is called when entering the comment production.
|
||||
EnterComment(c *CommentContext)
|
||||
|
||||
// EnterLeadingComment is called when entering the leadingComment production.
|
||||
EnterLeadingComment(c *LeadingCommentContext)
|
||||
|
||||
// ExitLineStatement is called when exiting the lineStatement production.
|
||||
ExitLineStatement(c *LineStatementContext)
|
||||
|
||||
// ExitEntry is called when exiting the entry production.
|
||||
ExitEntry(c *EntryContext)
|
||||
|
||||
// ExitAliases is called when exiting the aliases production.
|
||||
ExitAliases(c *AliasesContext)
|
||||
|
||||
// ExitAlias is called when exiting the alias production.
|
||||
ExitAlias(c *AliasContext)
|
||||
|
||||
// ExitHostname is called when exiting the hostname production.
|
||||
ExitHostname(c *HostnameContext)
|
||||
|
||||
// ExitDomain is called when exiting the domain production.
|
||||
ExitDomain(c *DomainContext)
|
||||
|
||||
// ExitIpAddress is called when exiting the ipAddress production.
|
||||
ExitIpAddress(c *IpAddressContext)
|
||||
|
||||
// ExitIpv4Address is called when exiting the ipv4Address production.
|
||||
ExitIpv4Address(c *Ipv4AddressContext)
|
||||
|
||||
// ExitSingleIPv4Address is called when exiting the singleIPv4Address production.
|
||||
ExitSingleIPv4Address(c *SingleIPv4AddressContext)
|
||||
|
||||
// ExitIpv6Address is called when exiting the ipv6Address production.
|
||||
ExitIpv6Address(c *Ipv6AddressContext)
|
||||
|
||||
// ExitSingleIPv6Address is called when exiting the singleIPv6Address production.
|
||||
ExitSingleIPv6Address(c *SingleIPv6AddressContext)
|
||||
|
||||
// ExitIpv4Digit is called when exiting the ipv4Digit production.
|
||||
ExitIpv4Digit(c *Ipv4DigitContext)
|
||||
|
||||
// ExitIpv6Octet is called when exiting the ipv6Octet production.
|
||||
ExitIpv6Octet(c *Ipv6OctetContext)
|
||||
|
||||
// ExitIpRange is called when exiting the ipRange production.
|
||||
ExitIpRange(c *IpRangeContext)
|
||||
|
||||
// ExitIpRangeBits is called when exiting the ipRangeBits production.
|
||||
ExitIpRangeBits(c *IpRangeBitsContext)
|
||||
|
||||
// ExitComment is called when exiting the comment production.
|
||||
ExitComment(c *CommentContext)
|
||||
|
||||
// ExitLeadingComment is called when exiting the leadingComment production.
|
||||
ExitLeadingComment(c *LeadingCommentContext)
|
||||
}
|
2369
handlers/hosts/parser/hosts_parser.go
Normal file
2369
handlers/hosts/parser/hosts_parser.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -16,12 +16,14 @@ const (
|
||||
LanguageSSHDConfig SupportedLanguage = "sshd_config"
|
||||
LanguageFstab SupportedLanguage = "fstab"
|
||||
LanguageWireguard SupportedLanguage = "languagewireguard"
|
||||
LanguageHosts SupportedLanguage = "hosts"
|
||||
)
|
||||
|
||||
var AllSupportedLanguages = []string{
|
||||
string(LanguageSSHDConfig),
|
||||
string(LanguageFstab),
|
||||
string(LanguageWireguard),
|
||||
string(LanguageHosts),
|
||||
}
|
||||
|
||||
type FatalFileNotReadableError struct {
|
||||
@ -59,11 +61,19 @@ var valueToLanguageMap = map[string]SupportedLanguage{
|
||||
"wireguard": LanguageWireguard,
|
||||
"wg": LanguageWireguard,
|
||||
"languagewireguard": LanguageWireguard,
|
||||
"host": LanguageHosts,
|
||||
"hosts": LanguageHosts,
|
||||
"etc/hosts": LanguageHosts,
|
||||
}
|
||||
|
||||
var typeOverwriteRegex = regexp.MustCompile(`^#\?\s*lsp\.language\s*=\s*(\w+)\s*$`)
|
||||
var typeOverwriteRegex = regexp.MustCompile(`#\?\s*lsp\.language\s*=\s*(\w+)\s*`)
|
||||
var wireguardPattern = regexp.MustCompile(`/wg\d+\.conf$`)
|
||||
|
||||
var undetectableError = common.ParseError{
|
||||
Line: 0,
|
||||
Err: LanguageUndetectableError{},
|
||||
}
|
||||
|
||||
func DetectLanguage(
|
||||
content string,
|
||||
advertisedLanguage string,
|
||||
@ -99,14 +109,13 @@ func DetectLanguage(
|
||||
return LanguageSSHDConfig, nil
|
||||
case "file:///etc/fstab":
|
||||
return LanguageFstab, nil
|
||||
case "file:///etc/hosts":
|
||||
return LanguageHosts, nil
|
||||
}
|
||||
|
||||
if strings.HasPrefix(uri, "file:///etc/wireguard/") || wireguardPattern.MatchString(uri) {
|
||||
return LanguageWireguard, nil
|
||||
}
|
||||
|
||||
return "", common.ParseError{
|
||||
Line: 0,
|
||||
Err: LanguageUndetectableError{},
|
||||
}
|
||||
return "", undetectableError
|
||||
}
|
||||
|
@ -20,8 +20,14 @@ func (h *RootHandler) AddDocument(uri protocol.DocumentUri, language SupportedLa
|
||||
h.languageMap[uri] = language
|
||||
}
|
||||
|
||||
func (h *RootHandler) GetLanguageForDocument(uri protocol.DocumentUri) SupportedLanguage {
|
||||
return h.languageMap[uri]
|
||||
func (h *RootHandler) GetLanguageForDocument(uri protocol.DocumentUri) *SupportedLanguage {
|
||||
language, found := h.languageMap[uri]
|
||||
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &language
|
||||
}
|
||||
|
||||
func (h *RootHandler) RemoveDocument(uri protocol.DocumentUri) {
|
||||
|
@ -1,7 +1,9 @@
|
||||
package roothandler
|
||||
|
||||
import (
|
||||
hosts "config-lsp/handlers/hosts/lsp"
|
||||
wireguard "config-lsp/handlers/wireguard/lsp"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
@ -9,14 +11,26 @@ import (
|
||||
func TextDocumentCodeAction(context *glsp.Context, params *protocol.CodeActionParams) (any, error) {
|
||||
language := rootHandler.GetLanguageForDocument(params.TextDocument.URI)
|
||||
|
||||
switch language {
|
||||
if language == nil {
|
||||
showParseError(
|
||||
context,
|
||||
params.TextDocument.URI,
|
||||
undetectableError,
|
||||
)
|
||||
|
||||
return nil, undetectableError.Err
|
||||
}
|
||||
|
||||
switch *language {
|
||||
case LanguageFstab:
|
||||
return nil, nil
|
||||
fallthrough
|
||||
case LanguageHosts:
|
||||
return hosts.TextDocumentCodeAction(context, params)
|
||||
case LanguageSSHDConfig:
|
||||
return nil, nil
|
||||
case LanguageWireguard:
|
||||
return wireguard.TextDocumentCodeAction(context, params)
|
||||
}
|
||||
|
||||
panic("root-handler/TextDocumentCompletion: unexpected language" + language)
|
||||
panic("root-handler/TextDocumentCompletion: unexpected language" + *language)
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package roothandler
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/fstab"
|
||||
hosts "config-lsp/handlers/hosts/lsp"
|
||||
wireguard "config-lsp/handlers/wireguard/lsp"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
@ -11,14 +12,26 @@ import (
|
||||
func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) {
|
||||
language := rootHandler.GetLanguageForDocument(params.TextDocument.URI)
|
||||
|
||||
switch language {
|
||||
if language == nil {
|
||||
showParseError(
|
||||
context,
|
||||
params.TextDocument.URI,
|
||||
undetectableError,
|
||||
)
|
||||
|
||||
return nil, undetectableError.Err
|
||||
}
|
||||
|
||||
switch *language {
|
||||
case LanguageFstab:
|
||||
return fstab.TextDocumentCompletion(context, params)
|
||||
case LanguageSSHDConfig:
|
||||
return nil, nil
|
||||
case LanguageWireguard:
|
||||
return wireguard.TextDocumentCompletion(context, params)
|
||||
case LanguageHosts:
|
||||
return hosts.TextDocumentCompletion(context, params)
|
||||
}
|
||||
|
||||
panic("root-handler/TextDocumentCompletion: unexpected language" + language)
|
||||
panic("root-handler/TextDocumentCompletion: unexpected language" + *language)
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package roothandler
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/fstab"
|
||||
hosts "config-lsp/handlers/hosts/lsp"
|
||||
wireguard "config-lsp/handlers/wireguard/lsp"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
@ -11,14 +12,52 @@ import (
|
||||
func TextDocumentDidChange(context *glsp.Context, params *protocol.DidChangeTextDocumentParams) error {
|
||||
language := rootHandler.GetLanguageForDocument(params.TextDocument.URI)
|
||||
|
||||
switch language {
|
||||
if language == nil {
|
||||
content := params.ContentChanges[0].(protocol.TextDocumentContentChangeEventWhole).Text
|
||||
newLanguage, err := initFile(
|
||||
context,
|
||||
content,
|
||||
params.TextDocument.URI,
|
||||
"",
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
language = newLanguage
|
||||
|
||||
params := &protocol.DidOpenTextDocumentParams{
|
||||
TextDocument: protocol.TextDocumentItem{
|
||||
URI: params.TextDocument.URI,
|
||||
Text: content,
|
||||
Version: params.TextDocument.Version,
|
||||
LanguageID: string(*language),
|
||||
},
|
||||
}
|
||||
|
||||
switch *language {
|
||||
case LanguageFstab:
|
||||
return fstab.TextDocumentDidOpen(context, params)
|
||||
case LanguageSSHDConfig:
|
||||
break
|
||||
case LanguageWireguard:
|
||||
return wireguard.TextDocumentDidOpen(context, params)
|
||||
case LanguageHosts:
|
||||
return hosts.TextDocumentDidOpen(context, params)
|
||||
}
|
||||
}
|
||||
|
||||
switch *language {
|
||||
case LanguageFstab:
|
||||
return fstab.TextDocumentDidChange(context, params)
|
||||
case LanguageSSHDConfig:
|
||||
return nil
|
||||
case LanguageWireguard:
|
||||
return wireguard.TextDocumentDidChange(context, params)
|
||||
case LanguageHosts:
|
||||
return hosts.TextDocumentDidChange(context, params)
|
||||
}
|
||||
|
||||
panic("root-handler/TextDocumentDidChange: unexpected language" + language)
|
||||
panic("root-handler/TextDocumentDidChange: unexpected language" + *language)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package roothandler
|
||||
|
||||
import (
|
||||
hosts "config-lsp/handlers/hosts/lsp"
|
||||
wireguard "config-lsp/handlers/wireguard/lsp"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
@ -10,14 +11,26 @@ import (
|
||||
func TextDocumentDidClose(context *glsp.Context, params *protocol.DidCloseTextDocumentParams) error {
|
||||
language := rootHandler.GetLanguageForDocument(params.TextDocument.URI)
|
||||
|
||||
if language == nil {
|
||||
showParseError(
|
||||
context,
|
||||
params.TextDocument.URI,
|
||||
undetectableError,
|
||||
)
|
||||
|
||||
return undetectableError.Err
|
||||
}
|
||||
|
||||
delete(openedFiles, params.TextDocument.URI)
|
||||
rootHandler.RemoveDocument(params.TextDocument.URI)
|
||||
|
||||
switch language {
|
||||
switch *language {
|
||||
case LanguageFstab:
|
||||
case LanguageSSHDConfig:
|
||||
case LanguageWireguard:
|
||||
return wireguard.TextDocumentDidClose(context, params)
|
||||
case LanguageHosts:
|
||||
return hosts.TextDocumentDidClose(context, params)
|
||||
default:
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package roothandler
|
||||
import (
|
||||
"config-lsp/common"
|
||||
fstab "config-lsp/handlers/fstab"
|
||||
hosts "config-lsp/handlers/hosts/lsp"
|
||||
wireguard "config-lsp/handlers/wireguard/lsp"
|
||||
"fmt"
|
||||
|
||||
@ -15,31 +16,26 @@ func TextDocumentDidOpen(context *glsp.Context, params *protocol.DidOpenTextDocu
|
||||
|
||||
// Find the file type
|
||||
content := params.TextDocument.Text
|
||||
language, err := DetectLanguage(content, params.TextDocument.LanguageID, params.TextDocument.URI)
|
||||
language, err := initFile(
|
||||
context,
|
||||
content,
|
||||
params.TextDocument.URI,
|
||||
params.TextDocument.LanguageID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
parseError := err.(common.ParseError)
|
||||
showParseError(
|
||||
context,
|
||||
params.TextDocument.URI,
|
||||
parseError,
|
||||
)
|
||||
|
||||
return parseError.Err
|
||||
return err
|
||||
}
|
||||
|
||||
openedFiles[params.TextDocument.URI] = struct{}{}
|
||||
|
||||
// Everything okay, now we can handle the file
|
||||
rootHandler.AddDocument(params.TextDocument.URI, language)
|
||||
|
||||
switch language {
|
||||
switch *language {
|
||||
case LanguageFstab:
|
||||
return fstab.TextDocumentDidOpen(context, params)
|
||||
case LanguageSSHDConfig:
|
||||
break
|
||||
case LanguageWireguard:
|
||||
return wireguard.TextDocumentDidOpen(context, params)
|
||||
case LanguageHosts:
|
||||
return hosts.TextDocumentDidOpen(context, params)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("unexpected roothandler.SupportedLanguage: %#v", language))
|
||||
@ -73,3 +69,30 @@ func showParseError(
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func initFile(
|
||||
context *glsp.Context,
|
||||
content string,
|
||||
uri protocol.DocumentUri,
|
||||
advertisedLanguage string,
|
||||
) (*SupportedLanguage, error) {
|
||||
language, err := DetectLanguage(content, advertisedLanguage, uri)
|
||||
|
||||
if err != nil {
|
||||
parseError := err.(common.ParseError)
|
||||
showParseError(
|
||||
context,
|
||||
uri,
|
||||
parseError,
|
||||
)
|
||||
|
||||
return nil, parseError.Err
|
||||
}
|
||||
|
||||
openedFiles[uri] = struct{}{}
|
||||
|
||||
// Everything okay, now we can handle the file
|
||||
rootHandler.AddDocument(uri, language)
|
||||
|
||||
return &language, nil
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package roothandler
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/fstab"
|
||||
hosts "config-lsp/handlers/hosts/lsp"
|
||||
wireguard "config-lsp/handlers/wireguard/lsp"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
@ -11,14 +12,26 @@ import (
|
||||
func TextDocumentHover(context *glsp.Context, params *protocol.HoverParams) (*protocol.Hover, error) {
|
||||
language := rootHandler.GetLanguageForDocument(params.TextDocument.URI)
|
||||
|
||||
switch language {
|
||||
case LanguageFstab:
|
||||
return fstab.TextDocumentHover(context, params)
|
||||
if language == nil {
|
||||
showParseError(
|
||||
context,
|
||||
params.TextDocument.URI,
|
||||
undetectableError,
|
||||
)
|
||||
|
||||
return nil, undetectableError.Err
|
||||
}
|
||||
|
||||
switch *language {
|
||||
case LanguageHosts:
|
||||
return hosts.TextDocumentHover(context, params)
|
||||
case LanguageSSHDConfig:
|
||||
return nil, nil
|
||||
case LanguageFstab:
|
||||
return fstab.TextDocumentHover(context, params)
|
||||
case LanguageWireguard:
|
||||
return wireguard.TextDocumentHover(context, params)
|
||||
}
|
||||
|
||||
panic("root-handler/TextDocumentHover: unexpected language" + language)
|
||||
panic("root-handler/TextDocumentHover: unexpected language" + *language)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package roothandler
|
||||
|
||||
import (
|
||||
hosts "config-lsp/handlers/hosts/lsp"
|
||||
wireguard "config-lsp/handlers/wireguard/lsp"
|
||||
"strings"
|
||||
|
||||
@ -17,6 +18,8 @@ func WorkspaceExecuteCommand(context *glsp.Context, params *protocol.ExecuteComm
|
||||
switch commandSection {
|
||||
case "wireguard":
|
||||
edit, err = wireguard.WorkspaceExecuteCommand(context, params)
|
||||
case "hosts":
|
||||
edit, err = hosts.WorkspaceExecuteCommand(context, params)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
@ -15,3 +15,7 @@ func GetTrimIndex(s string) []int {
|
||||
|
||||
return indexes[2:4]
|
||||
}
|
||||
|
||||
func SplitIntoLines(s string) []string {
|
||||
return regexp.MustCompile("\r?\n").Split(s, -1)
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
package utils
|
||||
|
||||
import "strings"
|
||||
|
||||
func Dedent(s string) string {
|
||||
return strings.TrimLeft(s, "\n")
|
||||
return s[len("\n"):]
|
||||
}
|
||||
|
||||
func KeyExists[T comparable, V any](keys map[T]V, key T) bool {
|
||||
|
Loading…
x
Reference in New Issue
Block a user