@effected/markdown 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import { MARKDOWN_PARSE_ERROR_CODES } from "./internal/carriers.js";
2
+ import { LineIndex } from "@effected/source";
2
3
  import { Schema } from "effect";
3
4
 
4
5
  // src/MarkdownDiagnostic.ts
@@ -16,14 +17,20 @@ const MarkdownParseErrorCode = Schema.Literals(MARKDOWN_PARSE_ERROR_CODES);
16
17
  * zero-based `line`/`character`).
17
18
  *
18
19
  * @remarks
19
- * The five-field positional core (`code`/`offset`/`length`/`line`/`character`)
20
- * is structurally identical to `@effected/toml`'s `TomlDiagnostic` (and, by
21
- * the same cross-package contract, `@effected/jsonc`'s parse-error detail
22
- * shape and `@effected/yaml`'s `YamlDiagnostic`); `message` is this
23
- * package's additive extra. `line`/`character` here are zero-based to match
24
- * that contract — a different numbering from the one-based `line`/`column`
25
- * unist `Point`s carried on `MarkdownNode` positions, which is a deliberate,
26
- * unrelated convention for the AST rather than a mismatch to reconcile.
20
+ * Satisfies `@effected/source`'s `SourceDiagnostic<MarkdownParseErrorCode>`:
21
+ * the positional core (`code`/`message`/`offset`/`length`/`line`/`character`)
22
+ * the kit's format packages share, so a consumer printing or serving
23
+ * diagnostics (an LSP server, a CLI annotation) is written once against that
24
+ * shape. `line`/`character` are zero-based, `character` in UTF-16 code units,
25
+ * with `\n`, `\r\n` and a bare `\r` as line breaks: exactly
26
+ * `LineIndex.make(source).positionAt(offset)`, and the same index
27
+ * {@link MarkdownDocument.lineIndex} returns. An offset at the `\n` of a
28
+ * `\r\n` reads as the end of its line.
29
+ *
30
+ * The one-based `line`/`column` unist `Point`s carried on `MarkdownNode`
31
+ * positions read the same index, one-based: a deliberate, different
32
+ * convention for the AST rather than a mismatch to reconcile (see
33
+ * {@link MarkdownDocument.lineIndex} for the exact mapping).
27
34
  *
28
35
  * @public
29
36
  */
