From 5b660d9b609a691b924567828ce4e1fed984b586 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 21 Sep 2024 15:30:58 +0200 Subject: [PATCH] feat(common): Add Position to common --- common/location.go | 107 +++++++++++++++++++++++++++++-- common/location_test.go | 137 ++++++++++++++++++++++++++++++++++++++++ common/lsp.go | 14 ++++ 3 files changed, 252 insertions(+), 6 deletions(-) create mode 100644 common/location_test.go diff --git a/common/location.go b/common/location.go index db050e7..fc62ca5 100644 --- a/common/location.go +++ b/common/location.go @@ -12,11 +12,60 @@ type Location struct { Character uint32 } +func (l Location) GetRelativeIndexPosition(i IndexPosition) IndexPosition { + return i - IndexPosition(l.Character) +} + +// LocationRange: Represents a range of characters in a document +// Locations are zero-based, start-inclusive and end-exclusive +// This approach is preferred over using an index-based range, because +// it allows to check very easily for cursor positions, as well as for +// index-based ranges. type LocationRange struct { Start Location End Location } +func (l LocationRange) ContainsPosition(p Position) bool { + return l.IsPositionAfterStart(p) && l.IsPositionBeforeEnd(p) +} + +// Check if the given position is after the start of the range +// It's like: Position >= Start +// This checks inclusively +func (l LocationRange) IsPositionAfterStart(p Position) bool { + return p.getValue() >= l.Start.Character +} + +func (l LocationRange) IsPositionBeforeStart(p Position) bool { + return p.getValue() < l.Start.Character +} + +// Check if the given position is before the end of the range +// It's like: Position <= End +// This checks inclusively +func (l LocationRange) IsPositionBeforeEnd(p Position) bool { + switch p.(type) { + case CursorPosition: + return p.getValue() <= l.End.Character + case IndexPosition: + return p.getValue() < l.End.Character + } + + return false +} + +func (l LocationRange) IsPositionAfterEnd(p Position) bool { + switch p.(type) { + case CursorPosition: + return p.getValue() > l.End.Character + case IndexPosition: + return p.getValue() >= l.End.Character + } + + return false +} + func (l LocationRange) ShiftHorizontal(offset uint32) LocationRange { return LocationRange{ Start: Location{ @@ -57,7 +106,7 @@ func (l LocationRange) ToLSPRange() protocol.Range { }, End: protocol.Position{ Line: l.End.Line, - Character: l.End.Character + 1, + Character: l.End.Character, }, } } @@ -67,10 +116,6 @@ func (l *LocationRange) ChangeBothLines(newLine uint32) { 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 } @@ -115,7 +160,57 @@ func CharacterRangeFromCtx( }, End: Location{ Line: line, - Character: end, + Character: end + 1, }, } } + +type Position interface { + getValue() uint32 +} + +// Use this type if you want to use a cursor based position +// A cursor based position is a position that represents a cursor +// Given the example: +// "PermitRootLogin yes" +// Taking a look at the first character "P" - the index is 0. +// However, the cursor can either be at: +// +// "|P" - 0 or +// "P|" - 1 +// +// This is used for example for textDocument/completion or textDocument/signature +type CursorPosition uint32 + +func (c CursorPosition) getValue() uint32 { + return uint32(c) +} + +func (c CursorPosition) shiftHorizontal(offset uint32) CursorPosition { + return CursorPosition(uint32(c) + offset) +} + +func LSPCharacterAsCursorPosition(character uint32) CursorPosition { + return CursorPosition(character) +} + +func (c CursorPosition) IsBeforeIndexPosition(i IndexPosition) bool { + // |H[e]llo + return uint32(c) < uint32(i) +} + +func (c CursorPosition) IsAfterIndexPosition(i IndexPosition) bool { + // H[e]|llo + return uint32(c) > uint32(i)+1 +} + +// Use this type if you want to use an index based position +type IndexPosition uint32 + +func (i IndexPosition) getValue() uint32 { + return uint32(i) +} + +func LSPCharacterAsIndexPosition(character uint32) IndexPosition { + return IndexPosition(character) +} diff --git a/common/location_test.go b/common/location_test.go new file mode 100644 index 0000000..823b6aa --- /dev/null +++ b/common/location_test.go @@ -0,0 +1,137 @@ +package common + +import ( + "testing" +) + +func TestCursorPosition( + t *testing.T, +) { + // Contains fictive range for the name "Test" in the code: + // func Test() {} + locationRange := LocationRange{ + Start: Location{ + Line: 0, + Character: 5, + }, + End: Location{ + Line: 0, + Character: 9, + }, + } + + if !(locationRange.ContainsPosition(LSPCharacterAsCursorPosition(5)) == true) { + t.Errorf("Expected 5 to be in range, but it wasn't") + } + + if !(locationRange.ContainsPosition(LSPCharacterAsCursorPosition(6)) == true) { + t.Errorf("Expected 6 to be in range, but it wasn't") + } + + if !(locationRange.ContainsPosition(LSPCharacterAsCursorPosition(9)) == true) { + t.Errorf("Expected 9 to be in range, but it wasn't") + } + + if !(locationRange.ContainsPosition(LSPCharacterAsCursorPosition(10)) == false) { + t.Errorf("Expected 10 to not be in range, but it was") + } + + if !(locationRange.ContainsPosition(LSPCharacterAsCursorPosition(4)) == false) { + t.Errorf("Expected 4 to not be in range, but it was") + } + + if !(locationRange.IsPositionBeforeStart(LSPCharacterAsCursorPosition(0)) == true) { + t.Errorf("Expected 0 to be before start, but it wasn't") + } + + if !(locationRange.IsPositionBeforeStart(LSPCharacterAsCursorPosition(4)) == true) { + t.Errorf("Expected 5 to be before start, but it wasn't") + } + + if !(locationRange.IsPositionBeforeStart(LSPCharacterAsCursorPosition(5)) == false) { + t.Errorf("Expected 5 to not be before start, but it was") + } + + if !(locationRange.IsPositionBeforeStart(LSPCharacterAsCursorPosition(10)) == false) { + t.Errorf("Expected 10 to not be before start, but it was") + } + + if !(locationRange.IsPositionAfterEnd(LSPCharacterAsCursorPosition(10)) == true) { + t.Errorf("Expected 10 to be after end, but it wasn't") + } + + if !(locationRange.IsPositionAfterEnd(LSPCharacterAsCursorPosition(11)) == true) { + t.Errorf("Expected 11 to be after end, but it wasn't") + } + + if !(locationRange.IsPositionAfterEnd(LSPCharacterAsCursorPosition(9)) == false) { + t.Errorf("Expected 9 to not be after end, but it was") + } + + if !(locationRange.IsPositionAfterEnd(LSPCharacterAsCursorPosition(5)) == false) { + t.Errorf("Expected 5 to not be after end, but it was") + } +} + +func TestIndexPosition(t *testing.T) { + // Contains fictive range for the name "Test" in the code: + // func Test() {} + locationRange := LocationRange{ + Start: Location{ + Line: 0, + Character: 5, + }, + End: Location{ + Line: 0, + Character: 9, + }, + } + + if !(locationRange.ContainsPosition(LSPCharacterAsIndexPosition(5)) == true) { + t.Errorf("Expected index position 5 to be in range, but it wasn't") + } + + if !(locationRange.ContainsPosition(LSPCharacterAsIndexPosition(6)) == true) { + t.Errorf("Expected index position 6 to be in range, but it wasn't") + } + + if !(locationRange.ContainsPosition(LSPCharacterAsIndexPosition(8)) == true) { + t.Errorf("Expected index position 6 to be in range, but it wasn't") + } + + if !(locationRange.ContainsPosition(LSPCharacterAsIndexPosition(9)) == false) { + t.Errorf("Expected index position 9 to not be in range, but it was") + } + + if !(locationRange.ContainsPosition(LSPCharacterAsIndexPosition(10)) == false) { + t.Errorf("Expected index position 10 to not be in range, but it was") + } + + if !(locationRange.ContainsPosition(LSPCharacterAsIndexPosition(4)) == false) { + t.Errorf("Expected index position 4 to not be in range, but it was") + } + + if !(locationRange.IsPositionBeforeStart(LSPCharacterAsIndexPosition(4)) == true) { + t.Errorf("Expected index position 4 to be before start, but it wasn't") + } + + if !(locationRange.IsPositionBeforeStart(LSPCharacterAsIndexPosition(5)) == false) { + t.Errorf("Expected index position 5 to not be before start, but it was") + } + + if !(locationRange.IsPositionBeforeStart(LSPCharacterAsIndexPosition(10)) == false) { + t.Errorf("Expected index position 10 to not be before start, but it wasn't") + } + + if !(locationRange.IsPositionAfterEnd(LSPCharacterAsIndexPosition(10)) == true) { + t.Errorf("Expected index position 10 to be after end, but it wasn't") + } + + if !(locationRange.IsPositionAfterEnd(LSPCharacterAsIndexPosition(9)) == true) { + t.Errorf("Expected index position 9 to be after end, but it wasn't") + } + + if !(locationRange.IsPositionAfterEnd(LSPCharacterAsIndexPosition(5)) == false) { + t.Errorf("Expected index position 5 to not be after end, but it was") + } +} diff --git a/common/lsp.go b/common/lsp.go index 29c0279..fd2e091 100644 --- a/common/lsp.go +++ b/common/lsp.go @@ -1,5 +1,19 @@ package common +// LSPCharacterAsCursorPosition: +// @deprecated func CursorToCharacterIndex(cursor uint32) uint32 { return max(1, cursor) - 1 } + +func DeprecatedImprovedCursorToIndex( + c CursorPosition, + line string, + offset uint32, +) uint32 { + if len(line) == 0 { + return 0 + } + + return min(uint32(len(line)-1), uint32(c)-offset+1) +}