all repos

clerk @ 942596e

missing tooling for ledger/hledger
5 files changed, 69 insertions(+), 19 deletions(-)
lsp: go to definition on include file path
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-09-03 17:01:00 +0300
Authored at: 2026-09-03 16:33:45 +0300
Change ID: pwtxrktvuyuqltrvtpmlppxotzoukksy
Parent: 961356c
M internal/lsp/testdata/definition-include.txtar
···
        1
        1
         -- in.journal --

      
        2
        
        -include sub.journal

      
        
        2
        +include su^b.journal

      
        
        3
        +include dir/ot^her.journal

      
        3
        4
         

      
        4
        5
         2024-01-16 Another^ Store

      
        5
        
        -    expenses:fo^od  100.00 EUR

      
        6
        
        -    assets:ca^sh

      
        
        6
        +  expenses:fo^od  100.00 EUR

      
        
        7
        +  assets:ca^sh

      
        
        8
        +

      
        
        9
        +-- dir/other.journal --

      
        
        10
        +account expenses:entertainment

      
        
        11
        +

      
        
        12
        +2024-01-13 Cinema

      
        
        13
        +  expenses:entertainment  £10

      
        
        14
        +  assets:bank

      
        7
        15
         

      
        8
        16
         -- sub.journal --

      
        9
        17
         account expenses:food

      
        10
        18
         

      
        11
        19
         2024-01-15 Grocery Store

      
        12
        
        -    expenses:food  $50

      
        13
        
        -    assets:cash

      
        
        20
        +  expenses:food  $50

      
        
        21
        +  assets:cash

      
        14
        22
         

      
        15
        23
         -- expect --

      
        16
        
        -2:18 in.journal 2:11-2:24

      
        17
        
        -3:15 sub.journal 0:8-0:21

      
        18
        
        -4:13 sub.journal 4:4-4:15

      
        
        24
        +0:10 sub.journal 0:0-0:0

      
        
        25
        +1:14 other.journal 0:0-0:0

      
        
        26
        +3:18 in.journal 3:11-3:24

      
        
        27
        +4:13 sub.journal 0:8-0:21

      
        
        28
        +5:11 sub.journal 4:2-4:13

      
