diff --git a/server/handlers/gitconfig/ast/gitconfig.go b/server/handlers/gitconfig/ast/gitconfig.go index 40778e7..912d9cb 100644 --- a/server/handlers/gitconfig/ast/gitconfig.go +++ b/server/handlers/gitconfig/ast/gitconfig.go @@ -3,8 +3,6 @@ package ast import ( "config-lsp/common" "config-lsp/common/parser" - - "github.com/emirpasic/gods/maps/treemap" ) type GitKey struct { @@ -41,7 +39,10 @@ type GitSectionHeader struct { type GitSection struct { common.LocationRange - Entries *treemap.Map + + // This is a simple list because gitconfig supports multiline entries, + // and thus fetching by line number is not feasible + Entries []*GitEntry Title *GitSectionHeader } diff --git a/server/handlers/gitconfig/ast/gitconfig_fields.go b/server/handlers/gitconfig/ast/gitconfig_fields.go index 854a446..ec1afc5 100644 --- a/server/handlers/gitconfig/ast/gitconfig_fields.go +++ b/server/handlers/gitconfig/ast/gitconfig_fields.go @@ -1,6 +1,68 @@ package ast +import "slices" + func (c *GitConfig) Clear() { c.Sections = []*GitSection{} c.CommentLines = map[uint32]struct{}{} } + +func (c *GitConfig) FindSection(line uint32) *GitSection { + index, found := slices.BinarySearchFunc( + c.Sections, + line, + func(current *GitSection, target uint32) int { + if target > current.End.Line { + return -1 + } + + if target < current.Start.Line { + return 1 + } + + return 0 + }, + ) + + if !found { + return nil + } + + return c.Sections[index] +} + +func (c *GitConfig) FindOption(line uint32) (*GitSection, *GitEntry) { + section := c.FindSection(line) + + if section == nil { + return nil, nil + } + + entry := section.FindOption(line) + + return section, entry +} + +func (s *GitSection) FindOption(line uint32) *GitEntry { + index, found := slices.BinarySearchFunc( + s.Entries, + line, + func(current *GitEntry, target uint32) int { + if target > current.End.Line { + return -1 + } + + if target < current.Start.Line { + return 1 + } + + return 0 + }, + ) + + if !found { + return nil + } + + return s.Entries[index] +} diff --git a/server/handlers/gitconfig/ast/listener.go b/server/handlers/gitconfig/ast/listener.go index 1ef79e3..c3b6932 100644 --- a/server/handlers/gitconfig/ast/listener.go +++ b/server/handlers/gitconfig/ast/listener.go @@ -46,10 +46,11 @@ func (s *gitconfigParserListener) EnterEntry(ctx *parser.EntryContext) { LocationRange: location, } - s.gitconfigContext.currentSection.Entries.Put( - location.Start.Line, + s.gitconfigContext.currentSection.Entries = append( + s.gitconfigContext.currentSection.Entries, s.gitconfigContext.currentEntry, ) + s.gitconfigContext.currentSection.End = location.End } func (s *gitconfigParserListener) ExitEntry(ctx *parser.EntryContext) { diff --git a/server/handlers/gitconfig/ast/parser.go b/server/handlers/gitconfig/ast/parser.go index 6f8fb70..1e33dac 100644 --- a/server/handlers/gitconfig/ast/parser.go +++ b/server/handlers/gitconfig/ast/parser.go @@ -8,9 +8,7 @@ import ( "strings" "github.com/antlr4-go/antlr/v4" - "github.com/emirpasic/gods/maps/treemap" - gods "github.com/emirpasic/gods/utils" protocol "github.com/tliron/glsp/protocol_3_16" ) @@ -161,7 +159,7 @@ func (c *GitConfig) parseHeader( LocationRange: location, Title: input[leftBracketIndex+1 : rightBracketIndex], }, - Entries: treemap.NewWith(gods.UInt32Comparator), + Entries: make([]*GitEntry, 0), } c.Sections = append(c.Sections, context.currentSection) diff --git a/server/handlers/gitconfig/ast/parser_test.go b/server/handlers/gitconfig/ast/parser_test.go index eb3b45c..6127d5f 100644 --- a/server/handlers/gitconfig/ast/parser_test.go +++ b/server/handlers/gitconfig/ast/parser_test.go @@ -26,23 +26,32 @@ func TestValidOneSectionExample(t *testing.T) { section := config.Sections[0] - rawFirstOption, _ := section.Entries.Get(uint32(1)) - firstOption := rawFirstOption.(*GitEntry) + firstOption := section.Entries[0] if !(firstOption.Key.Value.Value == "repositoryformatversion" && firstOption.Value.Value == "0") { t.Errorf("Expected repositoryformatversion, got %s", firstOption.Key.Value.Value) } - rawSecondOption, _ := section.Entries.Get(uint32(2)) - secondOption := rawSecondOption.(*GitEntry) + secondOption := section.Entries[1] if !(secondOption.Key.Value.Value == "filemode" && secondOption.Value.Value == "true") { t.Errorf("Expected filemode, got %s", secondOption.Key.Value.Value) } - rawThirdOption, _ := section.Entries.Get(uint32(3)) - thirdOption := rawThirdOption.(*GitEntry) + thirdOption := section.Entries[2] if !(thirdOption.Key.Value.Value == "bare" && thirdOption.Value.Value == "false") { t.Errorf("Expected bare, got %s", thirdOption.Key.Value.Value) } + + foundSection, foundEntry := config.FindOption(1) + + if !(foundSection == section && foundEntry == firstOption) { + t.Errorf("Expected first option, got %s", foundEntry.Key.Value.Value) + } + + foundSection, foundEntry = config.FindOption(0) + + if !(foundSection == section && foundEntry == nil) { + t.Errorf("Expected nil, got %s", foundEntry) + } } func TestComplexExample(t *testing.T) { @@ -81,29 +90,23 @@ func TestComplexExample(t *testing.T) { t.Errorf("Expected core, got %s", section.Title.Title) } - if !(section.Entries.Size() == 3) { - t.Errorf("Expected 3 entries, got %d", section.Entries.Size()) + if !(len(section.Entries) == 3) { + t.Errorf("Expected 3 entries, got %d", len(section.Entries)) } - rawFirstOption, _ := section.Entries.Get(uint32(1)) - firstOption := rawFirstOption.(*GitEntry) + firstOption := section.Entries[0] if !(firstOption.Key.Value.Value == "repositoryformatversion" && firstOption.Value.Value == "0" && firstOption.Start.Line == 1 && firstOption.End.Line == 1 && firstOption.Start.Character == 4 && firstOption.End.Character == 31) { t.Errorf("Expected 0, got %s", firstOption) } - _, found := section.Entries.Get(uint32(3)) - if found { - t.Errorf("Expected no entry at line 3") - } - section = config.Sections[1] if !(section.Title.Title == `remote "origin"`) { t.Errorf("Expected remote \"origin\", got %s", section.Title.Title) } - if !(section.Entries.Size() == 2) { - t.Errorf("Expected 2 entries, got %d", section.Entries.Size()) + if !(len(section.Entries) == 2) { + t.Errorf("Expected 2 entries, got %d", len(section.Entries)) } section = config.Sections[2] @@ -112,8 +115,7 @@ func TestComplexExample(t *testing.T) { t.Errorf("Expected alias, got %s", section.Title.Title) } - rawSecondOption, _ := section.Entries.Get(uint32(13)) - secondOption := rawSecondOption.(*GitEntry) + secondOption := section.Entries[0] if !(secondOption.Key.Value.Value == "ours" && secondOption.Value.Value == "!f() { git checkout --ours $@ && git add $@; }; f" && secondOption.Start.Character == 1 && secondOption.End.Character == 59) { t.Errorf("Expected ours, got %s", secondOption.Key.Value.Value) } @@ -162,8 +164,7 @@ func TestLeadingLine(t *testing.T) { t.Errorf("Expected core, got %s", section.Title.Title) } - rawFirstOption, _ := section.Entries.Get(uint32(1)) - firstOption := rawFirstOption.(*GitEntry) + firstOption := section.Entries[0] if !(firstOption.Key.Value.Value == "command" && firstOption.Value.Value == "git commit -m Hello World") { t.Errorf("Expected command, got %s", firstOption.Key.Value.Value) }