all repos

clerk @ 5581d44

missing tooling for ledger/hledger
3 files changed, 163 insertions(+), 14 deletions(-)
lsp: P directive completion
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-08-19 15:06:19 +0300
Authored at: 2026-08-19 14:23:17 +0300
Change ID: lxwwwvmqwyqwyuxwmrstzmlpwwtvxwzn
Parent: b7de91e
M internal/lsp/testdata/completion-contexts.txtar
···
        60
        60
         ~ monthly^ budget

      
        61
        61
         

      
        62
        62
         

      
        63
        
        -2024-01-15 Grocery

      
        64
        
        -    expenses:food  $50

      
        65
        
        -    expenses:taxi  $30

      
        66
        
        -    assets:cash

      
        67
        
        -

      
        68
        
        -2024-01-16 ^

      
        69
        
        -    expenses:f^ood  $5

      
        70
        
        -    assets:cash

      
        
        63
        +2024-01-15 Grocery

      
        
        64
        +    expenses:food  $50

      
        
        65
        +    expenses:taxi  $30

      
        
        66
        +    assets:cash

      
        
        67
        +

      
        
        68
        +2024-01-16 ^

      
        
        69
        +    expenses:f^ood  $5

      
        
        70
        +    assets:cash

      
        71
        71
         

      
        72
        72
         in^c

      
        73
        73
         20^

      ···
        100
        100
         2024/0^1/20 Grocery Store

      
        101
        101
             expenses:food  $10

      
        102
        102
             assets:cash

      
        
        103
        +

      
        
        104
        +P 2024-01-15 AAPL $1.5

      
        
        105
        +^P 2024-01-15 AAPL $1.5

      
        
        106
        +P^ 2024-01-15 AAPL $1.5

      
        
        107
        +

      
        
        108
        +P ^

      
        
        109
        +P 2^

      
        
        110
        +P 202-^

      
        
        111
        +P 2024-0^

      
        
        112
        +P 2024-01-15^

      
        
        113
        +P 2024-01-15 ^

      
        
        114
        +P 2024-01-15 U^

      
        
        115
        +P 2024-01-15 "my fund" U^

      
        
        116
        +P 2024-01-15 "my fund" $

      
        
        117
        +P 2024-01-15 AAPL $

      
        
        118
        +P 2024-01-15 AAPL $1.5^

      
        
        119
        +P 2024-01-15 AAPL $1.^5

      
        
        120
        +P 2024-01-15 AAPL ^1.5

      
        
        121
        +P 2024-01-15 AAPL 1.5 ^

      
        
        122
        +P 2024-01-15 AAPL 1.5 €^

      
        103
        123
         

      
        104
        124
         -- expect --

      
        105
        125
         19:0 date ""

      ···
        215
        235
           2024-06-15

      
        216
        236
         98:6 date "2024/0"

      
        217
        237
           2024/01/20

      
        
        238
        +103:0 none ""

      
        
        239
        +104:1 none ""

      
        
        240
        +106:2 date ""

      
        
        241
        +  today

      
        
        242
        +  yesterday

      
        
        243
        +  2 days ago

      
        
        244
        +  2024-06-15

      
        
        245
        +  2024/01/20

      
        
        246
        +  2024-01-16

      
        
        247
        +  2024-01-15

      
        
        248
        +  2024-01-05

      
        
        249
        +  2024-01-03

      
        
        250
        +  2024-01-02

      
        
        251
        +  2024-01-01

      
        
        252
        +  6-10

      
        
        253
        +107:3 date "2"

      
        
        254
        +  today

      
        
        255
        +  yesterday

      
        
        256
        +  2 days ago

      
        
        257
        +  2024-06-15

      
        
        258
        +  2024/01/20

      
        
        259
        +  2024-01-16

      
        
        260
        +  2024-01-15

      
        
        261
        +  2024-01-05

      
        
        262
        +  2024-01-03

      
        
        263
        +  2024-01-02

      
        
        264
        +  2024-01-01

      
        
        265
        +108:6 date "202-"

      
        
        266
        +  today

      
        
        267
        +  yesterday

      
        
        268
        +  2 days ago

      
        
        269
        +  2024-06-15

      
        
        270
        +  2024-01-16

      
        
        271
        +  2024-01-15

      
        
        272
        +  2024-01-05

      
        
        273
        +  2024-01-03

      
        
        274
        +  2024-01-02

      
        
        275
        +  2024-01-01

      
        
        276
        +109:8 date "2024-0"

      
        
        277
        +  2024-06-15

      
        
        278
        +  2024-01-16

      
        
        279
        +  2024-01-15

      
        
        280
        +  2024-01-05

      
        
        281
        +  2024-01-03

      
        
        282
        +  2024-01-02

      
        
        283
        +  2024-01-01

      
        
        284
        +110:12 date "2024-01-15"

      
        
        285
        +  2024-01-15

      
        
        286
        +111:13 commodity ""

      
        
        287
        +  $

      
        
        288
        +  USD

      
        
        289
        +112:14 commodity "U"

      
        
        290
        +  USD

      
        
        291
        +113:24 commodity "U"

      
        
        292
        +  USD

      
        
        293
        +116:22 commodity ""

      
        
        294
        +  $

      
        
        295
        +  USD

      
        
        296
        +117:21 none ""

      
        
        297
        +118:18 none ""

      
        
        298
        +119:22 commodity ""

      
        
        299
        +  $

      
        
        300
        +  USD

      
        
        301
        +120:23 commodity "€"

      