@@ -40,12 +47,20 @@ var MarkdownDiagnostic = class MarkdownDiagnostic extends Schema.Class("Markdown
40
47
  * from `offset` against the source text. Advanced — the parse entry
41
48
  * points call this for you.
42
49
  *
43
- * @param source - The source text the record's `offset` indexes into.
50
+ * @remarks
51
+ * Pass a line index already built over the source to materialize many
52
+ * records without rescanning the text; a string builds one. The
53
+ * parameter is the structural `SourceIndex`, so an index from any
54
+ * installed copy of `@effected/source` is accepted. An offset
55
+ * outside the source clamps into it, as `LineIndex.positionAt` does.
56
+ *
57
+ * @param source - The source text the record's `offset` indexes into, or
58
+ * a `LineIndex` over it.
44
59
  * @param raw - The engine record: `code`, `message`, `offset` and `length`.
45
60
  * @returns The diagnostic with `line` and `character` filled in.
46
61
  */
47
62
  static fromRaw(source, raw) {
48
- const { line, character } = lineChar(source, raw.offset);
63
+ const { line, character } = (typeof source === "string" ? LineIndex.make(source) : source).positionAt(raw.offset);
49
64
  return MarkdownDiagnostic.make({
50
65
  code: raw.code,
51
66
  message: raw.message,
@@ -56,32 +71,5 @@ var MarkdownDiagnostic = class MarkdownDiagnostic extends Schema.Class("Markdown
56
71
  });
57
72
  }
58
73
  };
59
- /**
60
- * Compute the zero-based line/character position of `offset` within `text`.
61
- * Recognizes `\n`, `\r` and `\r\n` as line breaks; a CRLF pair counts as a
62
- * single newline (toml `TomlDiagnostic` precedent — kept independent of
63
- * `internal/lineIndex.ts`, which answers a different, one-based question
64
- * for AST positions).
65
- */
66
- function lineChar(text, offset) {
67
- let line = 0;
68
- let lineStart = 0;
69
- const limit = Math.min(offset, text.length);
70
- for (let i = 0; i < limit; i++) {
71
- const ch = text.charCodeAt(i);
72
- if (ch === 10) {
73
- line++;
74
- lineStart = i + 1;
75
- } else if (ch === 13) {
76
- if (i + 1 < text.length && text.charCodeAt(i + 1) === 10) i++;
77
- line++;
78
- lineStart = i + 1;
79
- }
80
- }
81
- return {
82
- line,
83
- character: offset - lineStart
84
- };
85
- }
86
74
 
87
75
  export { MarkdownDiagnostic, MarkdownParseErrorCode };
@@ -6,6 +6,7 @@ import { normalizeLabelText } from "./internal/references.js";
6
6
  import { MarkdownDiagnostic } from "./MarkdownDiagnostic.js";
7
7
  import { rootHeadingBounds } from "./internal/sectionBounds.js";
8
8
  import { parsePassResult } from "./Markdown.js";
9
+ import { LineIndex } from "@effected/source";
9
10
  import { Effect, Option, Result, Schema } from "effect";
10
11
 
11
12
  // src/MarkdownDocument.ts
@@ -122,6 +123,12 @@ const phrasingText = (nodes) => {
122
123
  return out;
123
124
  };
124
125
  /**
126
+ * Each document's line index, built at most once. A side table rather than a
127
+ * field: the index is derived from `source`, so it is no part of the
128
+ * document's schema, its equality or its encoded form.
129
+ */
130
+ const lineIndexes = /* @__PURE__ */ new WeakMap();
131
+ /**
125
132
  * A parsed markdown document: the original `source`, the mdast-shaped
126
133
  * {@link Root} tree, the non-fatal {@link MarkdownDiagnostic}s the parse
127
134
  * produced, and the link-reference `definitions` index.
@@ -213,6 +220,55 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
213
220
  return scanFrontmatter(preprocessLines(this.source), this.source) !== null;
214
221
  }
215
222
  /**
223
+ * The line index over `source`: offsets to zero-based `{ line, character }`
224
+ * and back, with exactly the rules this package's own positions use.
225
+ *
226
+ * @remarks
227
+ * A `LineIndex` from `@effected/source` (re-exported from this package's
228
+ * entry point), typed as the structural `SourceIndex` so a consumer on
229
+ * another installed copy of `@effected/source` can pass it on, with its
230
+ * default `"lsp"` line breaks: `\n`, `\r\n` and a
231
+ * bare `\r`, `character` in UTF-16 code units, an offset at the `\n` of a
232
+ * `\r\n` reading as the end of its line. So for any offset into
233
+ * `source`:
234
+ *
235
+ * - `doc.lineIndex.positionAt(offset)` is the `line`/`character` a
236
+ * {@link MarkdownDiagnostic} at that offset carries;
237
+ * - a node's unist `Point` has `line` = `lineAt(offset) + 1` and
238
+ * `column` = `offset - lineStart(line) + 1`: `positionAt` plus one on
239
+ * both axes everywhere except at the `\n` of a `\r\n`, which a Point
240
+ * counts as one column past the `\r` (as `vfile-location` does) and
241
+ * `positionAt` reads as the end of its line;
242
+ * - `offsetAt`, `spanOf` and `rangeOf` convert an LSP `Position` or
243
+ * `Range` back to an offset or a `MarkdownRange`-shaped
244
+ * `{ offset, length }`.
245
+ *
246
+ * A parsed document returns the index the parse itself built; a document
247
+ * constructed with `make` builds one on first access. Either way it is
248
+ * built once per document and shared by every later access. To index a
249
+ * text with no document, `LineIndex.make(text)` gives the same answers.
250
+ *
251
+ * @example
252
+ * ```ts
253
+ * import { MarkdownDocument } from "@effected/markdown";
254
+ * import { Result } from "effect";
255
+ *
256
+ * const result = MarkdownDocument.parseResult("# Title\r\n\r\nBody\n");
257
+ * if (Result.isSuccess(result)) {
258
+ * const index = result.success.lineIndex;
259
+ * console.log(index.positionAt(12)); // => { line: 2, character: 1 }
260
+ * console.log(index.offsetAt({ line: 2, character: 0 })); // => 11
261
+ * }
262
+ * ```
263
+ */
264
+ get lineIndex() {
265
+ const cached = lineIndexes.get(this);
266
+ if (cached !== void 0) return cached;
267
+ const index = LineIndex.make(this.source);
268
+ lineIndexes.set(this, index);
269
+ return index;
270
+ }
271
+ /**
216
272
  * Every heading in the document, in document order, wherever it sits —
217
273
  * including headings nested inside blockquotes and list items.
218
274
  *
@@ -379,12 +435,16 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
379
435
  * `MarkdownParseError`.
380
436
  */
381
437
  static parseResult(text, options) {
382
- return Result.map(parsePassResult(text, options), (pass) => MarkdownDocument.make({
383
- source: text,
384
- root: pass.root,
385
- diagnostics: pass.carriers.map((carrier) => MarkdownDiagnostic.fromRaw(text, carrier)),
386
- definitions: pass.refmap
387
- }));
438
+ return Result.map(parsePassResult(text, options), (pass) => {
439
+ const document = MarkdownDocument.make({
440
+ source: text,
441
+ root: pass.root,
442
+ diagnostics: pass.carriers.map((carrier) => MarkdownDiagnostic.fromRaw(pass.lineIndex, carrier)),
443
+ definitions: pass.refmap
444
+ });
445
+ lineIndexes.set(document, pass.lineIndex);
446
+ return document;
447
+ });
388
448
  }
389
449
  /**
390
450
  * Parse markdown into a {@link MarkdownDocument}. Defined in terms of
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![Node.js %3E%3D24.11.0](https://img.shields.io/badge/Node.js-%3E%3D24.11.0-5fa04e.svg)](https://nodejs.org/)
6
6
  [![TypeScript 7.0](https://img.shields.io/badge/TypeScript-7.0-3178c6.svg)](https://www.typescriptlang.org/)
7
7
 
8
- Zero-dependency CommonMark 0.31.2 and GFM parsing, editing and transformation expressed as Effect schemas and pure functions. Parse markdown into mdast-shaped nodes carrying byte offsets, navigate headings, sections and links, compute surgical offset-splice edits, normalize markers, decode frontmatter into a validated domain schema, project to and from plain mdast for the remark ecosystem, and walk a document as a `Stream`.
8
+ CommonMark 0.31.2 and GFM parsing, editing and transformation expressed as Effect schemas and pure functions. Parse markdown into mdast-shaped nodes carrying byte offsets, navigate headings, sections and links, compute surgical offset-splice edits, normalize markers, decode frontmatter into a validated domain schema, project to and from plain mdast for the remark ecosystem, and walk a document as a `Stream`.
9
9
 
10
10
  > **Pre-`1.0.0`.** This package is part of the `@effected/*` kit, built on stable
11
11
  > Effect v4 (`effect` `^4.0.0`) and still in `0.x` development. Stable Effect
@@ -41,7 +41,7 @@ npm install @effected/markdown effect
41
41
  pnpm add @effected/markdown effect
42
42
  ```
43
43
 
44
- Requires Node.js >=24.11.0. `effect` v4 is a peer dependency; the package itself adds no runtime dependencies.
44
+ Requires Node.js >=24.11.0. `effect` v4 is a peer dependency; the package's one runtime dependency is the dependency-free `@effected/source`, whose `LineIndex` it re-exports.
45
45
 
46
46
  `@effected/yaml`, `@effected/toml` and `@effected/jsonc` are **optional** peers, required only by the frontmatter codec module you import. A consumer decoding yaml frontmatter installs `@effected/yaml` and nothing else; a consumer who never touches frontmatter installs none of them.
47
47
 
package/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { LineBreaks, LineIndex, LineIndexOptions, SourceDiagnostic, SourceIndex, SourceIndex as SourceIndex$1, SourceLocation, SourcePosition, SourceRange, SourceSpan } from "@effected/source";
1
2
  import { Data, Effect, Option, Result, Schema, Stream } from "effect";
2
3
  declare const Point_base: Schema.Class<Point, Schema.Struct<{
3
4
  readonly line: Schema.Number;
@@ -968,14 +969,20 @@ declare const MarkdownDiagnostic_base: Schema.Class<MarkdownDiagnostic, Schema.S
968
969
  * zero-based `line`/`character`).
969
970
  *
970
971
  * @remarks
971
- * The five-field positional core (`code`/`offset`/`length`/`line`/`character`)
972
- * is structurally identical to `@effected/toml`'s `TomlDiagnostic` (and, by
973
- * the same cross-package contract, `@effected/jsonc`'s parse-error detail
974
- * shape and `@effected/yaml`'s `YamlDiagnostic`); `message` is this
975
- * package's additive extra. `line`/`character` here are zero-based to match
976
- * that contract — a different numbering from the one-based `line`/`column`
977
- * unist `Point`s carried on `MarkdownNode` positions, which is a deliberate,
978
- * unrelated convention for the AST rather than a mismatch to reconcile.
972
+ * Satisfies `@effected/source`'s `SourceDiagnostic<MarkdownParseErrorCode>`:
973
+ * the positional core (`code`/`message`/`offset`/`length`/`line`/`character`)
974
+ * the kit's format packages share, so a consumer printing or serving
975
+ * diagnostics (an LSP server, a CLI annotation) is written once against that
976
+ * shape. `line`/`character` are zero-based, `character` in UTF-16 code units,
977
+ * with `\n`, `\r\n` and a bare `\r` as line breaks: exactly
978
+ * `LineIndex.make(source).positionAt(offset)`, and the same index
979
+ * {@link MarkdownDocument.lineIndex} returns. An offset at the `\n` of a
980
+ * `\r\n` reads as the end of its line.
981
+ *
982
+ * The one-based `line`/`column` unist `Point`s carried on `MarkdownNode`
983
+ * positions read the same index, one-based: a deliberate, different
984
+ * convention for the AST rather than a mismatch to reconcile (see
985
+ * {@link MarkdownDocument.lineIndex} for the exact mapping).
979
986
  *
980
987
  * @public
981
988
  */
@@ -985,11 +992,19 @@ export declare class MarkdownDiagnostic extends MarkdownDiagnostic_base {
985
992
  * from `offset` against the source text. Advanced — the parse entry
986
993
  * points call this for you.
987
994
  *
988
- * @param source - The source text the record's `offset` indexes into.
995
+ * @remarks
996
+ * Pass a line index already built over the source to materialize many
997
+ * records without rescanning the text; a string builds one. The
998
+ * parameter is the structural `SourceIndex`, so an index from any
999
+ * installed copy of `@effected/source` is accepted. An offset
1000
+ * outside the source clamps into it, as `LineIndex.positionAt` does.
1001
+ *
1002
+ * @param source - The source text the record's `offset` indexes into, or
1003
+ * a `LineIndex` over it.
989
1004
  * @param raw - The engine record: `code`, `message`, `offset` and `length`.
990
1005
  * @returns The diagnostic with `line` and `character` filled in.
991
1006
  */
992
- static fromRaw(source: string, raw: {
1007
+ static fromRaw(source: string | SourceIndex$1, raw: {
993
1008
  readonly code: MarkdownParseErrorCode;
994
1009
  readonly message: string;
995
1010
  readonly offset: number;
@@ -1653,6 +1668,49 @@ export declare class MarkdownDocument extends MarkdownDocument_base {
1653
1668
  * checking repeatedly.
1654
1669
  */
1655
1670
  get hasFrontmatterBlock(): boolean;
1671
+ /**
1672
+ * The line index over `source`: offsets to zero-based `{ line, character }`
1673
+ * and back, with exactly the rules this package's own positions use.
1674
+ *
1675
+ * @remarks
1676
+ * A `LineIndex` from `@effected/source` (re-exported from this package's
1677
+ * entry point), typed as the structural `SourceIndex` so a consumer on
1678
+ * another installed copy of `@effected/source` can pass it on, with its
1679
+ * default `"lsp"` line breaks: `\n`, `\r\n` and a
1680
+ * bare `\r`, `character` in UTF-16 code units, an offset at the `\n` of a
1681
+ * `\r\n` reading as the end of its line. So for any offset into
1682
+ * `source`:
1683
+ *
1684
+ * - `doc.lineIndex.positionAt(offset)` is the `line`/`character` a
1685
+ * {@link MarkdownDiagnostic} at that offset carries;
1686
+ * - a node's unist `Point` has `line` = `lineAt(offset) + 1` and
1687
+ * `column` = `offset - lineStart(line) + 1`: `positionAt` plus one on
1688
+ * both axes everywhere except at the `\n` of a `\r\n`, which a Point
1689
+ * counts as one column past the `\r` (as `vfile-location` does) and
1690
+ * `positionAt` reads as the end of its line;
1691
+ * - `offsetAt`, `spanOf` and `rangeOf` convert an LSP `Position` or
1692
+ * `Range` back to an offset or a `MarkdownRange`-shaped
1693
+ * `{ offset, length }`.
1694
+ *
1695
+ * A parsed document returns the index the parse itself built; a document
1696
+ * constructed with `make` builds one on first access. Either way it is
1697
+ * built once per document and shared by every later access. To index a
1698
+ * text with no document, `LineIndex.make(text)` gives the same answers.
1699
+ *
1700
+ * @example
1701
+ * ```ts
1702
+ * import { MarkdownDocument } from "@effected/markdown";
1703
+ * import { Result } from "effect";
1704
+ *
1705
+ * const result = MarkdownDocument.parseResult("# Title\r\n\r\nBody\n");
1706
+ * if (Result.isSuccess(result)) {
1707
+ * const index = result.success.lineIndex;
1708
+ * console.log(index.positionAt(12)); // => { line: 2, character: 1 }
1709
+ * console.log(index.offsetAt({ line: 2, character: 0 })); // => 11
1710
+ * }
1711
+ * ```
1712
+ */
1713
+ get lineIndex(): SourceIndex$1;
1656
1714
  /**
1657
1715
  * Every heading in the document, in document order, wherever it sits —
1658
1716
  * including headings nested inside blockquotes and list items.
@@ -3004,120 +3062,22 @@ export type MarkdownVisitorEvent = Data.TaggedEnum<{
3004
3062
  *
3005
3063
  * @public
3006
3064
  */
3007
- export declare const MarkdownVisitorEvent: {
3008
- readonly $is: <Tag extends "Enter" | "Error" | "Exit">(tag: Tag) => (u: unknown) => u is Extract<{
3009
- readonly _tag: "Enter";
3010
- readonly node: MarkdownNode;
3011
- readonly path: MarkdownPath;
3012
- readonly depth: number;
3013
- }, {
3014
- readonly _tag: Tag;
3015
- }> | Extract<{
3016
- readonly _tag: "Error";
3017
- readonly diagnostic: MarkdownDiagnostic;
3018
- readonly path: MarkdownPath;
3019
- readonly depth: number;
3020
- }, {
3021
- readonly _tag: Tag;
3022
- }> | Extract<{
3023
- readonly _tag: "Exit";
3024
- readonly node: MarkdownNode;
3025
- readonly path: MarkdownPath;
3026
- readonly depth: number;
3027
- }, {
3028
- readonly _tag: Tag;
3029
- }>;
3030
- readonly $match: {
3031
- <Cases extends {
3032
- readonly Enter: (args: {
3033
- readonly _tag: "Enter";
3034
- readonly node: MarkdownNode;
3035
- readonly path: MarkdownPath;
3036
- readonly depth: number;
3037
- }) => any;
3038
- readonly Error: (args: {
3039
- readonly _tag: "Error";
3040
- readonly diagnostic: MarkdownDiagnostic;
3041
- readonly path: MarkdownPath;
3042
- readonly depth: number;
3043
- }) => any;
3044
- readonly Exit: (args: {
3045
- readonly _tag: "Exit";
3046
- readonly node: MarkdownNode;
3047
- readonly path: MarkdownPath;
3048
- readonly depth: number;
3049
- }) => any;
3050
- }>(cases: Cases): (value: {
3051
- readonly _tag: "Enter";
3052
- readonly node: MarkdownNode;
3053
- readonly path: MarkdownPath;
3054
- readonly depth: number;
3055
- } | {
3056
- readonly _tag: "Error";
3057
- readonly diagnostic: MarkdownDiagnostic;
3058
- readonly path: MarkdownPath;
3059
- readonly depth: number;
3060
- } | {
3061
- readonly _tag: "Exit";
3062
- readonly node: MarkdownNode;
3063
- readonly path: MarkdownPath;
3064
- readonly depth: number;
3065
- }) => import("effect/Unify").Unify<ReturnType<Cases["Enter" | "Error" | "Exit"]>>;
3066
- <Cases extends {
3067
- readonly Enter: (args: {
3068
- readonly _tag: "Enter";
3069
- readonly node: MarkdownNode;
3070
- readonly path: MarkdownPath;
3071
- readonly depth: number;
3072
- }) => any;
3073
- readonly Error: (args: {
3074
- readonly _tag: "Error";
3075
- readonly diagnostic: MarkdownDiagnostic;
3076
- readonly path: MarkdownPath;
3077
- readonly depth: number;
3078
- }) => any;
3079
- readonly Exit: (args: {
3080
- readonly _tag: "Exit";
3081
- readonly node: MarkdownNode;
3082
- readonly path: MarkdownPath;
3083
- readonly depth: number;
3084
- }) => any;
3085
- }>(value: {
3086
- readonly _tag: "Enter";
3087
- readonly node: MarkdownNode;
3088
- readonly path: MarkdownPath;
3089
- readonly depth: number;
3090
- } | {
3091
- readonly _tag: "Error";
3092
- readonly diagnostic: MarkdownDiagnostic;
3093
- readonly path: MarkdownPath;
3094
- readonly depth: number;
3095
- } | {
3096
- readonly _tag: "Exit";
3097
- readonly node: MarkdownNode;
3098
- readonly path: MarkdownPath;
3099
- readonly depth: number;
3100
- }, cases: Cases): import("effect/Unify").Unify<ReturnType<Cases["Enter" | "Error" | "Exit"]>>;
3101
- };
3102
- readonly Enter: Data.TaggedEnum.ConstructorFrom<{
3103
- readonly _tag: "Enter";
3104
- readonly node: MarkdownNode;
3105
- readonly path: MarkdownPath;
3106
- readonly depth: number;
3107
- }, "_tag">;
3108
- readonly Error: Data.TaggedEnum.ConstructorFrom<{
3109
- readonly _tag: "Error";
3110
- readonly diagnostic: MarkdownDiagnostic;
3111
- readonly path: MarkdownPath;
3112
- readonly depth: number;
3113
- }, "_tag">;
3114
- readonly Exit: Data.TaggedEnum.ConstructorFrom<{
3115
- readonly _tag: "Exit";
3116
- readonly node: MarkdownNode;
3117
- readonly path: MarkdownPath;
3118
- readonly depth: number;
3119
- }, "_tag">;
3120
- };
3065
+ export declare const MarkdownVisitorEvent: Data.TaggedEnum.Constructor<{
3066
+ readonly _tag: "Enter";
3067
+ readonly node: MarkdownNode;
3068
+ readonly path: MarkdownPath;
3069
+ readonly depth: number;
3070
+ } | {
3071
+ readonly _tag: "Error";
3072
+ readonly diagnostic: MarkdownDiagnostic;
3073
+ readonly path: MarkdownPath;
3074
+ readonly depth: number;
3075
+ } | {
3076
+ readonly _tag: "Exit";
3077
+ readonly node: MarkdownNode;
3078
+ readonly path: MarkdownPath;
3079
+ readonly depth: number;
3080
+ }>;
3121
3081
  /**
3122
3082
  * Walks a parsed markdown tree as a lazy sequence of `Enter`/`Exit` events in
3123
3083
  * document pre-order — synchronously with {@link MarkdownVisitor.walk}, or as
@@ -3443,5 +3403,5 @@ export declare const TomlFrontmatter: FrontmatterCodec;
3443
3403
  * @public
3444
3404
  */
3445
3405
  export declare const YamlFrontmatter: FrontmatterCodec;
3446
- export type { DocumentHeading, DocumentLink, FrontmatterCodec, FrontmatterResolveError, FrontmatterSchemaError, FrontmatterSchemaResolver, FrontmatterWriteError, LinkBearingNode, MarkdownNodeOfType, MarkdownNodeType, MarkdownPath, MarkdownPhrasingOnlyType, MarkdownRangeLike, MarkdownSegment, MarkdownStringifyOptions, MarkdownTransformHandlers, MarkdownTransformResult, MdastNode, MdastToTextOptions, SectionHeadingMatch, SectionQueryOptions, SectionSpan, SectionSpanOptions, ShiftHeadingsOptions };
3406
+ export { type DocumentHeading, type DocumentLink, type FrontmatterCodec, type FrontmatterResolveError, type FrontmatterSchemaError, type FrontmatterSchemaResolver, type FrontmatterWriteError, type LineBreaks, LineIndex, type LineIndexOptions, type LinkBearingNode, type MarkdownNodeOfType, type MarkdownNodeType, type MarkdownPath, type MarkdownPhrasingOnlyType, type MarkdownRangeLike, type MarkdownSegment, type MarkdownStringifyOptions, type MarkdownTransformHandlers, type MarkdownTransformResult, type MdastNode, type MdastToTextOptions, type SectionHeadingMatch, type SectionQueryOptions, type SectionSpan, type SectionSpanOptions, type ShiftHeadingsOptions, type SourceDiagnostic, type SourceIndex, type SourceLocation, type SourcePosition, type SourceRange, type SourceSpan };
3447
3407
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -13,5 +13,6 @@ import { MarkdownVisitor, MarkdownVisitorEvent } from "./MarkdownVisitor.js";
13
13
  import { Mdast, MdastDecodeError } from "./Mdast.js";
14
14
  import { TomlFrontmatter } from "./TomlFrontmatter.js";
15
15
  import { YamlFrontmatter } from "./YamlFrontmatter.js";
16
+ import { LineIndex } from "@effected/source";
16
17
 
17
- export { Blockquote, Break, BreakStyle, BulletChar, Code, CodeBlockStyle, Definition, Delete, DocumentSection, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, FrontmatterContent, FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormat, FrontmatterFormatMismatchError, FrontmatterMissingError, FrontmatterMissingReason, FrontmatterNewline, FrontmatterSource, FrontmatterSourceBlock, FrontmatterSourceSplit, FrontmatterValidationError, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, JsonFrontmatter, Link, LinkReference, List, ListContent, ListDelimiter, ListItem, Markdown, MarkdownDiagnostic, MarkdownDialect, MarkdownDocument, MarkdownEdit, MarkdownFormat, MarkdownFormattingOptions, MarkdownFrontmatter, MarkdownModificationError, MarkdownModificationErrorCode, MarkdownNode, MarkdownParseError, MarkdownParseErrorCode, MarkdownParseOptions, MarkdownRange, MarkdownStringifyError, MarkdownTransform, MarkdownVisitor, MarkdownVisitorEvent, Mdast, MdastDecodeError, MdxFlowExpression, MdxJsxAttribute, MdxJsxAttributeContent, MdxJsxAttributeValueExpression, MdxJsxExpressionAttribute, MdxJsxFlowElement, MdxJsxTextElement, MdxTextExpression, MdxjsEsm, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar, TomlFrontmatter, YamlFrontmatter };
18
+ export { Blockquote, Break, BreakStyle, BulletChar, Code, CodeBlockStyle, Definition, Delete, DocumentSection, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, FrontmatterContent, FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormat, FrontmatterFormatMismatchError, FrontmatterMissingError, FrontmatterMissingReason, FrontmatterNewline, FrontmatterSource, FrontmatterSourceBlock, FrontmatterSourceSplit, FrontmatterValidationError, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, JsonFrontmatter, LineIndex, Link, LinkReference, List, ListContent, ListDelimiter, ListItem, Markdown, MarkdownDiagnostic, MarkdownDialect, MarkdownDocument, MarkdownEdit, MarkdownFormat, MarkdownFormattingOptions, MarkdownFrontmatter, MarkdownModificationError, MarkdownModificationErrorCode, MarkdownNode, MarkdownParseError, MarkdownParseErrorCode, MarkdownParseOptions, MarkdownRange, MarkdownStringifyError, MarkdownTransform, MarkdownVisitor, MarkdownVisitorEvent, Mdast, MdastDecodeError, MdxFlowExpression, MdxJsxAttribute, MdxJsxAttributeContent, MdxJsxAttributeValueExpression, MdxJsxExpressionAttribute, MdxJsxFlowElement, MdxJsxTextElement, MdxTextExpression, MdxjsEsm, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar, TomlFrontmatter, YamlFrontmatter };
@@ -1,12 +1,13 @@
1
- import { Frontmatter, Point, Position, Root } from "../MarkdownNode.js";
1
+ import { Frontmatter, Position, Root } from "../MarkdownNode.js";
2
2
  import { scanFrontmatter } from "./blocks/frontmatter.js";
3
3
  import { makeBlockNode } from "./blockTypes.js";
4
4
  import { columnsToNextTabStop, preprocessLines } from "./preprocess.js";
5
5
  import { isHtmlBlockEnd } from "./blocks/htmlBlock.js";
6
6
  import { blockDialect } from "./blockRegistry.js";
7
7
  import { GuardExceeded } from "./carriers.js";
8
- import { LineIndex } from "./lineIndex.js";
8
+ import { pointAt } from "./point.js";
9
9
  import { prepareInline } from "./rawInline.js";
10
+ import { LineIndex } from "@effected/source";
10
11
 
11
12
  // src/internal/blockParser.ts
12
13
  /** Upstream's cheap pre-filter: a line that cannot start any block. */
@@ -42,9 +43,7 @@ var BlockParser = class {
42
43
  constructor(text, dialect, dialectName, frontmatter = false) {
43
44
  this.lines = preprocessLines(text);
44
45
  this.frontmatterCapture = frontmatter ? scanFrontmatter(this.lines, text) : null;
45
- const starts = this.lines.map((line) => line.start);
46
- if (text.endsWith("\n")) starts.push(text.length);
47
- this.lineIndex = LineIndex.fromLineStarts(text, starts);
46
+ this.lineIndex = LineIndex.make(text);
48
47
  this.sourceLength = text.length;
49
48
  this.dialect = dialect;
50
49
  this.dialectName = dialectName;
@@ -306,19 +305,9 @@ var BlockParser = class {
306
305
  position(startOffset, endOffset) {
307
306
  const start = Math.min(Math.max(startOffset, 0), this.sourceLength);
308
307
  const end = Math.min(Math.max(endOffset, start), this.sourceLength);
309
- const startPoint = this.lineIndex.positionAt(start);
310
- const endPoint = this.lineIndex.positionAt(end);
311
308
  return Position.make({
312
- start: Point.make({
313
- line: startPoint.line,
314
- column: startPoint.column,
315
- offset: start
316
- }),
317
- end: Point.make({
318
- line: endPoint.line,
319
- column: endPoint.column,
320
- offset: end
321
- })
309
+ start: pointAt(this.lineIndex, start),
310
+ end: pointAt(this.lineIndex, end)
322
311
  });
323
312
  }
324
313
  /**
@@ -402,7 +391,8 @@ var BlockParser = class {
402
391
  rawInlines,
403
392
  carriers: [],
404
393
  refmap,
405
- footnoteLabels
394
+ footnoteLabels,
395
+ lineIndex: this.lineIndex
406
396
  };
407
397
  }
408
398
  };
@@ -1,8 +1,9 @@
1
- import { Point, Position } from "../MarkdownNode.js";
1
+ import { Position } from "../MarkdownNode.js";
2
2
  import { preprocessLines } from "./preprocess.js";
3
- import { LineIndex } from "./lineIndex.js";
3
+ import { pointAt } from "./point.js";
4
4
  import { parseInlines } from "./inlineParser.js";
5
5
  import { trimWithSegments } from "./rawInline.js";
6
+ import { LineIndex } from "@effected/source";
6
7
 
7
8
  // src/internal/phrasing.ts
8
9
  const EMPTY_REFMAP = /* @__PURE__ */ new Map();
@@ -26,24 +27,14 @@ const parsePhrasingText = (text, dialect) => {
26
27
  if (trimmed.text.length === 0) return [];
27
28
  const first = trimmed.segments[0];
28
29
  const startOffset = first === void 0 ? 0 : first.sourceOffset;
29
- const lineIndex = LineIndex.fromLineStarts(text, lines.map((line) => line.start));
30
+ const lineIndex = LineIndex.make(text);
30
31
  const sourceLength = text.length;
31
32
  const positionOf = (start, end) => {
32
33
  const clampedStart = Math.min(Math.max(start, 0), sourceLength);
33
34
  const clampedEnd = Math.min(Math.max(end, clampedStart), sourceLength);
34
- const startPoint = lineIndex.positionAt(clampedStart);
35
- const endPoint = lineIndex.positionAt(clampedEnd);
36
35
  return Position.make({
37
- start: Point.make({
38
- line: startPoint.line,
39
- column: startPoint.column,
40
- offset: clampedStart
41
- }),
42
- end: Point.make({
43
- line: endPoint.line,
44
- column: endPoint.column,
45
- offset: clampedEnd
46
- })
36
+ start: pointAt(lineIndex, clampedStart),
37
+ end: pointAt(lineIndex, clampedEnd)
47
38
  });
48
39
  };
49
40
  return parseInlines({
@@ -0,0 +1,32 @@
1
+ import { Point } from "../MarkdownNode.js";
2
+
3
+ // src/internal/point.ts
4
+ /**
5
+ * The unist `Point` of `offset` within `index`'s text: one-based `line` and
6
+ * `column`, plus the offset itself.
7
+ *
8
+ * @remarks
9
+ * The caller clamps `offset` into `[0, text.length]` first, so `offset` is
10
+ * always the point's own offset.
11
+ *
12
+ * The column is `offset - lineStart(line) + 1`, NOT `positionAt(offset)`
13
+ * plus one. The two differ at exactly one offset class, the `\n` of a
14
+ * `\r\n`: `positionAt` reads it as the end of its line (LSP cannot name the
15
+ * middle of a terminator), while a unist Point counts it as one column past
16
+ * the `\r`, as `vfile-location` and `mdast-util-from-markdown` do. Node
17
+ * positions DO land there: an empty list item before a CRLF ends at its `\n`
18
+ * (`"*\r\na"` ends the item at offset 2, line 1, column 3), so the
19
+ * `positionAt` form would change published positions.
20
+ *
21
+ * @internal
22
+ */
23
+ const pointAt = (index, offset) => {
24
+ const line = index.lineAt(offset);
25
+ return Point.make({
26
+ line: line + 1,
27
+ column: offset - index.lineStart(line) + 1,
28
+ offset
29
+ });
30
+ };
31
+
32
+ export { pointAt };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/markdown",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "private": false,
5
5
  "description": "CommonMark and GFM markdown parse, edit and transform schemas for Effect",
6
6
  "keywords": [
@@ -38,10 +38,13 @@
38
38
  },
39
39
  "./package.json": "./package.json"
40
40
  },
41
+ "dependencies": {
42
+ "@effected/source": "^0.1.0"
43
+ },
41
44
  "peerDependencies": {
42
- "@effected/jsonc": "^0.20.0",
43
- "@effected/toml": "^0.16.0",
44
- "@effected/yaml": "^0.25.1",
45
+ "@effected/jsonc": "^0.21.0",
46
+ "@effected/toml": "^0.17.0",
47
+ "@effected/yaml": "^0.26.0",
45
48
  "effect": "^4.0.0"
46
49
  },
47
50
  "peerDependenciesMeta": {
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.59.1"
8
+ "packageVersion": "7.59.4"
9
9
  }
10
10
  ]
11
11
  }
@@ -1,75 +0,0 @@
1
- // src/internal/lineIndex.ts
2
- /**
3
- * A precomputed index of line-start offsets over a fixed source text,
4
- * answering `positionAt(offset)` in `O(log n)` via binary search rather than
5
- * rescanning the text per query.
6
- *
7
- * @remarks
8
- * Only `\n` begins a new line. A `\r` immediately before a `\n` (CRLF) is
9
- * left as the trailing character of the prior line — the pair still reads as
10
- * a single boundary because the following `\n` is what advances the line
11
- * counter, not the `\r`. A bare `\r` (old Mac line endings) is not
12
- * recognized as a break; CommonMark's preprocessing pass normalizes those
13
- * before this index is ever built.
14
- */
15
- var LineIndex = class LineIndex {
16
- text;
17
- lineStarts;
18
- constructor(text, lineStarts) {
19
- this.text = text;
20
- this.lineStarts = lineStarts;
21
- }
22
- /**
23
- * Build a {@link LineIndex} over `text` with one forward scan.
24
- *
25
- * @remarks
26
- * Recognizes only `\n`, which is why the block pass hands over its own
27
- * line table instead — see {@link LineIndex.fromLineStarts}.
28
- */
29
- static make(text) {
30
- const lineStarts = [0];
31
- for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) === 10) lineStarts.push(i + 1);
32
- return new LineIndex(text, lineStarts);
33
- }
34
- /**
35
- * Build a {@link LineIndex} over `text` from a line table someone else
36
- * already computed.
37
- *
38
- * @remarks
39
- * The parser splits lines on `\r\n`, `\n` AND a bare `\r`; this index's
40
- * own scan recognizes only `\n`. For any document without a bare `\r` the
41
- * two agree, but for one with them they disagree about what a line is, and
42
- * every reported line number would be wrong. Handing the parser's own
43
- * table over removes the possibility rather than documenting it: there is
44
- * one definition of a line, and it belongs to the preprocessor.
45
- *
46
- * `starts` must be ascending and begin at 0; a table that is neither is a
47
- * wiring bug and dies as a defect.
48
- */
49
- static fromLineStarts(text, starts) {
50
- if (starts.length === 0 || starts[0] !== 0) throw new TypeError("line index: a line table must be non-empty and start at offset 0");
51
- return new LineIndex(text, starts);
52
- }
53
- /**
54
- * The 1-based `{ line, column }` of `offset` within this index's text.
55
- * Out-of-range offsets clamp to the nearest valid position (`0` or
56
- * `text.length`) rather than throwing.
57
- */
58
- positionAt(offset) {
59
- const clamped = Math.min(Math.max(offset, 0), this.text.length);
60
- let low = 0;
61
- let high = this.lineStarts.length - 1;
62
- while (low < high) {
63
- const mid = low + high + 1 >> 1;
64
- if ((this.lineStarts[mid] ?? 0) <= clamped) low = mid;
65
- else high = mid - 1;
66
- }
67
- const lineStart = this.lineStarts[low] ?? 0;
68
- return {
69
- line: low + 1,
70
- column: clamped - lineStart + 1
71
- };
72
- }
73
- };
74
-
75
- export { LineIndex };