Forge
markdowndeeb25a2
1<!-- English translation of adr/0111-editor-linenumber-linerange-value-objects.md. Canonical Russian: ../../adr/0111-editor-linenumber-linerange-value-objects.md -->
2
3# ADR 0111: `LineNumber` and `LineRange` as editor domain types
4
5## Related ADRs
6
7| ADR | Role |
8|-----|------|
9| [0081](0081-parametric-intent-melodies-editor-line-ranges.md) | Semantics of parametrics by strings; 0111 - VO LR/LN in IDE domain |
10| [0109](0109-declarative-parametric-melody-catalog-toml-and-code-binders.md) | Directory, `tail_signature`; `:ln` - directory metadata |
11| [0110](0110-roslyn-refactor-intent-melody-bridge.md) | Roslyn by range; future bridge to MCP arguments |
12
13## Summary
14
15- Value objects **`LineNumber`** / **`LineRange`** (1-based, Start ≤ End).
16- Border to JSON/commands - `int` in args; `ParsedLineRange` for parsing.
17
18
19## Rejected alternatives
20
21- **Only `:ln` aliases in TOML** - do not replace API checks and self-documentation in C#.
22- **`record` with int without invariants** - duplication of checks in each consumer.
23- **Complete transition of commands to typed DTO instead of JSON** - outside scope v1; the boundary remains on serialization.
24- **Special tokens “to the end of the file”** (`7:end`, `7:*`, etc.) - not included in v1: separate mini-grammar, interaction with file length in parse vs build, risk of collisions; left to a separate decision when there is a clear need.
25
26## Consequences (current)
27
28- New scripts **only by lines** in the parametric melody branch → preferably via LR/LN.
29- The "line is in file" check remains in **`ParametricLineRangeArgsBuilder`**, not in the order of the two numbers in the input and not in LN/LR per se.
30
31---
32## Solution
33
34- Enter **value objects** in the `CascadeIDE.Models.Editor` namespace:
35 - **LN (`LineNumber`)** — 1-based line number; invariant `Value >= LineNumber.MinimumOneBasedInclusive` (1); creation via `TryCreate(int, out LineNumber)`; comparison operators (including for CA1036 / `IComparable`).
36 - **LR (`LineRange`)** - a pair `Start` / `End` of type LN with the invariant `Start <= End` (inclusive range in terms of the editor and IDE commands); creation via `TryCreate(LineNumber, LineNumber, out LineRange)`.
37- **`ParametricIntentMelody.ParsedLineRange`** stores the range as **`LineRange Lines`**, rather than two "naked" `ints`.
38- Melody tail parser (`TryExtractLineRangeFromRemainder` in `ParametricIntentMelody`):
39 - **One whole** without a second slot - **one line** (`<line>` has the same meaning as `<line>:<line>` as LR).
40 - **Two integers** — boundaries of one inclusive range; the input order is not important: after parsing, **`min..max`** is applied (for example `7:3` and `3:7` → one LR).
41 - **`LineRange.TryCreate`** for **explicit code** still has a "second argument not before the first" contract; "inverted" input is processed **before** the `TryCreate` call, due to normalization in the parser.
42- At the border to JSON (**`ParametricLineRangeArgsBuilder`**) **`int`** are still sent to anonymous DTOs via `.Value` in LN - **wire format** `IdeCommands.Select` / `IdeCommands.ApplyEdit` does not change.
43- **`IntentMelodyTailSemantics.MinEditorLineNumber`** is consistent with **`LineNumber.MinimumOneBasedInclusive`** (one source constant for "minimal line 1-based").
44
45## Implementation v1 (sources in code)
46
47| Artifact | Destination |
48|----------|-----------|
49| `Models/Editor/LineNumber.cs` | LN, minimum constant, `TryCreate`, equality and comparison |
50| `Models/Editor/LineRange.cs` | LR, `TryCreate` with `End >= Start` |
51| `Services/ParametricIntentMelody.cs` | `ParsedLineRange`, `TryParseLineRangeTail`, `TryExtractLineRangeFromRemainder`, delegation to `ParametricLineRangeArgsBuilder` |
52| `Services/ParametricLineRangeArgsBuilder.cs` | Assembling JSON-args from `ParsedLineRange.Lines` + checking for file length overshoot |
53| `Services/IntentMelodyTailSemantics.cs` | `MinEditorLineNumber` → link to minimum LN |
54| `CascadeIDE.Tests/EditorLineNumberRangeTests.cs` | LN/LR Unit Tests |
55| `CascadeIDE.Tests/ParametricIntentMelodyTests.cs` | Parsing, one line, `min..max`, build args |
56| `IntentMelody/intent-melody-aliases.toml` and copy to `publish-gh-release/IntentMelody/` | Palette tooltips for `els` / `eld` (range and one line) |
57
58The directory still describes **two** numeric slots in `tail_signature` (`<start:ln>:<end:ln>`); the "single number" abbreviation is **application parser convention**, not a separate directory string.
59
60## Implementation v2 (roadmap after v1 - closed in code)
61| Artifact | Destination |
62|----------|-----------|
63| `Models/Editor/ColumnNumber.cs` | CN - 1-based column (`TryCreate`, compare) |
64| `Models/Editor/EditorDocumentPath.cs` | Document path wrapper: `CanonicalFilePath.TryNormalize`, case-insensitive comparison |
65| `Models/Editor/EditorMcpSpans.cs` | `EditorTextSpan.TryParse`, `EditorContentLineRangeMcpArgs.TryParse`, `EditorGoToPositionMcpArgs.TryParse` |
66| `Services/RoslynLinePositionMapper.cs` | `Microsoft.CodeAnalysis.Text.LinePosition` (0-based) → `(LineNumber, ColumnNumber)` for UI/MCP |
67| `Services/ContextMinimizer.cs`, `Services/WorkspaceDiagnosticsCoordinator.cs` | Use mapper instead of duplicating `+1` |
68| `ViewModels/IdeMcpCommandExecutor.Handlers.Editor/*.cs` | `Select` / `ApplyEdit` / `GoToPosition` / `GetEditorContentRange` via general VO parsing |
69| `Services/ParametricLineRangeArgsBuilder.cs` | Column boundaries via `ColumnNumber` + canonical `EditorDocumentPath` |
70| `Features/WebAiPortal/Application/WebAiPortalChatMixInFormatter.cs` | Editor range snapshot: `LineRange` instead of "naked" int lines |
71| `CascadeIDE.Tests/EditorMcpSpansTests.cs`, extension `EditorLineNumberRangeTests` | MCP Parser Failures and Limits |
72
73## Related ADRs
74
75| ADR | Role |
76|-----|------|
77| [0081](0081-parametric-intent-melodies-editor-line-ranges.md) | Semantics of parametrics by strings; 0111 - VO LR/LN in IDE domain |
78| [0109](0109-declarative-parametric-melody-catalog-toml-and-code-binders.md) | Directory, `tail_signature`; `:ln` - directory metadata |
79| [0110](0110-roslyn-refactor-intent-melody-bridge.md) | Roslyn by range; future bridge to MCP arguments |
80
81## Rejected alternatives
82
83- **Only `:ln` aliases in TOML** - do not replace API checks and self-documentation in C#.
84- **`record` with int without invariants** - duplication of checks in each consumer.
85- **Complete transition of commands to typed DTO instead of JSON** - outside scope v1; the boundary remains on serialization.
86- **Special tokens “to the end of the file”** (`7:end`, `7:*`, etc.) - not included in v1: separate mini-grammar, interaction with file length in parse vs build, risk of collisions; left to a separate decision when there is a clear need.
87
88## Consequences (current)
89
90- New scripts **only by lines** in the parametric melody branch → preferably via LR/LN.
91- The "line is in file" check remains in **`ParametricLineRangeArgsBuilder`**, not in the order of the two numbers in the input and not in LN/LR per se.
92
93---
94
95## Roadmap (after v1) - Done (v2)
96
97The goal of the v2 iterations is **the same pattern**: invariants in types up to the JSON/MCP boundary, without changing the `IdeCommands` wire contracts.
98
99### Priority 1 - MCP boundary → editor - **done**
100
101- **`EditorTextSpan`** + **`ColumnNumber`**, **`TryParse`** from `IReadOnlyDictionary<string, JsonElement>` for `Select` / `ApplyEdit`; **`EditorGoToPositionMcpArgs`** for `go_to_position`; **`EditorContentLineRangeMcpArgs`** for `get_editor_content_range` (if there are no keys - 1..1 as before; inverted explicit range - failure with message).
102
103### Priority 2 - web portal - **done**
104
105- **`WebAiPortalChatMixInFormatter`**: `EditorRangeSnap` stores **`LineRange`**; JSON parsing normalizes boundaries through `min/max` when ints are “reversed” in the wire response.
106
107### Priority 3 - parametric args - **done**
108
109- **`ParametricLineRangeArgsBuilder`**: columns via **`ColumnNumber.TryCreate`**, path via **`EditorDocumentPath`**.
110
111### Priority 4 - Roslyn - **done**
112
113- **`RoslynLinePositionMapper`**: `LinePosition` (0-based) → `(LineNumber, ColumnNumber)`; **`ContextMinimizer`**, **`WorkspaceDiagnosticsCoordinator`**.
114
115### File path - **done**
116
117- **`EditorDocumentPath`** over **`CanonicalFilePath.TryNormalize`**.
118
119### Readiness criterion
120
121- Two call sites with one set of JSON fields (`Select` + `ApplyEdit`) → **`EditorTextSpan`**; tests in **`EditorMcpSpansTests`**.
View only · write via MCP/CIDE