-
-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Content Collections Intellisense (#915)
* feat: cc integration * fix: clean up * chore: remove unused package * feat: support maps and seqs * feat: do hybrid ts / json * feat: apply schemas dynamically * feat: flag it * feat: references support * chore: lockfile * fix: merge conflicts * fix: handle errors * feat: cleanup ts output to not include values * refactor: ts-plugin * fix: remove unneeded param * refactor: don't hardcode frontmatter holders * refactor: use consistent languageIds * test: add tests * fix: disable codelenses properly * fix: use file urls * fix: atempt to debug windows * fix: windooooows * Revert "fix: atempt to debug windows" This reverts commit 4b86b00. * chore: changeset * refactor: remove some unused code * refactor: remove more unused code * refactor: use same frontmatter extraction as Astro itself * docs: add comments explaining uri transformations * nit: adjust for feedback * chore: lockfile * chore: update Astro version
- Loading branch information
1 parent
3a4d60b
commit d624646
Showing
37 changed files
with
1,368 additions
and
229 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
--- | ||
"@astrojs/language-server": minor | ||
"@astrojs/ts-plugin": minor | ||
"@astrojs/yaml2ts": minor | ||
"astro-vscode": minor | ||
--- | ||
|
||
Adds support for Content Collection Intellisense |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,3 +16,5 @@ packages/vscode/meta.json | |
|
||
# do not commit .env files or any files that end with `.env` | ||
*.env | ||
|
||
**/.astro |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
161 changes: 161 additions & 0 deletions
161
packages/language-server/src/core/frontmatterHolders.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import { yaml2ts, VIRTUAL_CODE_ID } from '@astrojs/yaml2ts'; | ||
import { | ||
type CodeMapping, | ||
type LanguagePlugin, | ||
type VirtualCode, | ||
forEachEmbeddedCode, | ||
} from '@volar/language-core'; | ||
import type ts from 'typescript'; | ||
import type { URI } from 'vscode-uri'; | ||
|
||
export const SUPPORTED_FRONTMATTER_EXTENSIONS = { md: 'markdown', mdx: 'mdx', mdoc: 'mdoc' }; | ||
export const SUPPORTED_FRONTMATTER_EXTENSIONS_KEYS = Object.keys(SUPPORTED_FRONTMATTER_EXTENSIONS); | ||
const SUPPORTED_FRONTMATTER_EXTENSIONS_VALUES = Object.values(SUPPORTED_FRONTMATTER_EXTENSIONS); | ||
|
||
export const frontmatterRE = /^---(.*?)^---/ms; | ||
|
||
export type CollectionConfig = { | ||
folder: URI; | ||
config: { | ||
collections: { | ||
hasSchema: boolean; | ||
name: string; | ||
}[]; | ||
entries: Record<string, string>; | ||
}; | ||
}; | ||
|
||
function getCollectionName(collectionConfigs: CollectionConfig[], fileURI: string) { | ||
for (const collection of collectionConfigs) { | ||
if (collection.config.entries[fileURI]) { | ||
return collection.config.entries[fileURI]; | ||
} | ||
} | ||
} | ||
|
||
export function getFrontmatterLanguagePlugin( | ||
collectionConfigs: CollectionConfig[], | ||
): LanguagePlugin<URI, FrontmatterHolder> { | ||
return { | ||
getLanguageId(scriptId) { | ||
const fileType = SUPPORTED_FRONTMATTER_EXTENSIONS_KEYS.find((ext) => | ||
scriptId.path.endsWith(`.${ext}`), | ||
); | ||
|
||
if (fileType) { | ||
return SUPPORTED_FRONTMATTER_EXTENSIONS[ | ||
fileType as keyof typeof SUPPORTED_FRONTMATTER_EXTENSIONS | ||
]; | ||
} | ||
}, | ||
createVirtualCode(scriptId, languageId, snapshot) { | ||
if (SUPPORTED_FRONTMATTER_EXTENSIONS_VALUES.includes(languageId)) { | ||
return new FrontmatterHolder( | ||
scriptId.fsPath.replace(/\\/g, '/'), | ||
languageId, | ||
snapshot, | ||
getCollectionName( | ||
collectionConfigs, | ||
// The scriptId here is encoded and somewhat normalized, as such we can't use it directly to compare with | ||
// the file URLs in the collection config entries that Astro generates. | ||
decodeURIComponent(scriptId.toString()).toLowerCase(), | ||
), | ||
); | ||
} | ||
}, | ||
typescript: { | ||
extraFileExtensions: SUPPORTED_FRONTMATTER_EXTENSIONS_KEYS.map((ext) => ({ | ||
extension: ext, | ||
isMixedContent: true, | ||
scriptKind: 7 satisfies ts.ScriptKind.Deferred, | ||
})), | ||
getServiceScript(astroCode) { | ||
for (const code of forEachEmbeddedCode(astroCode)) { | ||
if (code.id === VIRTUAL_CODE_ID) { | ||
return { | ||
code, | ||
extension: '.ts', | ||
scriptKind: 3 satisfies ts.ScriptKind.TS, | ||
}; | ||
} | ||
} | ||
return undefined; | ||
}, | ||
}, | ||
}; | ||
} | ||
|
||
export class FrontmatterHolder implements VirtualCode { | ||
id = 'frontmatter-holder'; | ||
mappings: CodeMapping[]; | ||
embeddedCodes: VirtualCode[]; | ||
public hasFrontmatter = false; | ||
|
||
constructor( | ||
public fileName: string, | ||
public languageId: string, | ||
public snapshot: ts.IScriptSnapshot, | ||
public collection: string | undefined, | ||
) { | ||
this.mappings = [ | ||
{ | ||
sourceOffsets: [0], | ||
generatedOffsets: [0], | ||
lengths: [this.snapshot.getLength()], | ||
data: { | ||
verification: true, | ||
completion: true, | ||
semantic: true, | ||
navigation: true, | ||
structure: true, | ||
format: true, | ||
}, | ||
}, | ||
]; | ||
|
||
this.embeddedCodes = []; | ||
this.snapshot = snapshot; | ||
|
||
// If the file is not part of a collection, we don't need to do anything | ||
if (!this.collection) { | ||
return; | ||
} | ||
|
||
const frontmatterContent = | ||
frontmatterRE | ||
.exec(this.snapshot.getText(0, this.snapshot.getLength()))?.[0] | ||
.replaceAll('---', ' ') ?? ''; | ||
|
||
this.hasFrontmatter = frontmatterContent.length > 0; | ||
|
||
this.embeddedCodes.push({ | ||
id: `yaml_frontmatter_${this.collection}`, | ||
languageId: 'yaml', | ||
snapshot: { | ||
getText: (start, end) => frontmatterContent.substring(start, end), | ||
getLength: () => frontmatterContent.length, | ||
getChangeRange: () => undefined, | ||
}, | ||
mappings: [ | ||
{ | ||
sourceOffsets: [0], | ||
generatedOffsets: [0], | ||
lengths: [frontmatterContent.length], | ||
data: { | ||
verification: true, | ||
completion: true, | ||
semantic: true, | ||
navigation: true, | ||
structure: true, | ||
format: false, | ||
}, | ||
}, | ||
], | ||
}); | ||
|
||
if (this.hasFrontmatter) { | ||
const yaml2tsResult = yaml2ts(frontmatterContent, this.collection); | ||
this.embeddedCodes.push(yaml2tsResult.virtualCode); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.