M internal/lsp/textdocument_completion_test.go
···
        1
        
        -// TODO:

      
        2
        1
         package lsp

      
        3
        2
         

      
        4
        3
         import (

      
M internal/lsp/textdocument_definition.go
···
        2
        2
         

      
        3
        3
         import (

      
        4
        4
         	"context"

      
        
        5
        +	"path/filepath"

      
        5
        6
         	"slices"

      
        6
        7
         	"sort"

      
        
        8
        +	"strings"

      
        7
        9
         

      
        8
        10
         	"go.lsp.dev/protocol"

      
        9
        11
         	"go.lsp.dev/uri"

      ···
        30
        32
         	if ref == nil {

      
        31
        33
         		return nil

      
        32
        34
         	}

      
        33
        
        -	loc := resolveSymbol(ref, an)

      
        34
        
        -	if loc == nil {

      
        35
        
        -		return nil

      
        36
        
        -	}

      
        37
        
        -	return protocol.LocationSlice{*loc}

      
        
        35
        +	return resolveSymbol(ref, an, docPath)

      
        38
        36
         }

      
        39
        37
         

      
        40
        
        -func resolveSymbol(ref *symbolRef, an *analyzer.Analysis) *protocol.Location {

      
        
        38
        +func resolveSymbol(ref *symbolRef, an *analyzer.Analysis, docPath string) protocol.LocationSlice {

      
        
        39
        +	var loc *protocol.Location

      
        41
        40
         	switch ref.kind {

      
        42
        41
         	case symbolAccount:

      
        43
        
        -		return findAccountDefinition(an, ref.name)

      
        
        42
        +		loc = findAccountDefinition(an, ref.name)

      
        44
        43
         	case symbolCommodity:

      
        45
        
        -		return findCommodityDefinition(an, ref.name)

      
        
        44
        +		loc = findCommodityDefinition(an, ref.name)

      
        46
        45
         	case symbolPayee:

      
        47
        
        -		return findPayeeDefinition(an, ref.name)

      
        
        46
        +		loc = findPayeeDefinition(an, ref.name)

      
        
        47
        +	case symbolInclude:

      
        
        48
        +		return includeTargets(an, docPath, ref.name)

      
        
        49
        +	default:

      
        
        50
        +		return nil

      
        48
        51
         	}

      
        49
        
        -	return nil

      
        
        52
        +	if loc == nil {

      
        
        53
        +		return nil

      
        
        54
        +	}

      
        
        55
        +	return protocol.LocationSlice{*loc}

      
        50
        56
         }

      
        51
        57
         

      
        52
        58
         func findAccountDefinition(an *analyzer.Analysis, name string) *protocol.Location {

      ···
        141
        147
         		return locationFor(an, u.FileIndex, span)

      
        142
        148
         	}

      
        143
        149
         	return nil

      
        
        150
        +}

      
        
        151
        +

      
        
        152
        +func includeTargets(an *analyzer.Analysis, docPath, pattern string) protocol.LocationSlice {

      
        
        153
        +	target := filepath.Join(filepath.Dir(docPath), pattern)

      
        
        154
        +	glob := strings.ContainsAny(target, "*?[")

      
        
        155
        +	found := make(map[string]bool)

      
        
        156
        +	var locs protocol.LocationSlice

      
        
        157
        +	for _, occ := range an.Files {

      
        
        158
        +		if found[occ.Path] {

      
        
        159
        +			continue

      
        
        160
        +		}

      
        
        161
        +		if glob {

      
        
        162
        +			if m, _ := filepath.Match(target, occ.Path); !m {

      
        
        163
        +				continue

      
        
        164
        +			}

      
        
        165
        +		} else if occ.Path != target {

      
        
        166
        +			continue

      
        
        167
        +		}

      
        
        168
        +		found[occ.Path] = true

      
        
        169
        +		locs = append(locs, protocol.Location{URI: uri.File(occ.Path)})

      
        
        170
        +	}

      
        
        171
        +	return locs

      
        144
        172
         }

      
        145
        173
         

      
        146
        174
         func fileIndexForEntry(a *analyzer.Analysis, d ast.Entry) int {

      
M internal/lsp/textdocument_rename.go
···
        27
        27
         	if ref == nil {

      
        28
        28
         		return nil, nil

      
        29
        29
         	}

      
        
        30
        +	switch ref.kind {

      
        
        31
        +	case symbolAccount, symbolCommodity, symbolPayee, symbolTag:

      
        
        32
        +	default:

      
        
        33
        +		return nil, nil // kinds without rename support

      
        
        34
        +	}

      
        30
        35
         

      
        31
        36
         	return &protocol.PrepareRenamePlaceholder{

      
        32
        37
         		Range:       state.lineIdx.SpanRange(ref.span),

      ···
        147
        152
         		if e.Name != nil && spanContains(content, e.Name.Span, cursor) {

      
        148
        153
         			return &symbolRef{symbolPayee, e.Name.Name, e.Name.Span}

      
        149
        154
         		}

      
        
        155
        +	case *ast.IncludeDirective:

      
        
        156
        +		if spanContains(content, e.Span, cursor) {

      
        
        157
        +			return &symbolRef{symbolInclude, e.Path, e.Span}

      
        
        158
        +		}

      
        150
        159
         	}

      
        151
        160
         	return nil

      
        152
        161
         }

      ···
        160
        169
         	symbolCommodity

      
        161
        170
         	symbolPayee

      
        162
        171
         	symbolTag

      
        
        172
        +	symbolInclude

      
        163
        173
         )

      
        164
        174
         

      
        165
        175
         func (s symbolKind) ToProtocol() protocol.SymbolKind {

      
M internal/lsp/textdocument_rename_test.go
···
        192
        192
         		if f.Name == "expect" || f.Name == "rename" {

      
        193
        193
         			continue

      
        194
        194
         		}

      
        
        195
        +		if err := os.MkdirAll(filepath.Join(dir, filepath.Dir(f.Name)), 0o755); err != nil {

      
        
        196
        +			t.Fatal(err)

      
        
        197
        +		}

      
        195
        198
         		if err := os.WriteFile(filepath.Join(dir, f.Name), f.Data, 0o644); err != nil {

      
        196
        199
         			t.Fatal(err)

      
        197
        200
         		}