M internal/lsp/textdocument_completion.go
···
        84
        84
         		return cmplNone, cursor

      
        85
        85
         	case token.ACCOUNT, token.COMMODITY, token.PAYEE, token.TAG:

      
        86
        86
         		return cmplDirectiveContext(cursor, lineStart, toks)

      
        
        87
        +	case token.P:

      
        
        88
        +		return cmplPriceContext(cursor, toks)

      
        87
        89
         	case token.TEXT:

      
        88
        90
         		return cmplDirective, lineStart // half-typed keyword or unparseable line

      
        89
        91
         	default:

      ···
        143
        145
         	fieldStart := toks[0].Span.End.Offset

      
        144
        146
         	fieldEnd := fieldStart

      
        145
        147
         	seen := false

      
        146
        
        -	for i := 1; i < len(toks); i++ {

      
        147
        
        -		t := toks[i]

      
        
        148
        +	for _, t := range toks[1:] {

      
        148
        149
         		switch t.Type {

      
        149
        150
         		case token.WHITESPACE, token.STAR, token.BANG, token.DATE, token.TIME,

      
        150
        151
         			token.EQ, token.EQEQ, token.EQEQEQ:

      ···
        224
        225
         		return cmplTagName, start

      
        225
        226
         	}

      
        226
        227
         	return cmplNone, cursor

      
        
        228
        +}

      
        
        229
        +

      
        
        230
        +func cmplPriceContext(cursor int, toks []token.Token) (cmplCtx, int) {

      
        
        231
        +	kwEnd := toks[0].Span.End.Offset

      
        
        232
        +	if cursor > toks[0].Span.Start.Offset && cursor < kwEnd {

      
        
        233
        +		return cmplNone, cursor // on the P keyword

      
        
        234
        +	}

      
        
        235
        +

      
        
        236
        +	dateStart := kwEnd           // start of the date field, for replacing partials whole

      
        
        237
        +	dateDone := false            // a DATE, TIME, or symbol has ended the date field

      
        
        238
        +	for _, t := range toks[1:] { // skip the P keyword

      
        
        239
        +		if t.Type == token.WHITESPACE {

      
        
        240
        +			continue

      
        
        241
        +		}

      
        
        242
        +		if !dateDone && dateStart == kwEnd {

      
        
        243
        +			dateStart = t.Span.Start.Offset

      
        
        244
        +		}

      
        
        245
        +		if cursor > t.Span.End.Offset {

      
        
        246
        +			switch t.Type {

      
        
        247
        +			case token.DATE, token.TIME, token.COMMODITYMARK, token.STRING:

      
        
        248
        +				dateDone = true

      
        
        249
        +			}

      
        
        250
        +			continue

      
        
        251
        +		}

      
        
        252
        +		if cursor < t.Span.Start.Offset {

      
        
        253
        +			if (t.Type == token.INT || t.Type == token.DECIMAL) && !dateDone {

      
        
        254
        +				return cmplDate, dateStart // partial date fragments ahead

      
        
        255
        +			}

      
        
        256
        +			if t.Type == token.DATE || t.Type == token.TIME {

      
        
        257
        +				return cmplNone, cursor

      
        
        258
        +			}

      
        
        259
        +			return cmplCommodity, cursor // symbol or price-commodity slot

      
        
        260
        +		}

      
        
        261
        +

      
        
        262
        +		switch t.Type {

      
        
        263
        +		case token.DATE:

      
        
        264
        +			return cmplDate, t.Span.Start.Offset

      
        
        265
        +		case token.COMMODITYMARK:

      
        
        266
        +			return cmplCommodity, t.Span.Start.Offset

      
        
        267
        +		case token.STRING:

      
        
        268
        +			return cmplCommodity, min(t.Span.Start.Offset+1, cursor) // skip the opening quote

      
        
        269
        +		case token.TIME:

      
        
        270
        +			return cmplNone, cursor

      
        
        271
        +		case token.INT, token.DECIMAL: // a partial date fragment, or the amount quantity

      
        
        272
        +			if !dateDone {

      
        
        273
        +				return cmplDate, dateStart

      
        
        274
        +			}

      
        
        275
        +			if cursor == t.Span.End.Offset {

      
        
        276
        +				return cmplCommodity, cursor // suffix price-commodity slot

      
        
        277
        +			}

      
        
        278
        +			return cmplNone, cursor

      
        
        279
        +		}

      
        
        280
        +	}

      
        
        281
        +	// the cursor sits in whitespace after the last token, or only after the keyword

      
        
        282
        +	if !dateDone {

      
        
        283
        +		start := dateStart

      
        
        284
        +		if start == kwEnd {

      
        
        285
        +			start = cursor // `P `: the date comes next

      
        
        286
        +		}

      
        
        287
        +		return cmplDate, start

      
        
        288
        +	}

      
        
        289
        +	return cmplCommodity, cursor

      
        227
        290
         }

      
        228
        291
         

      
        229
        292
         // commentStart completes tag names before ':' of the current tag and tag values after it

      
M internal/lsp/textdocument_completion_test.go
···
        176
        176
         	srv.server.analysisFor(uri.URI("file:///test.journal")) // warm the per-doc cache

      
        177
        177
         

      
        178
        178
         	for tname, tt := range map[string]int{

      
        179
        
        -		"1k txns, account":     strings.Index(content, "\n  1:2:3 ") + len("\n  ") + 2,

      
        180
        
        -		"1k txns, empty payee": strings.Index(content, "transaction 1") + len("transaction "),

      
        181
        
        -		"1k txns, commodity":   strings.Index(content, "2 B @@") + len("2 B"),

      
        182
        
        -		"1k txns, date":        strings.Index(content, "2000-01-0") + len("2000-01-0"),

      
        
        179
        +		"1k txns, account":        strings.Index(content, "\n  1:2:3 ") + len("\n  ") + 2,

      
        
        180
        +		"1k txns, empty payee":    strings.Index(content, "transaction 1") + len("transaction "),

      
        
        181
        +		"1k txns, commodity":      strings.Index(content, "2 B @@") + len("2 B"),

      
        
        182
        +		"1k txns, date":           strings.Index(content, "2000-01-0") + len("2000-01-0"),

      
        
        183
        +		"1k txns, price date":     strings.Index(content, "P 2000-01-0") + len("P 2000-01-0"),

      
        
        184
        +		"1k txns, price quantity": strings.Index(content, "P 2000-01-01 A  0.70") + len("P 2000-01-01 A  0.70"),

      
        183
        185
         	} {

      
        184
        186
         		b.Run(tname, func(b *testing.B) {

      
        185
        187
         			line, col := lsputil.LineCol(content, tt)