@rejot-dev/thalo 0.0.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.
- package/LICENSE +21 -0
- package/README.md +396 -0
- package/dist/ast/ast-types.d.ts +469 -0
- package/dist/ast/ast-types.d.ts.map +1 -0
- package/dist/ast/ast-types.js +11 -0
- package/dist/ast/ast-types.js.map +1 -0
- package/dist/ast/builder.js +158 -0
- package/dist/ast/builder.js.map +1 -0
- package/dist/ast/extract.js +748 -0
- package/dist/ast/extract.js.map +1 -0
- package/dist/ast/node-at-position.d.ts +147 -0
- package/dist/ast/node-at-position.d.ts.map +1 -0
- package/dist/ast/node-at-position.js +382 -0
- package/dist/ast/node-at-position.js.map +1 -0
- package/dist/ast/visitor.js +232 -0
- package/dist/ast/visitor.js.map +1 -0
- package/dist/checker/check.d.ts +53 -0
- package/dist/checker/check.d.ts.map +1 -0
- package/dist/checker/check.js +105 -0
- package/dist/checker/check.js.map +1 -0
- package/dist/checker/rules/actualize-missing-updated.js +34 -0
- package/dist/checker/rules/actualize-missing-updated.js.map +1 -0
- package/dist/checker/rules/actualize-unresolved-target.js +42 -0
- package/dist/checker/rules/actualize-unresolved-target.js.map +1 -0
- package/dist/checker/rules/alter-before-define.js +53 -0
- package/dist/checker/rules/alter-before-define.js.map +1 -0
- package/dist/checker/rules/alter-undefined-entity.js +32 -0
- package/dist/checker/rules/alter-undefined-entity.js.map +1 -0
- package/dist/checker/rules/create-requires-section.js +34 -0
- package/dist/checker/rules/create-requires-section.js.map +1 -0
- package/dist/checker/rules/define-entity-requires-section.js +31 -0
- package/dist/checker/rules/define-entity-requires-section.js.map +1 -0
- package/dist/checker/rules/duplicate-entity-definition.js +37 -0
- package/dist/checker/rules/duplicate-entity-definition.js.map +1 -0
- package/dist/checker/rules/duplicate-field-in-schema.js +38 -0
- package/dist/checker/rules/duplicate-field-in-schema.js.map +1 -0
- package/dist/checker/rules/duplicate-link-id.js +52 -0
- package/dist/checker/rules/duplicate-link-id.js.map +1 -0
- package/dist/checker/rules/duplicate-metadata-key.js +21 -0
- package/dist/checker/rules/duplicate-metadata-key.js.map +1 -0
- package/dist/checker/rules/duplicate-section-heading.js +41 -0
- package/dist/checker/rules/duplicate-section-heading.js.map +1 -0
- package/dist/checker/rules/duplicate-section-in-schema.js +38 -0
- package/dist/checker/rules/duplicate-section-in-schema.js.map +1 -0
- package/dist/checker/rules/duplicate-timestamp.js +104 -0
- package/dist/checker/rules/duplicate-timestamp.js.map +1 -0
- package/dist/checker/rules/empty-required-value.js +45 -0
- package/dist/checker/rules/empty-required-value.js.map +1 -0
- package/dist/checker/rules/empty-section.js +21 -0
- package/dist/checker/rules/empty-section.js.map +1 -0
- package/dist/checker/rules/invalid-date-range-value.js +56 -0
- package/dist/checker/rules/invalid-date-range-value.js.map +1 -0
- package/dist/checker/rules/invalid-default-value.js +86 -0
- package/dist/checker/rules/invalid-default-value.js.map +1 -0
- package/dist/checker/rules/invalid-field-type.js +45 -0
- package/dist/checker/rules/invalid-field-type.js.map +1 -0
- package/dist/checker/rules/missing-required-field.js +48 -0
- package/dist/checker/rules/missing-required-field.js.map +1 -0
- package/dist/checker/rules/missing-required-section.js +51 -0
- package/dist/checker/rules/missing-required-section.js.map +1 -0
- package/dist/checker/rules/missing-title.js +56 -0
- package/dist/checker/rules/missing-title.js.map +1 -0
- package/dist/checker/rules/remove-undefined-field.js +42 -0
- package/dist/checker/rules/remove-undefined-field.js.map +1 -0
- package/dist/checker/rules/remove-undefined-section.js +42 -0
- package/dist/checker/rules/remove-undefined-section.js.map +1 -0
- package/dist/checker/rules/rules.d.ts +71 -0
- package/dist/checker/rules/rules.d.ts.map +1 -0
- package/dist/checker/rules/rules.js +102 -0
- package/dist/checker/rules/rules.js.map +1 -0
- package/dist/checker/rules/synthesis-empty-query.js +35 -0
- package/dist/checker/rules/synthesis-empty-query.js.map +1 -0
- package/dist/checker/rules/synthesis-missing-prompt.js +42 -0
- package/dist/checker/rules/synthesis-missing-prompt.js.map +1 -0
- package/dist/checker/rules/synthesis-missing-sources.js +32 -0
- package/dist/checker/rules/synthesis-missing-sources.js.map +1 -0
- package/dist/checker/rules/synthesis-unknown-query-entity.js +39 -0
- package/dist/checker/rules/synthesis-unknown-query-entity.js.map +1 -0
- package/dist/checker/rules/timestamp-out-of-order.js +55 -0
- package/dist/checker/rules/timestamp-out-of-order.js.map +1 -0
- package/dist/checker/rules/unknown-entity.js +32 -0
- package/dist/checker/rules/unknown-entity.js.map +1 -0
- package/dist/checker/rules/unknown-field.js +40 -0
- package/dist/checker/rules/unknown-field.js.map +1 -0
- package/dist/checker/rules/unknown-section.js +47 -0
- package/dist/checker/rules/unknown-section.js.map +1 -0
- package/dist/checker/rules/unresolved-link.js +34 -0
- package/dist/checker/rules/unresolved-link.js.map +1 -0
- package/dist/checker/rules/update-without-create.js +65 -0
- package/dist/checker/rules/update-without-create.js.map +1 -0
- package/dist/checker/visitor.d.ts +69 -0
- package/dist/checker/visitor.d.ts.map +1 -0
- package/dist/checker/visitor.js +67 -0
- package/dist/checker/visitor.js.map +1 -0
- package/dist/checker/workspace-index.d.ts +50 -0
- package/dist/checker/workspace-index.d.ts.map +1 -0
- package/dist/checker/workspace-index.js +108 -0
- package/dist/checker/workspace-index.js.map +1 -0
- package/dist/commands/actualize.d.ts +113 -0
- package/dist/commands/actualize.d.ts.map +1 -0
- package/dist/commands/actualize.js +111 -0
- package/dist/commands/actualize.js.map +1 -0
- package/dist/commands/check.d.ts +65 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +61 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/format.d.ts +90 -0
- package/dist/commands/format.d.ts.map +1 -0
- package/dist/commands/format.js +80 -0
- package/dist/commands/format.js.map +1 -0
- package/dist/commands/query.d.ts +152 -0
- package/dist/commands/query.d.ts.map +1 -0
- package/dist/commands/query.js +151 -0
- package/dist/commands/query.js.map +1 -0
- package/dist/constants.d.ts +31 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +51 -0
- package/dist/constants.js.map +1 -0
- package/dist/files.d.ts +58 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +103 -0
- package/dist/files.js.map +1 -0
- package/dist/formatters.d.ts +39 -0
- package/dist/formatters.d.ts.map +1 -0
- package/dist/formatters.js +200 -0
- package/dist/formatters.js.map +1 -0
- package/dist/fragment.d.ts +22 -0
- package/dist/fragment.d.ts.map +1 -0
- package/dist/git/git.js +240 -0
- package/dist/git/git.js.map +1 -0
- package/dist/merge/conflict-detector.d.ts +89 -0
- package/dist/merge/conflict-detector.d.ts.map +1 -0
- package/dist/merge/conflict-detector.js +352 -0
- package/dist/merge/conflict-detector.js.map +1 -0
- package/dist/merge/conflict-formatter.js +143 -0
- package/dist/merge/conflict-formatter.js.map +1 -0
- package/dist/merge/driver.d.ts +54 -0
- package/dist/merge/driver.d.ts.map +1 -0
- package/dist/merge/driver.js +112 -0
- package/dist/merge/driver.js.map +1 -0
- package/dist/merge/entry-matcher.d.ts +50 -0
- package/dist/merge/entry-matcher.d.ts.map +1 -0
- package/dist/merge/entry-matcher.js +141 -0
- package/dist/merge/entry-matcher.js.map +1 -0
- package/dist/merge/entry-merger.js +194 -0
- package/dist/merge/entry-merger.js.map +1 -0
- package/dist/merge/merge-result-builder.d.ts +62 -0
- package/dist/merge/merge-result-builder.d.ts.map +1 -0
- package/dist/merge/merge-result-builder.js +89 -0
- package/dist/merge/merge-result-builder.js.map +1 -0
- package/dist/mod.d.ts +31 -0
- package/dist/mod.js +23 -0
- package/dist/model/document.d.ts +134 -0
- package/dist/model/document.d.ts.map +1 -0
- package/dist/model/document.js +275 -0
- package/dist/model/document.js.map +1 -0
- package/dist/model/line-index.d.ts +85 -0
- package/dist/model/line-index.d.ts.map +1 -0
- package/dist/model/line-index.js +159 -0
- package/dist/model/line-index.js.map +1 -0
- package/dist/model/workspace.d.ts +296 -0
- package/dist/model/workspace.d.ts.map +1 -0
- package/dist/model/workspace.js +562 -0
- package/dist/model/workspace.js.map +1 -0
- package/dist/parser.js +27 -0
- package/dist/parser.js.map +1 -0
- package/dist/parser.native.d.ts +51 -0
- package/dist/parser.native.d.ts.map +1 -0
- package/dist/parser.native.js +62 -0
- package/dist/parser.native.js.map +1 -0
- package/dist/parser.shared.d.ts +99 -0
- package/dist/parser.shared.d.ts.map +1 -0
- package/dist/parser.shared.js +124 -0
- package/dist/parser.shared.js.map +1 -0
- package/dist/parser.web.d.ts +67 -0
- package/dist/parser.web.d.ts.map +1 -0
- package/dist/parser.web.js +49 -0
- package/dist/parser.web.js.map +1 -0
- package/dist/schema/registry.d.ts +108 -0
- package/dist/schema/registry.d.ts.map +1 -0
- package/dist/schema/registry.js +281 -0
- package/dist/schema/registry.js.map +1 -0
- package/dist/semantic/analyzer.d.ts +107 -0
- package/dist/semantic/analyzer.d.ts.map +1 -0
- package/dist/semantic/analyzer.js +261 -0
- package/dist/semantic/analyzer.js.map +1 -0
- package/dist/services/change-tracker/change-tracker.d.ts +111 -0
- package/dist/services/change-tracker/change-tracker.d.ts.map +1 -0
- package/dist/services/change-tracker/change-tracker.js +62 -0
- package/dist/services/change-tracker/change-tracker.js.map +1 -0
- package/dist/services/change-tracker/create-tracker.d.ts +42 -0
- package/dist/services/change-tracker/create-tracker.d.ts.map +1 -0
- package/dist/services/change-tracker/create-tracker.js +53 -0
- package/dist/services/change-tracker/create-tracker.js.map +1 -0
- package/dist/services/change-tracker/git-tracker.d.ts +59 -0
- package/dist/services/change-tracker/git-tracker.d.ts.map +1 -0
- package/dist/services/change-tracker/git-tracker.js +218 -0
- package/dist/services/change-tracker/git-tracker.js.map +1 -0
- package/dist/services/change-tracker/timestamp-tracker.d.ts +22 -0
- package/dist/services/change-tracker/timestamp-tracker.d.ts.map +1 -0
- package/dist/services/change-tracker/timestamp-tracker.js +74 -0
- package/dist/services/change-tracker/timestamp-tracker.js.map +1 -0
- package/dist/services/definition.d.ts +37 -0
- package/dist/services/definition.d.ts.map +1 -0
- package/dist/services/definition.js +43 -0
- package/dist/services/definition.js.map +1 -0
- package/dist/services/entity-navigation.d.ts +200 -0
- package/dist/services/entity-navigation.d.ts.map +1 -0
- package/dist/services/entity-navigation.js +211 -0
- package/dist/services/entity-navigation.js.map +1 -0
- package/dist/services/hover.d.ts +81 -0
- package/dist/services/hover.d.ts.map +1 -0
- package/dist/services/hover.js +669 -0
- package/dist/services/hover.js.map +1 -0
- package/dist/services/query.d.ts +116 -0
- package/dist/services/query.d.ts.map +1 -0
- package/dist/services/query.js +225 -0
- package/dist/services/query.js.map +1 -0
- package/dist/services/references.d.ts +52 -0
- package/dist/services/references.d.ts.map +1 -0
- package/dist/services/references.js +66 -0
- package/dist/services/references.js.map +1 -0
- package/dist/services/semantic-tokens.d.ts +54 -0
- package/dist/services/semantic-tokens.d.ts.map +1 -0
- package/dist/services/semantic-tokens.js +213 -0
- package/dist/services/semantic-tokens.js.map +1 -0
- package/dist/services/synthesis.d.ts +90 -0
- package/dist/services/synthesis.d.ts.map +1 -0
- package/dist/services/synthesis.js +113 -0
- package/dist/services/synthesis.js.map +1 -0
- package/dist/source-map.d.ts +42 -0
- package/dist/source-map.d.ts.map +1 -0
- package/dist/source-map.js +170 -0
- package/dist/source-map.js.map +1 -0
- package/package.json +128 -0
- package/tree-sitter-thalo.wasm +0 -0
- package/web-tree-sitter.wasm +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hover.js","names":["lines: string[]","shownMetadata: string[]","title: string","timestamp: string","directiveInfo: string","entries: { entry: Entry; file: string }[]","foundEntry: Entry | undefined","foundFile: string | undefined","ts: Timestamp"],"sources":["../../src/services/hover.ts"],"sourcesContent":["import type { Workspace } from \"../model/workspace.js\";\nimport type { EntitySchema, FieldSchema, SectionSchema } from \"../schema/registry.js\";\nimport { TypeExpr } from \"../schema/registry.js\";\nimport type {\n Location,\n Entry,\n InstanceEntry,\n SchemaEntry,\n SynthesisEntry,\n ActualizeEntry,\n Timestamp,\n} from \"../ast/ast-types.js\";\nimport type { NodeContext } from \"../ast/node-at-position.js\";\nimport { toFileLocation } from \"../source-map.js\";\nimport { formatTimestamp } from \"../formatters.js\";\n\n// ===================\n// Types\n// ===================\n\n/**\n * Result of a hover lookup\n */\nexport interface HoverResult {\n /** Markdown content to display */\n content: string;\n /** Optional range to highlight (file-absolute) */\n range?: Location;\n}\n\n// ===================\n// Entry Formatting\n// ===================\n\n/**\n * Get tags from an entry\n */\nfunction getEntryTags(entry: Entry): string[] {\n switch (entry.type) {\n case \"instance_entry\":\n return entry.header.tags.map((t) => t.name);\n case \"schema_entry\":\n return entry.header.tags.map((t) => t.name);\n case \"synthesis_entry\":\n return entry.header.tags.map((t) => t.name);\n case \"actualize_entry\":\n // Actualize entries don't have tags\n return [];\n }\n}\n\n/**\n * Format an entry for hover display\n */\nexport function formatEntryHover(entry: Entry, file: string): string {\n switch (entry.type) {\n case \"instance_entry\":\n return formatInstanceEntry(entry, file);\n case \"synthesis_entry\":\n return formatSynthesisEntry(entry, file);\n case \"actualize_entry\":\n return formatActualizeEntry(entry, file);\n case \"schema_entry\":\n return formatSchemaEntry(entry, file);\n }\n}\n\n/**\n * Format an instance entry for hover\n */\nexport function formatInstanceEntry(entry: InstanceEntry, file: string): string {\n const lines: string[] = [];\n\n const title = entry.header.title?.value ?? \"(no title)\";\n const timestamp = formatTimestamp(entry.header.timestamp);\n\n // Header\n lines.push(`### ${title}`);\n lines.push(\"\");\n lines.push(`**${entry.header.directive}** ${entry.header.entity} • ${timestamp}`);\n\n // Tags\n const tags = getEntryTags(entry);\n if (tags.length > 0) {\n lines.push(\"\");\n lines.push(`Tags: ${tags.map((t) => `\\`#${t}\\``).join(\" \")}`);\n }\n\n // Link ID\n if (entry.header.link) {\n lines.push(\"\");\n lines.push(`Link: \\`^${entry.header.link.id}\\``);\n }\n\n // Key metadata\n const metadataToShow = [\"subject\", \"type\", \"confidence\", \"status\", \"ref-type\"];\n const shownMetadata: string[] = [];\n\n for (const key of metadataToShow) {\n const meta = entry.metadata.find((m) => m.key.value === key);\n if (meta) {\n shownMetadata.push(`**${key}:** ${meta.value.raw}`);\n }\n }\n\n if (shownMetadata.length > 0) {\n lines.push(\"\");\n lines.push(shownMetadata.join(\" • \"));\n }\n\n // File location\n lines.push(\"\");\n lines.push(`*${file}*`);\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Format a schema entry for hover\n */\nexport function formatSchemaEntry(entry: SchemaEntry, file: string): string {\n const lines: string[] = [];\n\n const title = entry.header.title?.value ?? \"(no title)\";\n const timestamp = formatTimestamp(entry.header.timestamp);\n const entityName = entry.header.entityName.value;\n\n // Header\n lines.push(`### ${title}`);\n lines.push(\"\");\n lines.push(`**${entry.header.directive}** \\`${entityName}\\` • ${timestamp}`);\n\n // Tags\n const tags = getEntryTags(entry);\n if (tags.length > 0) {\n lines.push(\"\");\n lines.push(`Tags: ${tags.map((t) => `\\`#${t}\\``).join(\" \")}`);\n }\n\n // Fields summary\n const fields = entry.metadataBlock?.fields ?? [];\n if (fields.length > 0) {\n lines.push(\"\");\n lines.push(`**Fields:** ${fields.map((f) => `\\`${f.name.value}\\``).join(\", \")}`);\n }\n\n // Sections summary\n const sections = entry.sectionsBlock?.sections ?? [];\n if (sections.length > 0) {\n lines.push(\"\");\n lines.push(`**Sections:** ${sections.map((s) => `\\`${s.name.value}\\``).join(\", \")}`);\n }\n\n // File location\n lines.push(\"\");\n lines.push(`*${file}*`);\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Format a synthesis entry for hover\n */\nexport function formatSynthesisEntry(entry: SynthesisEntry, file: string): string {\n const lines: string[] = [];\n\n const title = entry.header.title?.value ?? \"(no title)\";\n const timestamp = formatTimestamp(entry.header.timestamp);\n\n // Header\n lines.push(`### ${title}`);\n lines.push(\"\");\n lines.push(`**define-synthesis** synthesis • ${timestamp}`);\n\n // Tags\n const tags = getEntryTags(entry);\n if (tags.length > 0) {\n lines.push(\"\");\n lines.push(`Tags: ${tags.map((t) => `\\`#${t}\\``).join(\" \")}`);\n }\n\n // Sources - extract from metadata\n const sourcesMeta = entry.metadata.find((m) => m.key.value === \"sources\");\n if (sourcesMeta && sourcesMeta.value.content.type === \"query_value\") {\n lines.push(\"\");\n lines.push(`**Sources:** ${sourcesMeta.value.content.query.entity}`);\n }\n\n // File location\n lines.push(\"\");\n lines.push(`*${file}*`);\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Format an actualize entry for hover\n */\nexport function formatActualizeEntry(entry: ActualizeEntry, file: string): string {\n const lines: string[] = [];\n\n const timestamp = formatTimestamp(entry.header.timestamp);\n\n // Header\n lines.push(`### actualize-synthesis`);\n lines.push(\"\");\n lines.push(`**actualize-synthesis** ^${entry.header.target.id} • ${timestamp}`);\n\n // Updated timestamp\n const updated = entry.metadata.find((m) => m.key.value === \"updated\");\n if (updated) {\n lines.push(\"\");\n lines.push(`**Updated:** ${updated.value.raw}`);\n }\n\n // File location\n lines.push(\"\");\n lines.push(`*${file}*`);\n\n return lines.join(\"\\n\");\n}\n\n// ===================\n// Directive Documentation\n// ===================\n\n/**\n * Get documentation for a directive\n */\nexport function getDirectiveDocumentation(directive: string): string | null {\n switch (directive) {\n case \"create\":\n return [\n \"### `create` directive\",\n \"\",\n \"Creates a new instance entry.\",\n \"\",\n \"**Syntax:**\",\n \"```\",\n '{timestamp} create {entity} \"Title\" [^link-id] [#tags...]',\n \" {key}: {value}\",\n \" ...\",\n \"```\",\n \"\",\n \"Add an explicit `^link-id` for cross-referencing.\",\n \"\",\n \"**Entities:** `journal`, `lore`, `opinion`, `reference`\",\n ].join(\"\\n\");\n\n case \"update\":\n return [\n \"### `update` directive\",\n \"\",\n \"Updates an existing entry, typically to revise opinions or add information.\",\n \"\",\n \"**Syntax:**\",\n \"```\",\n '{timestamp} update {entity} \"Title\" [^link-id] [#tags...]',\n \" supersedes: ^previous-entry\",\n \" ...\",\n \"```\",\n \"\",\n \"Use `supersedes:` metadata to link to the entry being updated.\",\n \"\",\n \"**Entities:** `journal`, `lore`, `opinion`, `reference`\",\n ].join(\"\\n\");\n\n case \"define-entity\":\n return [\n \"### `define-entity` directive\",\n \"\",\n \"Defines a new entity schema with fields and sections.\",\n \"\",\n \"**Syntax:**\",\n \"```\",\n '{timestamp} define-entity {name} \"Description\"',\n \" # Metadata\",\n ' field-name: type ; \"description\"',\n ' optional-field?: type = default ; \"description\"',\n \" # Sections\",\n ' SectionName ; \"description\"',\n ' OptionalSection? ; \"description\"',\n \"```\",\n \"\",\n '**Field types:** `string`, `datetime`, `daterange`, `number`, `link`, `\"literal\"`, unions (`|`), arrays (`[]`)',\n ].join(\"\\n\");\n\n case \"alter-entity\":\n return [\n \"### `alter-entity` directive\",\n \"\",\n \"Modifies an existing entity schema.\",\n \"\",\n \"**Syntax:**\",\n \"```\",\n '{timestamp} alter-entity {name} \"Description of change\"',\n \" # Metadata\",\n ' new-field: type ; \"add a field\"',\n \" # Remove Metadata\",\n ' old-field ; \"reason for removal\"',\n \" # Sections\",\n ' NewSection ; \"add a section\"',\n \" # Remove Sections\",\n ' OldSection ; \"reason for removal\"',\n \"```\",\n \"\",\n \"All blocks are optional. Only include what you're changing.\",\n ].join(\"\\n\");\n\n case \"define-synthesis\":\n return [\n \"### `define-synthesis` directive\",\n \"\",\n \"Defines a synthesis operation that queries entries and generates content via LLM.\",\n \"\",\n \"**Syntax:**\",\n \"```\",\n '{timestamp} define-synthesis \"Title\" ^link-id [#tags...]',\n \" sources: {entity} where {conditions}\",\n \"\",\n \" # Prompt\",\n \" Instructions for the LLM...\",\n \"```\",\n \"\",\n \"**Query language:**\",\n \"- `{entity} where {conditions}` — Query entries\",\n \"- Conditions: `field = value`, `#tag`, `^link`\",\n \"- Multiple queries: comma-separated\",\n \"\",\n \"**Example:**\",\n \"```\",\n '2026-01-05T10:00 define-synthesis \"Career Summary\" ^career-summary',\n \" sources: lore where subject = ^self and #career\",\n \"\",\n \" # Prompt\",\n \" Write a professional career summary from these lore entries.\",\n \"```\",\n ].join(\"\\n\");\n\n case \"actualize-synthesis\":\n return [\n \"### `actualize-synthesis` directive\",\n \"\",\n \"Triggers a synthesis to regenerate its output based on current data.\",\n \"\",\n \"**Syntax:**\",\n \"```\",\n \"{timestamp} actualize-synthesis ^target-synthesis\",\n \" updated: {timestamp}\",\n \"```\",\n \"\",\n \"**Required metadata:**\",\n \"- `updated:` — Timestamp when the synthesis was actualized\",\n \"\",\n \"**Example:**\",\n \"```\",\n \"2026-01-05T15:30 actualize-synthesis ^career-summary\",\n \" updated: 2026-01-05T15:30\",\n \"```\",\n ].join(\"\\n\");\n\n default:\n return null;\n }\n}\n\n// ===================\n// Entity Schema Formatting\n// ===================\n\n/**\n * Format an entity schema for hover display\n */\nexport function formatEntitySchema(schema: EntitySchema): string {\n const lines: string[] = [];\n\n lines.push(`### Entity: \\`${schema.name}\\``);\n lines.push(\"\");\n lines.push(schema.description);\n lines.push(\"\");\n\n // Fields\n if (schema.fields.size > 0) {\n lines.push(\"**Metadata Fields:**\");\n lines.push(\"\");\n for (const [, field] of schema.fields) {\n const typeStr = TypeExpr.toString(field.type);\n const opt = field.optional ? \"?\" : \"\";\n const desc = field.description ? ` — ${field.description}` : \"\";\n const def = field.defaultValue ? ` (default: \\`${field.defaultValue}\\`)` : \"\";\n lines.push(`- \\`${field.name}${opt}\\`: \\`${typeStr}\\`${def}${desc}`);\n }\n lines.push(\"\");\n }\n\n // Sections\n if (schema.sections.size > 0) {\n lines.push(\"**Sections:**\");\n lines.push(\"\");\n for (const [, section] of schema.sections) {\n const opt = section.optional ? \" (optional)\" : \"\";\n const desc = section.description ? ` — ${section.description}` : \"\";\n lines.push(`- \\`# ${section.name}\\`${opt}${desc}`);\n }\n lines.push(\"\");\n }\n\n lines.push(`*Defined at ${schema.definedAt} in ${schema.definedIn}*`);\n\n return lines.join(\"\\n\");\n}\n\n// ===================\n// Field/Section Formatting\n// ===================\n\n/**\n * Format a field schema for hover display\n */\nexport function formatFieldHover(field: FieldSchema, entityName: string): string {\n const lines: string[] = [];\n\n const typeStr = TypeExpr.toString(field.type);\n const opt = field.optional ? \" (optional)\" : \" (required)\";\n\n lines.push(`### Field: \\`${field.name}\\``);\n lines.push(\"\");\n lines.push(`**Type:** \\`${typeStr}\\`${opt}`);\n\n if (field.defaultValue) {\n lines.push(\"\");\n lines.push(`**Default:** \\`${field.defaultValue}\\``);\n }\n\n if (field.description) {\n lines.push(\"\");\n lines.push(field.description);\n }\n\n lines.push(\"\");\n lines.push(`*From entity \\`${entityName}\\`*`);\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Format a section schema for hover display\n */\nexport function formatSectionHover(section: SectionSchema, entityName: string): string {\n const lines: string[] = [];\n\n const opt = section.optional ? \" (optional)\" : \" (required)\";\n\n lines.push(`### Section: \\`# ${section.name}\\``);\n lines.push(\"\");\n lines.push(`**Status:** ${opt.trim()}`);\n\n if (section.description) {\n lines.push(\"\");\n lines.push(section.description);\n }\n\n lines.push(\"\");\n lines.push(`*From entity \\`${entityName}\\`*`);\n\n return lines.join(\"\\n\");\n}\n\n// ===================\n// Type Documentation\n// ===================\n\n/**\n * Get documentation for a primitive type\n */\nexport function getPrimitiveTypeDocumentation(typeName: string): string | null {\n switch (typeName) {\n case \"string\":\n return [\n \"### Type: `string`\",\n \"\",\n \"Any text value. Can be quoted or unquoted in metadata.\",\n \"\",\n \"**Examples:**\",\n \"```\",\n 'author: \"Jane Doe\"',\n \"author: Jane Doe\",\n \"```\",\n ].join(\"\\n\");\n\n case \"datetime\":\n return [\n \"### Type: `datetime`\",\n \"\",\n \"A date in ISO format (YYYY-MM-DD).\",\n \"\",\n \"**Format:** `YYYY-MM-DD`\",\n \"\",\n \"**Example:**\",\n \"```\",\n \"published: 2024-05-11\",\n \"```\",\n \"\",\n \"Note: For partial dates (YYYY or YYYY-MM), use `daterange` type.\",\n ].join(\"\\n\");\n\n case \"daterange\":\n return [\n \"### Type: `daterange`\",\n \"\",\n \"A range between two dates, using `~` separator.\",\n \"\",\n \"**Format:** `{date} ~ {date}`\",\n \"\",\n \"**Examples:**\",\n \"```\",\n \"date: 2020 ~ 2021\",\n \"date: 2022-05 ~ 2024\",\n \"date: 2024-01-01 ~ 2024-12-31\",\n \"```\",\n ].join(\"\\n\");\n\n case \"link\":\n return [\n \"### Type: `link`\",\n \"\",\n \"A reference to another entry using its link ID.\",\n \"\",\n \"**Format:** `^{link-id}`\",\n \"\",\n \"Link IDs can be:\",\n \"- Timestamps (implicit): `^2026-01-05T15:30`\",\n \"- Explicit IDs: `^opinion-ts-enums`\",\n \"- Special: `^self` (reference to self/author)\",\n \"\",\n \"**Example:**\",\n \"```\",\n \"supersedes: ^2026-01-05T15:30\",\n \"subject: ^self\",\n \"```\",\n ].join(\"\\n\");\n\n case \"number\":\n return [\n \"### Type: `number`\",\n \"\",\n \"A numeric value (integer or decimal).\",\n \"\",\n \"**Examples:**\",\n \"```\",\n \"rating: 5\",\n \"score: 4.5\",\n \"count: -10\",\n \"```\",\n ].join(\"\\n\");\n\n default:\n return null;\n }\n}\n\n// ===================\n// Tag Hover\n// ===================\n\n/**\n * Format tag hover information\n */\nexport function formatTagHover(tag: string, entries: { entry: Entry; file: string }[]): string {\n const lines: string[] = [];\n\n lines.push(`### Tag: \\`#${tag}\\``);\n lines.push(\"\");\n lines.push(`Used in **${entries.length}** ${entries.length === 1 ? \"entry\" : \"entries\"}:`);\n lines.push(\"\");\n\n // Show first few entries\n const toShow = entries.slice(0, 5);\n for (const { entry } of toShow) {\n let title: string;\n let timestamp: string;\n\n switch (entry.type) {\n case \"instance_entry\":\n title = entry.header.title?.value ?? \"(no title)\";\n timestamp = formatTimestamp(entry.header.timestamp);\n break;\n case \"synthesis_entry\":\n title = entry.header.title?.value ?? \"(no title)\";\n timestamp = formatTimestamp(entry.header.timestamp);\n break;\n case \"actualize_entry\":\n title = `actualize-synthesis ^${entry.header.target.id}`;\n timestamp = formatTimestamp(entry.header.timestamp);\n break;\n case \"schema_entry\":\n title = entry.header.title?.value ?? \"(no title)\";\n timestamp = formatTimestamp(entry.header.timestamp);\n break;\n }\n lines.push(`- ${title} *(${timestamp})*`);\n }\n\n if (entries.length > 5) {\n lines.push(`- *...and ${entries.length - 5} more*`);\n }\n\n return lines.join(\"\\n\");\n}\n\n// ===================\n// Timestamp Hover\n// ===================\n\n/**\n * Format timestamp hover\n */\nexport function formatTimestampHover(\n timestamp: string,\n entry: Entry | undefined,\n _file?: string,\n): string {\n const lines: string[] = [];\n\n lines.push(`### Timestamp: \\`${timestamp}\\``);\n lines.push(\"\");\n\n if (entry) {\n lines.push(\"This timestamp identifies an entry:\");\n lines.push(\"\");\n let title: string;\n let directiveInfo: string;\n\n switch (entry.type) {\n case \"instance_entry\":\n title = entry.header.title?.value ?? \"(no title)\";\n directiveInfo = `\\`${entry.header.directive}\\` ${entry.header.entity}`;\n break;\n case \"synthesis_entry\":\n title = entry.header.title?.value ?? \"(no title)\";\n directiveInfo = \"`define-synthesis` synthesis\";\n break;\n case \"actualize_entry\":\n title = `actualize-synthesis ^${entry.header.target.id}`;\n directiveInfo = `\\`actualize-synthesis\\` ^${entry.header.target.id}`;\n break;\n case \"schema_entry\":\n title = entry.header.title?.value ?? \"(no title)\";\n directiveInfo = `\\`${entry.header.directive}\\` ${entry.header.entityName.value}`;\n break;\n }\n\n lines.push(`**${title}**`);\n lines.push(\"\");\n lines.push(directiveInfo);\n lines.push(\"\");\n lines.push(`Reference with: \\`^${timestamp}\\``);\n } else {\n lines.push(\"Entry timestamp.\");\n lines.push(\"\");\n lines.push(\"Add an explicit `^link-id` after the title to enable cross-referencing.\");\n }\n\n return lines.join(\"\\n\");\n}\n\n// ===================\n// Main Hover Service\n// ===================\n\n/**\n * Get hover information for a node context.\n *\n * This function takes a semantic node context (from findNodeAtPosition) and\n * returns hover information including markdown content and an optional range.\n *\n * @param workspace - The workspace to look up entries, schemas, etc.\n * @param context - The node context from findNodeAtPosition\n * @returns Hover result with markdown content, or null if no hover info\n */\nexport function getHoverInfo(workspace: Workspace, context: NodeContext): HoverResult | null {\n switch (context.kind) {\n // Link reference (^link-id)\n case \"link\": {\n const definition = workspace.getLinkDefinition(context.linkId);\n if (definition) {\n return {\n content: formatEntryHover(definition.entry, definition.file),\n range: toFileLocation(context.sourceMap, context.node.location),\n };\n }\n return {\n content: `⚠️ **Unknown link:** \\`^${context.linkId}\\`\\n\\nNo entry found with this ID.`,\n range: toFileLocation(context.sourceMap, context.node.location),\n };\n }\n\n // Tag (#tag)\n case \"tag\": {\n const entries: { entry: Entry; file: string }[] = [];\n for (const model of workspace.allModels()) {\n for (const entry of model.ast.entries) {\n if (getEntryTags(entry).includes(context.tagName)) {\n entries.push({ entry, file: model.file });\n }\n }\n }\n if (entries.length > 0) {\n return {\n content: formatTagHover(context.tagName, entries),\n range: toFileLocation(context.sourceMap, context.node.location),\n };\n }\n return null;\n }\n\n // Directive (create, update, define-entity, alter-entity)\n case \"directive\": {\n const doc = getDirectiveDocumentation(context.directive);\n if (doc) {\n return {\n content: doc,\n range: toFileLocation(context.sourceMap, context.location),\n };\n }\n return null;\n }\n\n // Entity name in instance entries\n case \"entity\": {\n const schema = workspace.schemaRegistry.get(context.entityName);\n if (schema) {\n return {\n content: formatEntitySchema(schema),\n range: toFileLocation(context.sourceMap, context.location),\n };\n }\n return {\n content: `⚠️ **Unknown entity:** \\`${context.entityName}\\`\\n\\nNo schema found. Define with \\`define-entity\\`.`,\n range: toFileLocation(context.sourceMap, context.location),\n };\n }\n\n // Entity name in schema entries\n case \"schema_entity\": {\n const schema = workspace.schemaRegistry.get(context.entityName);\n if (schema) {\n return {\n content: formatEntitySchema(schema),\n range: toFileLocation(context.sourceMap, context.node.location),\n };\n }\n return {\n content: `**Entity:** \\`${context.entityName}\\`\\n\\n*Being defined in this entry*`,\n range: toFileLocation(context.sourceMap, context.node.location),\n };\n }\n\n // Metadata key (field name)\n case \"metadata_key\": {\n if (context.entityContext) {\n const schema = workspace.schemaRegistry.get(context.entityContext);\n if (schema) {\n const field = schema.fields.get(context.key);\n if (field) {\n return {\n content: formatFieldHover(field, context.entityContext),\n range: toFileLocation(context.sourceMap, context.node.location),\n };\n }\n }\n }\n // Field not found in schema\n return {\n content: `**Field:** \\`${context.key}\\`\\n\\n*Not defined in entity schema*`,\n range: toFileLocation(context.sourceMap, context.node.location),\n };\n }\n\n // Timestamp\n case \"timestamp\": {\n // Find entry by timestamp\n let foundEntry: Entry | undefined;\n let foundFile: string | undefined;\n for (const model of workspace.allModels()) {\n for (const entry of model.ast.entries) {\n let ts: Timestamp;\n switch (entry.type) {\n case \"instance_entry\":\n ts = entry.header.timestamp;\n break;\n case \"schema_entry\":\n ts = entry.header.timestamp;\n break;\n case \"synthesis_entry\":\n ts = entry.header.timestamp;\n break;\n case \"actualize_entry\":\n ts = entry.header.timestamp;\n break;\n }\n if (formatTimestamp(ts) === context.value || ts.value === context.value) {\n foundEntry = entry;\n foundFile = model.file;\n break;\n }\n }\n if (foundEntry) {\n break;\n }\n }\n return {\n content: formatTimestampHover(context.value, foundEntry, foundFile),\n range: toFileLocation(context.sourceMap, context.node.location),\n };\n }\n\n // Type expression (string, date, date-range, link)\n case \"type\": {\n const doc = getPrimitiveTypeDocumentation(context.typeName);\n if (doc) {\n return {\n content: doc,\n range: toFileLocation(context.sourceMap, context.node.location),\n };\n }\n return null;\n }\n\n // Section header in content (# SectionName)\n case \"section_header\": {\n if (context.entityContext) {\n const schema = workspace.schemaRegistry.get(context.entityContext);\n if (schema) {\n const section = schema.sections.get(context.sectionName);\n if (section) {\n return {\n content: formatSectionHover(section, context.entityContext),\n range: toFileLocation(context.sourceMap, context.location),\n };\n }\n }\n }\n // Section not found in schema\n return {\n content: `**Section:** \\`# ${context.sectionName}\\`\\n\\n*Not defined in entity schema*`,\n range: toFileLocation(context.sourceMap, context.location),\n };\n }\n\n // Field name in schema definition\n case \"field_name\": {\n if (context.entityContext) {\n const schema = workspace.schemaRegistry.get(context.entityContext);\n if (schema) {\n const field = schema.fields.get(context.fieldName);\n if (field) {\n return {\n content: formatFieldHover(field, context.entityContext),\n range: toFileLocation(context.sourceMap, context.node.location),\n };\n }\n }\n }\n return {\n content: `**Field:** \\`${context.fieldName}\\`\\n\\n*Being defined in this schema*`,\n range: toFileLocation(context.sourceMap, context.node.location),\n };\n }\n\n // Section name in schema definition\n case \"section_name\": {\n if (context.entityContext) {\n const schema = workspace.schemaRegistry.get(context.entityContext);\n if (schema) {\n const section = schema.sections.get(context.sectionName);\n if (section) {\n return {\n content: formatSectionHover(section, context.entityContext),\n range: toFileLocation(context.sourceMap, context.node.location),\n };\n }\n }\n }\n return {\n content: `**Section:** \\`# ${context.sectionName}\\`\\n\\n*Being defined in this schema*`,\n range: toFileLocation(context.sourceMap, context.node.location),\n };\n }\n\n // Title\n case \"title\": {\n return {\n content: `**Title:** \"${context.title}\"`,\n range: toFileLocation(context.sourceMap, context.location),\n };\n }\n\n default:\n return null;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { InstanceEntry, Query } from "../ast/ast-types.js";
|
|
2
|
+
import { Workspace } from "../model/workspace.js";
|
|
3
|
+
|
|
4
|
+
//#region src/services/query.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A parsed query for filtering entries
|
|
8
|
+
* Example: "lore where subject = ^self and #career"
|
|
9
|
+
*/
|
|
10
|
+
interface Query$1 {
|
|
11
|
+
/** The entity type to query (lore, opinion, etc.) */
|
|
12
|
+
entity: string;
|
|
13
|
+
/** Filter conditions (ANDed together) */
|
|
14
|
+
conditions: QueryCondition[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A single condition in a query
|
|
18
|
+
*/
|
|
19
|
+
type QueryCondition = FieldCondition | TagCondition | LinkCondition;
|
|
20
|
+
/**
|
|
21
|
+
* A field equality condition: field = value
|
|
22
|
+
*/
|
|
23
|
+
interface FieldCondition {
|
|
24
|
+
kind: "field";
|
|
25
|
+
field: string;
|
|
26
|
+
value: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A tag condition: #tag (entry must have this tag)
|
|
30
|
+
*/
|
|
31
|
+
interface TagCondition {
|
|
32
|
+
kind: "tag";
|
|
33
|
+
tag: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A link condition: ^link (entry must have this link)
|
|
37
|
+
*/
|
|
38
|
+
interface LinkCondition {
|
|
39
|
+
kind: "link";
|
|
40
|
+
link: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Convert an AST Query to a Model Query.
|
|
44
|
+
* The AST Query comes from parsing, while Model Query is used for execution.
|
|
45
|
+
*/
|
|
46
|
+
declare function astQueryToModelQuery(astQuery: Query): Query$1;
|
|
47
|
+
/**
|
|
48
|
+
* Parse a query string into Query objects.
|
|
49
|
+
* Supports both single queries and comma-separated multiple queries.
|
|
50
|
+
* Uses regex-based parsing to work in both Node.js and browser environments.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* parseQueryString("lore") // [{ entity: "lore", conditions: [] }]
|
|
54
|
+
* parseQueryString("lore where #career") // [{ entity: "lore", conditions: [...] }]
|
|
55
|
+
* parseQueryString("lore, journal") // [{ entity: "lore", ... }, { entity: "journal", ... }]
|
|
56
|
+
* parseQueryString("lore where #career, journal") // [{ entity: "lore", ... }, { entity: "journal", ... }]
|
|
57
|
+
*
|
|
58
|
+
* @returns Array of Query objects, or null if the input is invalid
|
|
59
|
+
*/
|
|
60
|
+
declare function parseQueryString(queryStr: string): Query$1[] | null;
|
|
61
|
+
/**
|
|
62
|
+
* Validate that all query entities exist in the schema registry.
|
|
63
|
+
*
|
|
64
|
+
* @param workspace - The workspace containing the schema registry
|
|
65
|
+
* @param queries - The queries to validate
|
|
66
|
+
* @returns Array of unknown entity names (empty if all are valid)
|
|
67
|
+
*/
|
|
68
|
+
declare function validateQueryEntities(workspace: Workspace, queries: Query$1[]): string[];
|
|
69
|
+
/**
|
|
70
|
+
* Options for executing queries
|
|
71
|
+
*/
|
|
72
|
+
interface QueryOptions {
|
|
73
|
+
/**
|
|
74
|
+
* Only return entries with timestamps after this value.
|
|
75
|
+
* Useful for incremental updates.
|
|
76
|
+
*/
|
|
77
|
+
afterTimestamp?: string | null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check if an entry matches a query.
|
|
81
|
+
* All conditions are ANDed together.
|
|
82
|
+
*
|
|
83
|
+
* @param entry - The entry to check
|
|
84
|
+
* @param query - The query to match against
|
|
85
|
+
* @returns true if the entry matches all conditions
|
|
86
|
+
*/
|
|
87
|
+
declare function entryMatchesQuery(entry: InstanceEntry, query: Query$1): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Execute a single query against the workspace.
|
|
90
|
+
*
|
|
91
|
+
* @param workspace - The workspace to query
|
|
92
|
+
* @param query - The query to execute
|
|
93
|
+
* @param options - Query options
|
|
94
|
+
* @returns Matching entries sorted by timestamp
|
|
95
|
+
*/
|
|
96
|
+
declare function executeQuery(workspace: Workspace, query: Query$1, options?: QueryOptions): InstanceEntry[];
|
|
97
|
+
/**
|
|
98
|
+
* Execute multiple queries against the workspace.
|
|
99
|
+
* An entry is included if it matches ANY of the queries (OR).
|
|
100
|
+
*
|
|
101
|
+
* @param workspace - The workspace to query
|
|
102
|
+
* @param queries - The queries to execute
|
|
103
|
+
* @param options - Query options
|
|
104
|
+
* @returns Matching entries sorted by timestamp (deduplicated)
|
|
105
|
+
*/
|
|
106
|
+
declare function executeQueries(workspace: Workspace, queries: Query$1[], options?: QueryOptions): InstanceEntry[];
|
|
107
|
+
/**
|
|
108
|
+
* Format a query for display.
|
|
109
|
+
*
|
|
110
|
+
* @param query - The query to format
|
|
111
|
+
* @returns Human-readable query string
|
|
112
|
+
*/
|
|
113
|
+
declare function formatQuery(query: Query$1): string;
|
|
114
|
+
//#endregion
|
|
115
|
+
export { FieldCondition, LinkCondition, Query$1 as Query, QueryCondition, QueryOptions, TagCondition, astQueryToModelQuery, entryMatchesQuery, executeQueries, executeQuery, formatQuery, parseQueryString, validateQueryEntities };
|
|
116
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","names":[],"sources":["../../src/services/query.ts"],"sourcesContent":[],"mappings":";;;;;;;AAgBA;AAUA;AAA6B,UAVZ,OAAA,CAUY;EAAiB;EAAe,MAAA,EAAA,MAAA;EAAa;EAKzD,UAAA,EAXH,cAWiB,EAAA;AAS/B;AAQA;AAaA;AAmFA;AAiCgB,KAvJJ,cAAA,GAAiB,cAuJoB,GAvJH,YAuJuB,GAvJR,aAuJa;AAmB1E;AA2EA;AAkBA;AACa,UAnQI,cAAA,CAmQJ;EACJ,IAAA,EAAA,OAAA;EACE,KAAA,EAAA,MAAA;EACR,KAAA,EAAA,MAAA;;AAaH;;;AAGW,UA7QM,YAAA,CA6QN;EACR,IAAA,EAAA,KAAA;EAAa,GAAA,EAAA,MAAA;AAkDhB;;;;UAxTiB,aAAA;;;;;;;;iBAaD,oBAAA,WAA+B,QAAW;;;;;;;;;;;;;;iBAmF1C,gBAAA,oBAAoC;;;;;;;;iBAiCpC,qBAAA,YAAiC,oBAAoB;;;;UAmBpD,YAAA;;;;;;;;;;;;;;;iBA2ED,iBAAA,QAAyB,sBAAsB;;;;;;;;;iBAkB/C,YAAA,YACH,kBACJ,mBACE,eACR;;;;;;;;;;iBAaa,cAAA,YACH,oBACF,qBACA,eACR;;;;;;;iBAkDa,WAAA,QAAmB"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { formatTimestamp } from "../formatters.js";
|
|
2
|
+
|
|
3
|
+
//#region src/services/query.ts
|
|
4
|
+
/**
|
|
5
|
+
* Convert an AST Query to a Model Query.
|
|
6
|
+
* The AST Query comes from parsing, while Model Query is used for execution.
|
|
7
|
+
*/
|
|
8
|
+
function astQueryToModelQuery(astQuery) {
|
|
9
|
+
return {
|
|
10
|
+
entity: astQuery.entity,
|
|
11
|
+
conditions: astQuery.conditions.map((c) => {
|
|
12
|
+
switch (c.type) {
|
|
13
|
+
case "field_condition": return {
|
|
14
|
+
kind: "field",
|
|
15
|
+
field: c.field,
|
|
16
|
+
value: c.value
|
|
17
|
+
};
|
|
18
|
+
case "tag_condition": return {
|
|
19
|
+
kind: "tag",
|
|
20
|
+
tag: c.tag
|
|
21
|
+
};
|
|
22
|
+
case "link_condition": return {
|
|
23
|
+
kind: "link",
|
|
24
|
+
link: c.linkId
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Parse query conditions from the "where" clause string.
|
|
32
|
+
*/
|
|
33
|
+
function parseConditions(conditionsStr) {
|
|
34
|
+
const conditions = [];
|
|
35
|
+
const condParts = conditionsStr.split(/\s+and\s+/i);
|
|
36
|
+
for (const part of condParts) {
|
|
37
|
+
const trimmed = part.trim();
|
|
38
|
+
if (trimmed.startsWith("#")) {
|
|
39
|
+
conditions.push({
|
|
40
|
+
kind: "tag",
|
|
41
|
+
tag: trimmed.slice(1)
|
|
42
|
+
});
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (trimmed.startsWith("^")) {
|
|
46
|
+
conditions.push({
|
|
47
|
+
kind: "link",
|
|
48
|
+
link: trimmed.slice(1)
|
|
49
|
+
});
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const fieldMatch = trimmed.match(/^(\S+)\s*=\s*(.+)$/);
|
|
53
|
+
if (fieldMatch) {
|
|
54
|
+
const [, field, value] = fieldMatch;
|
|
55
|
+
conditions.push({
|
|
56
|
+
kind: "field",
|
|
57
|
+
field,
|
|
58
|
+
value
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return conditions;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Parse a single query from a string.
|
|
66
|
+
* Returns null if the string is not a valid query.
|
|
67
|
+
*/
|
|
68
|
+
function parseSingleQuery(queryStr) {
|
|
69
|
+
const trimmed = queryStr.trim();
|
|
70
|
+
if (!trimmed) return null;
|
|
71
|
+
const queryMatch = trimmed.match(/^([a-z][a-z0-9-]*?)(?:\s+where\s+(.+))?$/i);
|
|
72
|
+
if (!queryMatch) return null;
|
|
73
|
+
const [, entity, conditionsStr] = queryMatch;
|
|
74
|
+
return {
|
|
75
|
+
entity,
|
|
76
|
+
conditions: conditionsStr ? parseConditions(conditionsStr) : []
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Parse a query string into Query objects.
|
|
81
|
+
* Supports both single queries and comma-separated multiple queries.
|
|
82
|
+
* Uses regex-based parsing to work in both Node.js and browser environments.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* parseQueryString("lore") // [{ entity: "lore", conditions: [] }]
|
|
86
|
+
* parseQueryString("lore where #career") // [{ entity: "lore", conditions: [...] }]
|
|
87
|
+
* parseQueryString("lore, journal") // [{ entity: "lore", ... }, { entity: "journal", ... }]
|
|
88
|
+
* parseQueryString("lore where #career, journal") // [{ entity: "lore", ... }, { entity: "journal", ... }]
|
|
89
|
+
*
|
|
90
|
+
* @returns Array of Query objects, or null if the input is invalid
|
|
91
|
+
*/
|
|
92
|
+
function parseQueryString(queryStr) {
|
|
93
|
+
const trimmed = queryStr.trim();
|
|
94
|
+
if (!trimmed) return null;
|
|
95
|
+
const parts = trimmed.split(/\s*,\s*/);
|
|
96
|
+
const queries = [];
|
|
97
|
+
for (const part of parts) {
|
|
98
|
+
const query = parseSingleQuery(part);
|
|
99
|
+
if (!query) return null;
|
|
100
|
+
queries.push(query);
|
|
101
|
+
}
|
|
102
|
+
return queries.length > 0 ? queries : null;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Validate that all query entities exist in the schema registry.
|
|
106
|
+
*
|
|
107
|
+
* @param workspace - The workspace containing the schema registry
|
|
108
|
+
* @param queries - The queries to validate
|
|
109
|
+
* @returns Array of unknown entity names (empty if all are valid)
|
|
110
|
+
*/
|
|
111
|
+
function validateQueryEntities(workspace, queries) {
|
|
112
|
+
const registry = workspace.schemaRegistry;
|
|
113
|
+
const unknown = [];
|
|
114
|
+
for (const query of queries) if (!registry.has(query.entity)) {
|
|
115
|
+
if (!unknown.includes(query.entity)) unknown.push(query.entity);
|
|
116
|
+
}
|
|
117
|
+
return unknown;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get metadata value as string for a given key.
|
|
121
|
+
* Returns the raw value (with quotes for quoted strings) to match query syntax.
|
|
122
|
+
*/
|
|
123
|
+
function getMetadataValue(entry, key) {
|
|
124
|
+
const meta = entry.metadata.find((m) => m.key.value === key);
|
|
125
|
+
if (!meta) return;
|
|
126
|
+
return meta.value.raw;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Check if a single entry matches a query condition.
|
|
130
|
+
*/
|
|
131
|
+
function matchCondition(entry, condition) {
|
|
132
|
+
switch (condition.kind) {
|
|
133
|
+
case "field": return getMetadataValue(entry, condition.field) === condition.value;
|
|
134
|
+
case "tag": return entry.header.tags.some((t) => t.name === condition.tag);
|
|
135
|
+
case "link":
|
|
136
|
+
if (entry.header.link?.id === condition.link) return true;
|
|
137
|
+
for (const meta of entry.metadata) {
|
|
138
|
+
const content = meta.value.content;
|
|
139
|
+
if (content.type === "link_value" && content.link.id === condition.link) return true;
|
|
140
|
+
if (content.type === "value_array") {
|
|
141
|
+
for (const elem of content.elements) if (elem.type === "link" && elem.id === condition.link) return true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Check if an entry matches a query.
|
|
149
|
+
* All conditions are ANDed together.
|
|
150
|
+
*
|
|
151
|
+
* @param entry - The entry to check
|
|
152
|
+
* @param query - The query to match against
|
|
153
|
+
* @returns true if the entry matches all conditions
|
|
154
|
+
*/
|
|
155
|
+
function entryMatchesQuery(entry, query) {
|
|
156
|
+
if (entry.header.entity !== query.entity) return false;
|
|
157
|
+
return query.conditions.every((condition) => matchCondition(entry, condition));
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Execute a single query against the workspace.
|
|
161
|
+
*
|
|
162
|
+
* @param workspace - The workspace to query
|
|
163
|
+
* @param query - The query to execute
|
|
164
|
+
* @param options - Query options
|
|
165
|
+
* @returns Matching entries sorted by timestamp
|
|
166
|
+
*/
|
|
167
|
+
function executeQuery(workspace, query, options = {}) {
|
|
168
|
+
return executeQueries(workspace, [query], options);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Execute multiple queries against the workspace.
|
|
172
|
+
* An entry is included if it matches ANY of the queries (OR).
|
|
173
|
+
*
|
|
174
|
+
* @param workspace - The workspace to query
|
|
175
|
+
* @param queries - The queries to execute
|
|
176
|
+
* @param options - Query options
|
|
177
|
+
* @returns Matching entries sorted by timestamp (deduplicated)
|
|
178
|
+
*/
|
|
179
|
+
function executeQueries(workspace, queries, options = {}) {
|
|
180
|
+
const { afterTimestamp } = options;
|
|
181
|
+
const results = [];
|
|
182
|
+
const seen = /* @__PURE__ */ new Set();
|
|
183
|
+
for (const model of workspace.allModels()) for (const entry of model.ast.entries) {
|
|
184
|
+
if (entry.type !== "instance_entry") continue;
|
|
185
|
+
const timestampStr = formatTimestamp(entry.header.timestamp);
|
|
186
|
+
const key = `${model.file}:${entry.location.startPosition.row}:${entry.location.startPosition.column}`;
|
|
187
|
+
if (seen.has(key)) continue;
|
|
188
|
+
if (afterTimestamp && timestampStr <= afterTimestamp) continue;
|
|
189
|
+
for (const query of queries) if (entryMatchesQuery(entry, query)) {
|
|
190
|
+
results.push({
|
|
191
|
+
entry,
|
|
192
|
+
file: model.file,
|
|
193
|
+
timestampStr
|
|
194
|
+
});
|
|
195
|
+
seen.add(key);
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
results.sort((a, b) => a.timestampStr.localeCompare(b.timestampStr));
|
|
200
|
+
return results.map((r) => r.entry);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Format a query for display.
|
|
204
|
+
*
|
|
205
|
+
* @param query - The query to format
|
|
206
|
+
* @returns Human-readable query string
|
|
207
|
+
*/
|
|
208
|
+
function formatQuery(query) {
|
|
209
|
+
let result = query.entity;
|
|
210
|
+
if (query.conditions.length > 0) {
|
|
211
|
+
const condStrs = query.conditions.map((c) => {
|
|
212
|
+
switch (c.kind) {
|
|
213
|
+
case "field": return `${c.field} = ${c.value}`;
|
|
214
|
+
case "tag": return `#${c.tag}`;
|
|
215
|
+
case "link": return `^${c.link}`;
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
result += ` where ${condStrs.join(" and ")}`;
|
|
219
|
+
}
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
//#endregion
|
|
224
|
+
export { astQueryToModelQuery, entryMatchesQuery, executeQueries, executeQuery, formatQuery, parseQueryString, validateQueryEntities };
|
|
225
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","names":["conditions: QueryCondition[]","queries: Query[]","unknown: string[]","results: QueryResultEntry[]"],"sources":["../../src/services/query.ts"],"sourcesContent":["import type { Workspace } from \"../model/workspace.js\";\nimport type {\n InstanceEntry,\n Query as AstQuery,\n QueryCondition as AstQueryCondition,\n} from \"../ast/ast-types.js\";\nimport { formatTimestamp } from \"../formatters.js\";\n\n// ===================\n// Query Types\n// ===================\n\n/**\n * A parsed query for filtering entries\n * Example: \"lore where subject = ^self and #career\"\n */\nexport interface Query {\n /** The entity type to query (lore, opinion, etc.) */\n entity: string;\n /** Filter conditions (ANDed together) */\n conditions: QueryCondition[];\n}\n\n/**\n * A single condition in a query\n */\nexport type QueryCondition = FieldCondition | TagCondition | LinkCondition;\n\n/**\n * A field equality condition: field = value\n */\nexport interface FieldCondition {\n kind: \"field\";\n field: string;\n value: string;\n}\n\n/**\n * A tag condition: #tag (entry must have this tag)\n */\nexport interface TagCondition {\n kind: \"tag\";\n tag: string;\n}\n\n/**\n * A link condition: ^link (entry must have this link)\n */\nexport interface LinkCondition {\n kind: \"link\";\n link: string;\n}\n\n// ===================\n// Query Parsing\n// ===================\n\n/**\n * Convert an AST Query to a Model Query.\n * The AST Query comes from parsing, while Model Query is used for execution.\n */\nexport function astQueryToModelQuery(astQuery: AstQuery): Query {\n return {\n entity: astQuery.entity,\n conditions: astQuery.conditions.map((c: AstQueryCondition): QueryCondition => {\n switch (c.type) {\n case \"field_condition\":\n return { kind: \"field\", field: c.field, value: c.value };\n case \"tag_condition\":\n return { kind: \"tag\", tag: c.tag };\n case \"link_condition\":\n return { kind: \"link\", link: c.linkId };\n }\n }),\n };\n}\n\n/**\n * Parse query conditions from the \"where\" clause string.\n */\nfunction parseConditions(conditionsStr: string): QueryCondition[] {\n const conditions: QueryCondition[] = [];\n const condParts = conditionsStr.split(/\\s+and\\s+/i);\n\n for (const part of condParts) {\n const trimmed = part.trim();\n\n if (trimmed.startsWith(\"#\")) {\n conditions.push({ kind: \"tag\", tag: trimmed.slice(1) });\n continue;\n }\n\n if (trimmed.startsWith(\"^\")) {\n conditions.push({ kind: \"link\", link: trimmed.slice(1) });\n continue;\n }\n\n const fieldMatch = trimmed.match(/^(\\S+)\\s*=\\s*(.+)$/);\n if (fieldMatch) {\n const [, field, value] = fieldMatch;\n conditions.push({ kind: \"field\", field, value });\n }\n }\n\n return conditions;\n}\n\n/**\n * Parse a single query from a string.\n * Returns null if the string is not a valid query.\n */\nfunction parseSingleQuery(queryStr: string): Query | null {\n const trimmed = queryStr.trim();\n\n if (!trimmed) {\n return null;\n }\n\n // Match: entity [where conditions]\n const queryMatch = trimmed.match(/^([a-z][a-z0-9-]*?)(?:\\s+where\\s+(.+))?$/i);\n\n if (!queryMatch) {\n return null;\n }\n\n const [, entity, conditionsStr] = queryMatch;\n const conditions = conditionsStr ? parseConditions(conditionsStr) : [];\n\n return { entity, conditions };\n}\n\n/**\n * Parse a query string into Query objects.\n * Supports both single queries and comma-separated multiple queries.\n * Uses regex-based parsing to work in both Node.js and browser environments.\n *\n * @example\n * parseQueryString(\"lore\") // [{ entity: \"lore\", conditions: [] }]\n * parseQueryString(\"lore where #career\") // [{ entity: \"lore\", conditions: [...] }]\n * parseQueryString(\"lore, journal\") // [{ entity: \"lore\", ... }, { entity: \"journal\", ... }]\n * parseQueryString(\"lore where #career, journal\") // [{ entity: \"lore\", ... }, { entity: \"journal\", ... }]\n *\n * @returns Array of Query objects, or null if the input is invalid\n */\nexport function parseQueryString(queryStr: string): Query[] | null {\n const trimmed = queryStr.trim();\n\n if (!trimmed) {\n return null;\n }\n\n // Split by comma to support multiple queries\n const parts = trimmed.split(/\\s*,\\s*/);\n const queries: Query[] = [];\n\n for (const part of parts) {\n const query = parseSingleQuery(part);\n if (!query) {\n return null; // If any part is invalid, the whole query is invalid\n }\n queries.push(query);\n }\n\n return queries.length > 0 ? queries : null;\n}\n\n// ===================\n// Query Validation\n// ===================\n\n/**\n * Validate that all query entities exist in the schema registry.\n *\n * @param workspace - The workspace containing the schema registry\n * @param queries - The queries to validate\n * @returns Array of unknown entity names (empty if all are valid)\n */\nexport function validateQueryEntities(workspace: Workspace, queries: Query[]): string[] {\n const registry = workspace.schemaRegistry;\n const unknown: string[] = [];\n\n for (const query of queries) {\n if (!registry.has(query.entity)) {\n // Avoid duplicates\n if (!unknown.includes(query.entity)) {\n unknown.push(query.entity);\n }\n }\n }\n\n return unknown;\n}\n\n/**\n * Options for executing queries\n */\nexport interface QueryOptions {\n /**\n * Only return entries with timestamps after this value.\n * Useful for incremental updates.\n */\n afterTimestamp?: string | null;\n}\n\n/**\n * Result entry with file context for sorting and deduplication\n */\ninterface QueryResultEntry {\n entry: InstanceEntry;\n file: string;\n timestampStr: string;\n}\n\n/**\n * Get metadata value as string for a given key.\n * Returns the raw value (with quotes for quoted strings) to match query syntax.\n */\nfunction getMetadataValue(entry: InstanceEntry, key: string): string | undefined {\n const meta = entry.metadata.find((m) => m.key.value === key);\n if (!meta) {\n return undefined;\n }\n\n // Return the raw value to match query syntax (which includes quotes)\n return meta.value.raw;\n}\n\n/**\n * Check if a single entry matches a query condition.\n */\nfunction matchCondition(entry: InstanceEntry, condition: QueryCondition): boolean {\n switch (condition.kind) {\n case \"field\": {\n const value = getMetadataValue(entry, condition.field);\n return value === condition.value;\n }\n case \"tag\": {\n return entry.header.tags.some((t) => t.name === condition.tag);\n }\n case \"link\": {\n // Check if entry has this link in header\n if (entry.header.link?.id === condition.link) {\n return true;\n }\n // Also check metadata values for links\n for (const meta of entry.metadata) {\n const content = meta.value.content;\n if (content.type === \"link_value\" && content.link.id === condition.link) {\n return true;\n }\n if (content.type === \"value_array\") {\n for (const elem of content.elements) {\n if (elem.type === \"link\" && elem.id === condition.link) {\n return true;\n }\n }\n }\n }\n return false;\n }\n }\n}\n\n/**\n * Check if an entry matches a query.\n * All conditions are ANDed together.\n *\n * @param entry - The entry to check\n * @param query - The query to match against\n * @returns true if the entry matches all conditions\n */\nexport function entryMatchesQuery(entry: InstanceEntry, query: Query): boolean {\n // Check entity type\n if (entry.header.entity !== query.entity) {\n return false;\n }\n\n // Check all conditions (ANDed together)\n return query.conditions.every((condition) => matchCondition(entry, condition));\n}\n\n/**\n * Execute a single query against the workspace.\n *\n * @param workspace - The workspace to query\n * @param query - The query to execute\n * @param options - Query options\n * @returns Matching entries sorted by timestamp\n */\nexport function executeQuery(\n workspace: Workspace,\n query: Query,\n options: QueryOptions = {},\n): InstanceEntry[] {\n return executeQueries(workspace, [query], options);\n}\n\n/**\n * Execute multiple queries against the workspace.\n * An entry is included if it matches ANY of the queries (OR).\n *\n * @param workspace - The workspace to query\n * @param queries - The queries to execute\n * @param options - Query options\n * @returns Matching entries sorted by timestamp (deduplicated)\n */\nexport function executeQueries(\n workspace: Workspace,\n queries: Query[],\n options: QueryOptions = {},\n): InstanceEntry[] {\n const { afterTimestamp } = options;\n const results: QueryResultEntry[] = [];\n // Track seen entries by file:position to avoid returning the same entry twice\n // (can happen when entry matches multiple queries). We use position rather than\n // semantic identity (timestamp+type) because query should return ALL physical entries.\n const seen = new Set<string>();\n\n for (const model of workspace.allModels()) {\n for (const entry of model.ast.entries) {\n if (entry.type !== \"instance_entry\") {\n continue;\n }\n\n const timestampStr = formatTimestamp(entry.header.timestamp);\n const key = `${model.file}:${entry.location.startPosition.row}:${entry.location.startPosition.column}`;\n\n // Skip if we've already seen this entry\n if (seen.has(key)) {\n continue;\n }\n\n // Skip if entry is before the cutoff\n if (afterTimestamp && timestampStr <= afterTimestamp) {\n continue;\n }\n\n // Check if entry matches any of the queries\n for (const query of queries) {\n if (entryMatchesQuery(entry, query)) {\n results.push({ entry, file: model.file, timestampStr });\n seen.add(key);\n break; // Entry matched, no need to check other queries\n }\n }\n }\n }\n\n // Sort by timestamp\n results.sort((a, b) => a.timestampStr.localeCompare(b.timestampStr));\n\n return results.map((r) => r.entry);\n}\n\n/**\n * Format a query for display.\n *\n * @param query - The query to format\n * @returns Human-readable query string\n */\nexport function formatQuery(query: Query): string {\n let result = query.entity;\n\n if (query.conditions.length > 0) {\n const condStrs = query.conditions.map((c) => {\n switch (c.kind) {\n case \"field\":\n return `${c.field} = ${c.value}`;\n case \"tag\":\n return `#${c.tag}`;\n case \"link\":\n return `^${c.link}`;\n }\n });\n result += ` where ${condStrs.join(\" and \")}`;\n }\n\n return result;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Location } from "../ast/ast-types.js";
|
|
2
|
+
import { LinkDefinition, LinkReference } from "../semantic/analyzer.js";
|
|
3
|
+
import { Workspace } from "../model/workspace.js";
|
|
4
|
+
|
|
5
|
+
//#region src/services/references.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A reference location
|
|
9
|
+
*/
|
|
10
|
+
interface ReferenceLocation {
|
|
11
|
+
/** The file containing the reference */
|
|
12
|
+
file: string;
|
|
13
|
+
/** Location of the reference (file-absolute) */
|
|
14
|
+
location: Location;
|
|
15
|
+
/** Whether this is the definition (vs a reference) */
|
|
16
|
+
isDefinition: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Result of a find-references query
|
|
20
|
+
*/
|
|
21
|
+
interface ReferencesResult {
|
|
22
|
+
/** The link ID */
|
|
23
|
+
linkId: string;
|
|
24
|
+
/** The definition (if it exists) */
|
|
25
|
+
definition: LinkDefinition | undefined;
|
|
26
|
+
/** All references to this link */
|
|
27
|
+
references: LinkReference[];
|
|
28
|
+
/** All locations (definition + references) with file-absolute positions */
|
|
29
|
+
locations: ReferenceLocation[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Find all references to a link ID
|
|
33
|
+
*
|
|
34
|
+
* @param workspace - The workspace to search in
|
|
35
|
+
* @param linkId - The link ID to find (without ^ prefix)
|
|
36
|
+
* @param includeDefinition - Whether to include the definition in the results
|
|
37
|
+
* @returns The references result with file-absolute locations
|
|
38
|
+
*/
|
|
39
|
+
declare function findReferences(workspace: Workspace, linkId: string, includeDefinition?: boolean): ReferencesResult;
|
|
40
|
+
/**
|
|
41
|
+
* Find all references at a given position in a file
|
|
42
|
+
*
|
|
43
|
+
* @param workspace - The workspace to search in
|
|
44
|
+
* @param file - The file path
|
|
45
|
+
* @param offset - The character offset in the file
|
|
46
|
+
* @param includeDefinition - Whether to include the definition in the results
|
|
47
|
+
* @returns The references result, or undefined if no link at position
|
|
48
|
+
*/
|
|
49
|
+
declare function findReferencesAtPosition(workspace: Workspace, file: string, offset: number, includeDefinition?: boolean): ReferencesResult | undefined;
|
|
50
|
+
//#endregion
|
|
51
|
+
export { ReferenceLocation, ReferencesResult, findReferences, findReferencesAtPosition };
|
|
52
|
+
//# sourceMappingURL=references.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"references.d.ts","names":[],"sources":["../../src/services/references.ts"],"sourcesContent":[],"mappings":";;;;;;;;AASA;AAYiB,UAZA,iBAAA,CAYgB;EAInB;EAEA,IAAA,EAAA,MAAA;EAED;EAAiB,QAAA,EAhBlB,QAgBkB;EAWd;EAuDA,YAAA,EAAA,OAAA;;;;;UA1EC,gBAAA;;;;cAIH;;cAEA;;aAED;;;;;;;;;;iBAWG,cAAA,YACH,yDAGV;;;;;;;;;;iBAmDa,wBAAA,YACH,uEAIV"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { positionFromOffset, toFileLocation } from "../source-map.js";
|
|
2
|
+
import { findNodeAtPosition } from "../ast/node-at-position.js";
|
|
3
|
+
|
|
4
|
+
//#region src/services/references.ts
|
|
5
|
+
/**
|
|
6
|
+
* Find all references to a link ID
|
|
7
|
+
*
|
|
8
|
+
* @param workspace - The workspace to search in
|
|
9
|
+
* @param linkId - The link ID to find (without ^ prefix)
|
|
10
|
+
* @param includeDefinition - Whether to include the definition in the results
|
|
11
|
+
* @returns The references result with file-absolute locations
|
|
12
|
+
*/
|
|
13
|
+
function findReferences(workspace, linkId, includeDefinition = true) {
|
|
14
|
+
const definition = workspace.getLinkDefinition(linkId);
|
|
15
|
+
const references = workspace.getLinkReferences(linkId);
|
|
16
|
+
const locations = [];
|
|
17
|
+
if (includeDefinition && definition) {
|
|
18
|
+
const model = workspace.getModel(definition.file);
|
|
19
|
+
if (model) {
|
|
20
|
+
const fileLocation = toFileLocation(model.sourceMap, definition.location);
|
|
21
|
+
locations.push({
|
|
22
|
+
file: definition.file,
|
|
23
|
+
location: fileLocation,
|
|
24
|
+
isDefinition: true
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
for (const ref of references) {
|
|
29
|
+
const model = workspace.getModel(ref.file);
|
|
30
|
+
if (model) {
|
|
31
|
+
const fileLocation = toFileLocation(model.sourceMap, ref.location);
|
|
32
|
+
locations.push({
|
|
33
|
+
file: ref.file,
|
|
34
|
+
location: fileLocation,
|
|
35
|
+
isDefinition: false
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
linkId,
|
|
41
|
+
definition,
|
|
42
|
+
references,
|
|
43
|
+
locations
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Find all references at a given position in a file
|
|
48
|
+
*
|
|
49
|
+
* @param workspace - The workspace to search in
|
|
50
|
+
* @param file - The file path
|
|
51
|
+
* @param offset - The character offset in the file
|
|
52
|
+
* @param includeDefinition - Whether to include the definition in the results
|
|
53
|
+
* @returns The references result, or undefined if no link at position
|
|
54
|
+
*/
|
|
55
|
+
function findReferencesAtPosition(workspace, file, offset, includeDefinition = true) {
|
|
56
|
+
const model = workspace.getModel(file);
|
|
57
|
+
if (!model) return;
|
|
58
|
+
const position = positionFromOffset(model.source, offset);
|
|
59
|
+
const context = findNodeAtPosition({ blocks: model.blocks }, position);
|
|
60
|
+
if (context.kind !== "link") return;
|
|
61
|
+
return findReferences(workspace, context.linkId, includeDefinition);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//#endregion
|
|
65
|
+
export { findReferences, findReferencesAtPosition };
|
|
66
|
+
//# sourceMappingURL=references.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"references.js","names":["locations: ReferenceLocation[]"],"sources":["../../src/services/references.ts"],"sourcesContent":["import type { Workspace } from \"../model/workspace.js\";\nimport type { Location } from \"../ast/ast-types.js\";\nimport type { LinkReference, LinkDefinition } from \"../semantic/analyzer.js\";\nimport { toFileLocation, positionFromOffset } from \"../source-map.js\";\nimport { findNodeAtPosition } from \"../ast/node-at-position.js\";\n\n/**\n * A reference location\n */\nexport interface ReferenceLocation {\n /** The file containing the reference */\n file: string;\n /** Location of the reference (file-absolute) */\n location: Location;\n /** Whether this is the definition (vs a reference) */\n isDefinition: boolean;\n}\n\n/**\n * Result of a find-references query\n */\nexport interface ReferencesResult {\n /** The link ID */\n linkId: string;\n /** The definition (if it exists) */\n definition: LinkDefinition | undefined;\n /** All references to this link */\n references: LinkReference[];\n /** All locations (definition + references) with file-absolute positions */\n locations: ReferenceLocation[];\n}\n\n/**\n * Find all references to a link ID\n *\n * @param workspace - The workspace to search in\n * @param linkId - The link ID to find (without ^ prefix)\n * @param includeDefinition - Whether to include the definition in the results\n * @returns The references result with file-absolute locations\n */\nexport function findReferences(\n workspace: Workspace,\n linkId: string,\n includeDefinition = true,\n): ReferencesResult {\n const definition = workspace.getLinkDefinition(linkId);\n const references = workspace.getLinkReferences(linkId);\n\n const locations: ReferenceLocation[] = [];\n\n // Add definition first if requested\n if (includeDefinition && definition) {\n // Get source map from model\n const model = workspace.getModel(definition.file);\n if (model) {\n const fileLocation = toFileLocation(model.sourceMap, definition.location);\n locations.push({\n file: definition.file,\n location: fileLocation,\n isDefinition: true,\n });\n }\n }\n\n // Add all references\n for (const ref of references) {\n // Get source map from model\n const model = workspace.getModel(ref.file);\n if (model) {\n const fileLocation = toFileLocation(model.sourceMap, ref.location);\n locations.push({\n file: ref.file,\n location: fileLocation,\n isDefinition: false,\n });\n }\n }\n\n return {\n linkId,\n definition,\n references,\n locations,\n };\n}\n\n/**\n * Find all references at a given position in a file\n *\n * @param workspace - The workspace to search in\n * @param file - The file path\n * @param offset - The character offset in the file\n * @param includeDefinition - Whether to include the definition in the results\n * @returns The references result, or undefined if no link at position\n */\nexport function findReferencesAtPosition(\n workspace: Workspace,\n file: string,\n offset: number,\n includeDefinition = true,\n): ReferencesResult | undefined {\n const model = workspace.getModel(file);\n if (!model) {\n return undefined;\n }\n\n // Convert offset to position\n const position = positionFromOffset(model.source, offset);\n\n // Use AST-based node detection\n const context = findNodeAtPosition({ blocks: model.blocks }, position);\n\n // Only handle link contexts\n if (context.kind !== \"link\") {\n return undefined;\n }\n\n return findReferences(workspace, context.linkId, includeDefinition);\n}\n"],"mappings":";;;;;;;;;;;;AAwCA,SAAgB,eACd,WACA,QACA,oBAAoB,MACF;CAClB,MAAM,aAAa,UAAU,kBAAkB,OAAO;CACtD,MAAM,aAAa,UAAU,kBAAkB,OAAO;CAEtD,MAAMA,YAAiC,EAAE;AAGzC,KAAI,qBAAqB,YAAY;EAEnC,MAAM,QAAQ,UAAU,SAAS,WAAW,KAAK;AACjD,MAAI,OAAO;GACT,MAAM,eAAe,eAAe,MAAM,WAAW,WAAW,SAAS;AACzE,aAAU,KAAK;IACb,MAAM,WAAW;IACjB,UAAU;IACV,cAAc;IACf,CAAC;;;AAKN,MAAK,MAAM,OAAO,YAAY;EAE5B,MAAM,QAAQ,UAAU,SAAS,IAAI,KAAK;AAC1C,MAAI,OAAO;GACT,MAAM,eAAe,eAAe,MAAM,WAAW,IAAI,SAAS;AAClE,aAAU,KAAK;IACb,MAAM,IAAI;IACV,UAAU;IACV,cAAc;IACf,CAAC;;;AAIN,QAAO;EACL;EACA;EACA;EACA;EACD;;;;;;;;;;;AAYH,SAAgB,yBACd,WACA,MACA,QACA,oBAAoB,MACU;CAC9B,MAAM,QAAQ,UAAU,SAAS,KAAK;AACtC,KAAI,CAAC,MACH;CAIF,MAAM,WAAW,mBAAmB,MAAM,QAAQ,OAAO;CAGzD,MAAM,UAAU,mBAAmB,EAAE,QAAQ,MAAM,QAAQ,EAAE,SAAS;AAGtE,KAAI,QAAQ,SAAS,OACnB;AAGF,QAAO,eAAe,WAAW,QAAQ,QAAQ,kBAAkB"}
|