@cosense-toolbox/lsp 0.1.0-beta.2

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-DouAOZnK.mjs","names":["Arr","Arr","Rec","Arr"],"sources":["../src/diagnostics.ts","../src/settings.ts","../src/workspace.ts"],"sourcesContent":["import {\n type NodeOfType,\n normalizeLineEndings,\n parse,\n type ParseOptions,\n} from \"@cosense-toolbox/parser\"\nimport { Array as Arr, Match, Option, pipe, Schema } from \"effect\"\nimport { type Diagnostic, DiagnosticSeverity } from \"vscode-languageserver/node\"\n\nimport { normalizeForMatch } from \"./completion\"\nimport { COMPONENT_LINE, fenceOf } from \"./tokens\"\nimport { branch, gather, leaf } from \"./tree\"\nimport type { Index } from \"./workspace\"\n\n/**\n * Links to pages no file holds.\n *\n * Only `[title]` links are checked. A tag names a tag page more often than a page, and a\n * `[/project/page]` link points outside the workspace; neither is expected to have a file.\n */\n\n/** How loudly an unresolved link is reported, as the reader sets it. */\nexport const UnresolvedSeverity = Schema.Literal(\"off\", \"hint\", \"information\", \"warning\", \"error\")\n\nexport type UnresolvedSeverity = typeof UnresolvedSeverity.Type\n\n/**\n * The severity a setting names, or `warning` for one that is missing or unknown: the build\n * warns about the same links by default (`unresolved: \"warn\"`).\n */\nexport const severityOf = (setting: unknown): UnresolvedSeverity =>\n Option.getOrElse(\n Schema.decodeUnknownOption(UnresolvedSeverity)(setting),\n (): UnresolvedSeverity => \"warning\",\n )\n\n/** The LSP's severity for a setting, or None for `off`. */\nconst lspSeverity = (severity: UnresolvedSeverity): Option.Option<DiagnosticSeverity> =>\n Match.value(severity).pipe(\n Match.when(\"off\", () => Option.none()),\n Match.when(\"hint\", () => Option.some(DiagnosticSeverity.Hint)),\n Match.when(\"information\", () => Option.some(DiagnosticSeverity.Information)),\n Match.when(\"warning\", () => Option.some(DiagnosticSeverity.Warning)),\n Match.when(\"error\", () => Option.some(DiagnosticSeverity.Error)),\n Match.exhaustive,\n )\n\nexport interface UnresolvedLinkOptions {\n readonly severity: UnresolvedSeverity\n /** `.csnx`: a component line is JSX, and a bracket in it is not a link. */\n readonly components?: boolean\n /** How to parse: the site's notation extensions, so `[! 注意]` is not read as a link. */\n readonly parseOptions?: ParseOptions\n /** Whether a `---` fence on the first line opens YAML to skip (default: true). */\n readonly frontmatter?: boolean\n}\n\n/** A `[title]` link, and the line it is on in the file. */\ninterface LinkAt {\n readonly link: NodeOfType<\"internalLink\">\n readonly line: number\n}\n\n/**\n * Every `[title]` link in the body, with the line it is on in the file. The title line holds\n * no links, and neither does a `.csnx` component line.\n */\nconst linksIn = (\n text: string,\n {\n components = false,\n parseOptions = {},\n frontmatter = true,\n }: Omit<UnresolvedLinkOptions, \"severity\">,\n): ReadonlyArray<LinkAt> => {\n const lines = normalizeLineEndings(text).split(\"\\n\")\n // The parser never sees the frontmatter, so its line numbers start after the fence.\n const offset = Option.match(fenceOf(lines, frontmatter), {\n onNone: () => 0,\n onSome: (end) => end + 1,\n })\n const isComponentLine = (line: number) =>\n components && COMPONENT_LINE.test(lines[line + offset] ?? \"\")\n\n return gather(parse(lines.slice(offset).join(\"\\n\"), parseOptions), (node) =>\n Match.value(node).pipe(\n Match.when({ type: \"title\" }, () => leaf<LinkAt>([])),\n Match.when({ type: \"line\" }, (line) =>\n isComponentLine(line.position.start.line) ? leaf<LinkAt>([]) : branch<LinkAt>([]),\n ),\n Match.when({ type: \"internalLink\" }, (link) =>\n branch([{ link, line: link.position.start.line + offset }]),\n ),\n Match.orElse(() => branch<LinkAt>([])),\n ),\n )\n}\n\n/** A diagnostic for each `[title]` link in `text` whose page is not in `index`. */\nexport const unresolvedLinkDiagnostics = (\n index: Index,\n text: string,\n options: UnresolvedLinkOptions,\n): Diagnostic[] => {\n const withFile = new Set(Arr.map(index.pages, (page) => normalizeForMatch(page.title)))\n return pipe(\n lspSeverity(options.severity),\n Option.match({\n onNone: () => [],\n onSome: (severity) =>\n pipe(\n linksIn(text, options),\n Arr.filter(({ link }) => !withFile.has(normalizeForMatch(link.target))),\n Arr.map(({ link, line }): Diagnostic => ({\n range: {\n start: { line, character: link.position.start.column },\n end: { line, character: link.position.end.column },\n },\n severity,\n source: \"cosense\",\n code: \"unresolved-link\",\n message: `リンク先のページが見つからない: [${link.target}]`,\n })),\n ),\n }),\n )\n}\n","import type { ParseOptions } from \"@cosense-toolbox/parser\"\nimport { customDecorations } from \"@cosense-toolbox/parser/extensions\"\nimport { Array as Arr, Option, pipe, Record as Rec, Schema } from \"effect\"\n\nimport { severityOf, type UnresolvedSeverity } from \"./diagnostics\"\n\n/**\n * What the reader can set, through the editor's initialization options:\n *\n * ```json\n * { \"sources\": [\"src\"], \"decorations\": [\"!\"], \"unresolvedLinks\": \"warning\", \"frontmatter\": true }\n * ```\n */\nexport interface Settings {\n /**\n * Where the pages are, relative to each workspace folder. Empty reads the whole folder.\n * A site keeps its pages in a few directories, and the rest of the repository is noise.\n */\n readonly sources: ReadonlyArray<string>\n /**\n * Markers read as text decorations on top of Cosense's own (`* / - _`), as the site's\n * `customDecorations` has them. Without them, `[! 注意]` reads as a link to a page called\n * \"! 注意\".\n */\n readonly decorations: ReadonlyArray<string>\n /** How loudly a link to a missing page is reported. */\n readonly unresolvedLinks: UnresolvedSeverity\n /**\n * Whether a `---` fence on a page's first line opens YAML to skip. Off for Cosense pages,\n * which have no frontmatter: a page titled `---` would otherwise lose its first lines.\n */\n readonly frontmatter: boolean\n}\n\nexport const defaultSettings: Settings = {\n sources: [],\n decorations: [],\n unresolvedLinks: \"warning\",\n frontmatter: true,\n}\n\n/** `schema`'s reading of `value`, or `fallback` when `value` is not of that shape. */\nconst decodeOr =\n <A, I>(schema: Schema.Schema<A, I>, fallback: A) =>\n (value: unknown): A =>\n Option.getOrElse(Schema.decodeUnknownOption(schema)(value), () => fallback)\n\n/** The options as an object, which the editor may send in any shape or not at all. */\nconst OptionsObject = Schema.Record({ key: Schema.String, value: Schema.Unknown })\n\n/**\n * The non-empty strings of a list. Anything else in it is dropped rather than failing the\n * whole list, and anything but a list counts as none.\n */\nconst stringsOf = (value: unknown): ReadonlyArray<string> =>\n Arr.filter(decodeOr(Schema.Array(Schema.Unknown), [])(value), Schema.is(Schema.NonEmptyString))\n\n/**\n * Each setting read on its own, so one of the wrong shape falls back to its default\n * without taking the others with it.\n */\nexport const settingsOf = (options: unknown): Settings => {\n const field = (key: keyof Settings): unknown =>\n pipe(\n Schema.decodeUnknownOption(OptionsObject)(options),\n Option.flatMap(Rec.get(key)),\n Option.getOrUndefined,\n )\n return {\n sources: stringsOf(field(\"sources\")),\n decorations: stringsOf(field(\"decorations\")),\n unresolvedLinks: severityOf(field(\"unresolvedLinks\")),\n frontmatter: decodeOr(Schema.Boolean, defaultSettings.frontmatter)(field(\"frontmatter\")),\n }\n}\n\n/** How the pages are parsed, so the server reads notation as the site's build does. */\nexport const parseOptionsOf = ({ decorations }: Settings): ParseOptions =>\n Arr.match(decorations, {\n onEmpty: (): ParseOptions => ({}),\n onNonEmpty: (markers): ParseOptions => ({ extensions: [customDecorations(markers)] }),\n })\n","import type { Dirent } from \"node:fs\"\nimport { readdir, readFile } from \"node:fs/promises\"\nimport { basename, extname, join, relative, resolve, sep } from \"node:path\"\nimport { fileURLToPath, pathToFileURL } from \"node:url\"\n\nimport { Array as Arr, Effect, Match, Option, pipe } from \"effect\"\n\n/**\n * The pages a workspace holds, read from disk.\n *\n * This is the one place the language server differs from a Cosense client: there is no\n * project and no account, only `.csn` and `.csnx` files. A title is what the file calls\n * itself: its first line.\n */\nexport interface Page {\n readonly title: string\n /** `file://` URI of the page's file. */\n readonly uri: string\n /**\n * Where the file is, from the root it was found under, with `/` between directories. Shown\n * next to the title so two pages of one title can be told apart.\n */\n readonly location: string\n}\n\nexport interface Index {\n /**\n * Every page file, in the order the roots and their directories list them. A link to a\n * page is not a page: only a file is, since only a file has a first line to be its title.\n */\n readonly pages: ReadonlyArray<Page>\n}\n\nexport const emptyIndex: Index = { pages: [] }\n\n// --- Finding the files --------------------------------------------------------------------\n\nconst PAGE_EXTENSIONS: ReadonlyArray<string> = [\".csn\", \".csnx\"]\n\n// Directories that never hold pages and can be very large. Skipped by name, because\n// walking into node_modules is the difference between instant and unusable.\nconst SKIP_DIRECTORIES: ReadonlyArray<string> = [\n \"node_modules\",\n \".git\",\n \"dist\",\n \"build\",\n \".next\",\n \".astro\",\n \".turbo\",\n \".cache\",\n]\n\n/** A directory's entries. One that cannot be read holds nothing, rather than failing the walk. */\nconst entriesOf = (directory: string): Effect.Effect<ReadonlyArray<Dirent>> =>\n pipe(\n Effect.tryPromise(() => readdir(directory, { withFileTypes: true })),\n Effect.orElseSucceed(() => []),\n )\n\n/** The page files one entry of `directory` holds: itself, what is under it, or none. */\nconst filesAt = (directory: string, entry: Dirent): Effect.Effect<ReadonlyArray<string>> =>\n Match.value(entry).pipe(\n Match.when(\n (dir) => dir.isDirectory() && Arr.contains(SKIP_DIRECTORIES, dir.name),\n () => Effect.succeed([]),\n ),\n Match.when(\n (dir) => dir.isDirectory(),\n () => pageFiles(join(directory, entry.name)),\n ),\n Match.when(\n (file) => Arr.contains(PAGE_EXTENSIONS, extname(file.name)),\n () => Effect.succeed([join(directory, entry.name)]),\n ),\n Match.orElse(() => Effect.succeed([])),\n )\n\n/** Every page file under `directory`, depth-first, in the order the directory lists them. */\nconst pageFiles = (directory: string): Effect.Effect<ReadonlyArray<string>> =>\n pipe(\n entriesOf(directory),\n Effect.map(Arr.filter((entry) => !entry.name.startsWith(\".\"))),\n Effect.flatMap(Effect.forEach((entry) => filesAt(directory, entry))),\n Effect.map(Arr.flatten),\n )\n\n// --- Reading one file ---------------------------------------------------------------------\n\n/** `text` past the leading `---` fence, which is YAML rather than a line of the page. */\nconst withoutFrontmatter = (text: string): string =>\n pipe(\n Option.liftPredicate(text, (all) => all.startsWith(\"---\\n\")),\n Option.flatMap((all) =>\n pipe(\n all.indexOf(\"\\n---\", 3),\n Option.liftPredicate((end) => end !== -1),\n Option.map((end) => all.slice(end + 4)),\n ),\n ),\n Option.getOrElse(() => text),\n )\n\nconst firstLine = (text: string): Option.Option<string> =>\n pipe(\n text.split(\"\\n\"),\n Arr.map((line) => line.trim()),\n Arr.findFirst((line) => line !== \"\"),\n )\n\n/**\n * What the file calls itself: its first line.\n *\n * The frontmatter is skipped rather than read. Cosense has no notion of a title held apart\n * from the text — the first line *is* the title, and a link finds a page by it. A `title:`\n * that disagreed with the first line would name a page nobody can see, so it is ignored\n * here even though `metadata.ts` still prefers it.\n *\n * An empty file falls back to its name, so it stays linkable.\n */\nconst titleOf = (path: string, text: string, frontmatter: boolean): string =>\n pipe(\n text.replace(/\\r\\n?/g, \"\\n\"),\n (normalized) =>\n Option.getOrElse(\n Option.map(\n Option.liftPredicate(normalized, () => frontmatter),\n withoutFrontmatter,\n ),\n () => normalized,\n ),\n firstLine,\n Option.getOrElse(() => basename(path, extname(path))),\n )\n\n/** `path` from `root`, with `/` between directories whatever the platform. */\nconst locationOf = (root: string, path: string): string => relative(root, path).split(sep).join(\"/\")\n\n/** How the pages are read. */\nexport interface ReadIndexOptions {\n /**\n * Whether a `---` fence on the first line opens YAML to skip (default: true). Off for\n * Cosense pages, which have no frontmatter: a page titled `---` keeps its title.\n */\n readonly frontmatter?: boolean\n}\n\n/** A page file as read: the page it makes, where it is on disk, and its text. */\nexport interface PageFile {\n readonly page: Page\n readonly path: string\n readonly text: string\n}\n\n/** A file that cannot be read is left out rather than failing the rest. */\nconst readPageFile = (\n root: string,\n path: string,\n { frontmatter = true }: ReadIndexOptions,\n): Effect.Effect<Option.Option<PageFile>> =>\n pipe(\n Effect.tryPromise(() => readFile(path, \"utf8\")),\n Effect.map((text) =>\n Option.some({\n page: {\n title: titleOf(path, text, frontmatter),\n uri: pathToFileURL(path).href,\n location: locationOf(root, path),\n },\n path,\n text,\n }),\n ),\n Effect.orElseSucceed(() => Option.none()),\n )\n\n/** The page files under one root. */\nconst filesUnder =\n (options: ReadIndexOptions) =>\n (root: string): Effect.Effect<ReadonlyArray<PageFile>> =>\n pipe(\n pageFiles(root),\n Effect.flatMap(Effect.forEach((path) => readPageFile(root, path, options))),\n Effect.map(Arr.getSomes),\n )\n\n/**\n * Every page file under `roots`. Roots may overlap (`src` and `src/content`), so a file\n * found twice is kept once, where it was first found.\n */\nexport const readPageFiles = (\n roots: ReadonlyArray<string>,\n options: ReadIndexOptions = {},\n): Effect.Effect<ReadonlyArray<PageFile>> =>\n pipe(\n Effect.forEach(roots, filesUnder(options)),\n Effect.map((found) => Arr.dedupeWith(Arr.flatten(found), (a, b) => a.path === b.path)),\n )\n\n/** The pages of `files`, as an index. */\nexport const indexOf = (files: ReadonlyArray<PageFile>): Index => ({\n pages: Arr.map(files, (file) => file.page),\n})\n\n/** Read every page under `roots` and index it. */\nexport const readIndex = (\n roots: ReadonlyArray<string>,\n options: ReadIndexOptions = {},\n): Effect.Effect<Index> => Effect.map(readPageFiles(roots, options), indexOf)\n\n/**\n * Where to read pages: each workspace folder, or each of `sources` under it when the reader\n * has set them. A folder that is not a `file://` URI has no files to read.\n */\nexport const rootsOf = (\n folders: ReadonlyArray<{ readonly uri: string }> | null | undefined,\n sources: ReadonlyArray<string>,\n): ReadonlyArray<string> =>\n pipe(\n Arr.filterMap(folders ?? [], ({ uri }) => Option.liftThrowable(fileURLToPath)(uri)),\n Arr.flatMap((folder) =>\n Arr.match(sources, {\n onEmpty: () => [folder],\n onNonEmpty: (under) => Arr.map(under, (source) => resolve(folder, source)),\n }),\n ),\n )\n"],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import { Effect } from "effect";
2
+ //#region src/workspace.d.ts
3
+ /**
4
+ * The pages a workspace holds, read from disk.
5
+ *
6
+ * This is the one place the language server differs from a Cosense client: there is no
7
+ * project and no account, only `.csn` and `.csnx` files. A title is what the file calls
8
+ * itself: its first line.
9
+ */
10
+ interface Page {
11
+ readonly title: string;
12
+ /** `file://` URI of the page's file. */
13
+ readonly uri: string;
14
+ /**
15
+ * Where the file is, from the root it was found under, with `/` between directories. Shown
16
+ * next to the title so two pages of one title can be told apart.
17
+ */
18
+ readonly location: string;
19
+ }
20
+ interface Index {
21
+ /**
22
+ * Every page file, in the order the roots and their directories list them. A link to a
23
+ * page is not a page: only a file is, since only a file has a first line to be its title.
24
+ */
25
+ readonly pages: ReadonlyArray<Page>;
26
+ }
27
+ //#endregion
28
+ export { Index as t };
29
+ //# sourceMappingURL=workspace-pdw-fg9B.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-pdw-fg9B.d.mts","names":[],"sources":["../src/workspace.ts"],"mappings":";;;;;;;;;UAciB;WACN;;WAEA;;;;;WAKA;;UAGM;;;;;WAKN,OAAO,cAAc"}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@cosense-toolbox/lsp",
3
+ "version": "0.1.0-beta.2",
4
+ "type": "module",
5
+ "description": "Language server for .csn and .csnx, and a check of a whole site's links: csn-lsp.",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "cosense",
9
+ "scrapbox",
10
+ "lsp",
11
+ "language-server",
12
+ "lint"
13
+ ],
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/qaynam/cosense-toolbox.git",
17
+ "directory": "packages/lsp"
18
+ },
19
+ "bin": {
20
+ "csn-lsp": "./dist/main.mjs"
21
+ },
22
+ "exports": {
23
+ "./check": {
24
+ "types": "./dist/check.d.mts",
25
+ "default": "./dist/check.mjs"
26
+ },
27
+ "./completion": {
28
+ "types": "./dist/completion.d.mts",
29
+ "default": "./dist/completion.mjs"
30
+ },
31
+ "./tokens": {
32
+ "types": "./dist/tokens.d.mts",
33
+ "default": "./dist/tokens.mjs"
34
+ }
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "README.md",
39
+ "LICENSE"
40
+ ],
41
+ "scripts": {
42
+ "build": "tsdown",
43
+ "typecheck": "tsc --noEmit",
44
+ "test": "vitest run",
45
+ "prepublishOnly": "bun run build"
46
+ },
47
+ "dependencies": {
48
+ "@cosense-toolbox/parser": "0.1.0-beta.2",
49
+ "effect": "^3.22.1",
50
+ "vscode-languageserver": "^10.1.0",
51
+ "vscode-languageserver-textdocument": "^1.0.12"
52
+ },
53
+ "devDependencies": {
54
+ "tsdown": "^0.22.14",
55
+ "typescript": "^6.0.0",
56
+ "vitest": "^2.1.9"
57
+ },
58
+ "publishConfig": {
59
+ "access": "public",
60
+ "tag": "beta",
61
+ "registry": "https://registry.npmjs.org/"
62
+ }
63
+ }