Add documentation for using Milkdown with various frameworks
- Created a new document for using components in Milkdown. - Added a guide for using plugins in Milkdown, including toggling plugins programmatically and listing official plugins. - Introduced a recipe for integrating Milkdown with Angular, including installation steps and component creation. - Added a recipe for using Milkdown with Next.js, detailing installation and component setup. - Created a guide for integrating Milkdown with NuxtJS, including installation and component creation. - Added a comprehensive guide for using Milkdown with React, covering both Crepe and core Milkdown usage. - Introduced a recipe for SolidJS integration with Milkdown, including installation and component creation. - Added a guide for using Milkdown with Svelte, detailing installation and component setup. - Created a comprehensive guide for integrating Milkdown with Vue, covering both Crepe and core Milkdown usage. - Added a recipe for using Milkdown with Vue2, including installation and component creation.
This commit is contained in:
@@ -0,0 +1,394 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { packageJson } from '../../../../../platform/env/common/packagejson';
|
||||
import { createServiceIdentifier } from '../../../../../util/common/services';
|
||||
import { ServicesAccessor } from '../../../../../util/vs/platform/instantiation/common/instantiation';
|
||||
import { CopilotConfigPrefix } from './constants';
|
||||
import { Filter } from './experiments/filters';
|
||||
import { Emitter, Event } from './util/event';
|
||||
|
||||
export { packageJson };
|
||||
|
||||
export const ConfigKey = {
|
||||
Enable: 'enable',
|
||||
UserSelectedCompletionModel: 'selectedCompletionModel',
|
||||
|
||||
ShowEditorCompletions: 'editor.showEditorCompletions',
|
||||
EnableAutoCompletions: 'editor.enableAutoCompletions',
|
||||
DelayCompletions: 'editor.delayCompletions',
|
||||
FilterCompletions: 'editor.filterCompletions',
|
||||
CompletionsDelay: 'completionsDelay',
|
||||
CompletionsDebounce: 'completionsDebounce',
|
||||
|
||||
// Advanced config (don't add new config here)
|
||||
RelatedFilesVSCodeCSharp: 'advanced.relatedFilesVSCodeCSharp',
|
||||
RelatedFilesVSCodeTypeScript: 'advanced.relatedFilesVSCodeTypeScript',
|
||||
RelatedFilesVSCode: 'advanced.relatedFilesVSCode',
|
||||
ContextProviders: 'advanced.contextProviders',
|
||||
DebugFilterLogCategories: 'advanced.debug.filterLogCategories',
|
||||
DebugSnippyOverrideUrl: 'advanced.debug.codeRefOverrideUrl',
|
||||
UseSubsetMatching: 'advanced.useSubsetMatching',
|
||||
ContextProviderTimeBudget: 'advanced.contextProviderTimeBudget',
|
||||
|
||||
// Internal config
|
||||
DebugOverrideCapiUrl: 'internal.capiUrl',
|
||||
DebugOverrideCapiUrlLegacy: 'advanced.debug.overrideCapiUrl',
|
||||
DebugTestOverrideCapiUrl: 'internal.capiTestUrl',
|
||||
DebugTestOverrideCapiUrlLegacy: 'advanced.debug.testOverrideCapiUrl',
|
||||
DebugOverrideProxyUrl: 'internal.completionsUrl',
|
||||
DebugOverrideProxyUrlLegacy: 'advanced.debug.overrideProxyUrl',
|
||||
DebugTestOverrideProxyUrl: 'internal.completionsTestUrl',
|
||||
DebugTestOverrideProxyUrlLegacy: 'advanced.debug.testOverrideProxyUrl',
|
||||
DebugOverrideEngine: 'internal.completionModel',
|
||||
DebugOverrideEngineLegacy: 'advanced.debug.overrideEngine',
|
||||
/**
|
||||
* Internal experiment for always requesting multiline completions.
|
||||
* This might not result always in a multiline suggestion, but most often will.
|
||||
*/
|
||||
AlwaysRequestMultiline: 'internal.alwaysRequestMultiline',
|
||||
/**
|
||||
* Let the model terminate single line completions when AlwaysRequestMultiline is enabled.
|
||||
*/
|
||||
ModelAlwaysTerminatesSingleline: 'internal.modelAlwaysTerminatesSingleline',
|
||||
|
||||
/**
|
||||
* Overrides whether to use the Workspace Context Coordinator to coordinate workspace context.
|
||||
* This setting takes precedence over the value from ExP.
|
||||
*/
|
||||
UseWorkspaceContextCoordinator: 'internal.useWorkspaceContextCoordinator',
|
||||
|
||||
/**
|
||||
* Overrides whether to include neighboring files in the prompt
|
||||
* alongside context providers.
|
||||
* This setting takes precedence over the value from ExP.
|
||||
*/
|
||||
IncludeNeighboringFiles: 'internal.includeNeighboringFiles',
|
||||
ExcludeRelatedFiles: 'internal.excludeRelatedFiles',
|
||||
DebugOverrideCppHeadersEnableSwitch: 'internal.cppHeadersEnableSwitch',
|
||||
|
||||
/**
|
||||
* Internal config for using the completions prompt with split context.
|
||||
* https://github.com/github/copilot/issues/19286
|
||||
*/
|
||||
UseSplitContextPrompt: 'internal.useSplitContextPrompt',
|
||||
};
|
||||
|
||||
export type ConfigKeyType = string;
|
||||
|
||||
// How to determine where to terminate the completion to the current block.
|
||||
export enum BlockMode {
|
||||
/**
|
||||
* Parse the context + completion on the client using treesitter to
|
||||
* determine blocks.
|
||||
*/
|
||||
Parsing = 'parsing',
|
||||
/**
|
||||
* Let the server parse out blocks and assume that the completion terminates
|
||||
* at the end of a block.
|
||||
*/
|
||||
Server = 'server',
|
||||
/**
|
||||
* Runs both the treesitter parsing on the client plus indentation-based
|
||||
* truncation on the proxy.
|
||||
*/
|
||||
ParsingAndServer = 'parsingandserver',
|
||||
/**
|
||||
* Client-based heuristic to display more multiline completions.
|
||||
* It almost always requests a multiline completion from the server and tries to break it up to something useful on the client.
|
||||
*
|
||||
* This should not be rolled out at the moment (latency impact is high, UX needs further fine-tuning),
|
||||
* but can be used for internal experimentation.
|
||||
*/
|
||||
MoreMultiline = 'moremultiline',
|
||||
}
|
||||
|
||||
export function shouldDoServerTrimming(blockMode: BlockMode): boolean {
|
||||
return [BlockMode.Server, BlockMode.ParsingAndServer].includes(blockMode);
|
||||
}
|
||||
|
||||
// TODO rework this enum so that the normal/nightly and prod/dev distinctions are orthogonal. (dev builds should behave like nightly?)
|
||||
export enum BuildType {
|
||||
DEV = 'dev',
|
||||
PROD = 'prod',
|
||||
NIGHTLY = 'nightly',
|
||||
}
|
||||
|
||||
export const ICompletionsConfigProvider = createServiceIdentifier<ICompletionsConfigProvider>('ICompletionsConfigProvider');
|
||||
export interface ICompletionsConfigProvider {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
getConfig<T>(key: ConfigKeyType): T;
|
||||
getOptionalConfig<T>(key: ConfigKeyType): T | undefined;
|
||||
dumpForTelemetry(): { [key: string]: string };
|
||||
onDidChangeCopilotSettings: Event<ConfigProvider>;
|
||||
}
|
||||
|
||||
export abstract class ConfigProvider implements ICompletionsConfigProvider {
|
||||
declare _serviceBrand: undefined;
|
||||
abstract getConfig<T>(key: ConfigKeyType): T;
|
||||
abstract getOptionalConfig<T>(key: ConfigKeyType): T | undefined;
|
||||
abstract dumpForTelemetry(): { [key: string]: string };
|
||||
abstract onDidChangeCopilotSettings: Event<ConfigProvider>;
|
||||
|
||||
// The language server receives workspace configuration *after* it is fully initialized, which creates a race
|
||||
// condition where an incoming request immediately after initialization might have the default values. Awaiting
|
||||
// this promise allows consumers to ensure that the configuration is ready before using it.
|
||||
requireReady(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides only the default values, ignoring the user's settings.
|
||||
* @public KEEPING FOR TESTS
|
||||
*/
|
||||
export class DefaultsOnlyConfigProvider extends ConfigProvider {
|
||||
override getConfig<T>(key: ConfigKeyType): T {
|
||||
// hardcode default values for the agent, for now
|
||||
return getConfigDefaultForKey<T>(key);
|
||||
}
|
||||
|
||||
override getOptionalConfig<T>(key: ConfigKeyType): T | undefined {
|
||||
return getOptionalConfigDefaultForKey<T>(key);
|
||||
}
|
||||
|
||||
override dumpForTelemetry(): { [key: string]: string } {
|
||||
return {};
|
||||
}
|
||||
|
||||
override onDidChangeCopilotSettings = () => {
|
||||
// no-op, since this provider does not support changing settings
|
||||
return {
|
||||
dispose: () => { },
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A ConfigProvider that allows overriding of config values.
|
||||
* @public KEEPING FOR TESTS
|
||||
*/
|
||||
export class InMemoryConfigProvider extends ConfigProvider {
|
||||
protected readonly copilotEmitter = new Emitter<this>();
|
||||
readonly onDidChangeCopilotSettings = this.copilotEmitter.event;
|
||||
private overrides: Map<ConfigKeyType, unknown> = new Map();
|
||||
|
||||
constructor(
|
||||
private readonly baseConfigProvider: ConfigProvider,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
setOverrides(overrides: Map<ConfigKeyType, unknown>): void {
|
||||
this.overrides = overrides;
|
||||
}
|
||||
|
||||
clearOverrides(): void {
|
||||
this.overrides.clear();
|
||||
}
|
||||
|
||||
protected getOptionalOverride<T>(key: ConfigKeyType): T | undefined {
|
||||
return this.overrides.get(key) as T | undefined;
|
||||
}
|
||||
|
||||
override getConfig<T>(key: ConfigKeyType): T {
|
||||
return this.getOptionalOverride(key) ?? this.baseConfigProvider.getConfig(key);
|
||||
}
|
||||
|
||||
override getOptionalConfig<T>(key: ConfigKeyType): T | undefined {
|
||||
return this.getOptionalOverride(key) ?? this.baseConfigProvider.getOptionalConfig(key);
|
||||
}
|
||||
|
||||
setConfig(key: ConfigKeyType, value: unknown): void {
|
||||
this.setCopilotSettings({ [key]: value });
|
||||
}
|
||||
|
||||
setCopilotSettings(settings: Record<ConfigKeyType, unknown>): void {
|
||||
for (const [key, value] of Object.entries(settings)) {
|
||||
if (value !== undefined) {
|
||||
this.overrides.set(key, value);
|
||||
} else {
|
||||
this.overrides.delete(key);
|
||||
}
|
||||
}
|
||||
this.copilotEmitter.fire(this);
|
||||
}
|
||||
|
||||
override dumpForTelemetry(): { [key: string]: string } {
|
||||
const config = this.baseConfigProvider.dumpForTelemetry();
|
||||
// reflects what's mapped in Hydro
|
||||
for (const key of [
|
||||
ConfigKey.ShowEditorCompletions,
|
||||
ConfigKey.EnableAutoCompletions,
|
||||
ConfigKey.DelayCompletions,
|
||||
ConfigKey.FilterCompletions,
|
||||
]) {
|
||||
const value = this.overrides.get(key);
|
||||
if (value !== undefined) {
|
||||
config[key] = JSON.stringify(value);
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export function getConfigKeyRecursively<T>(config: Record<string, unknown>, key: string): T | undefined {
|
||||
let value: unknown = config;
|
||||
const prefix: string[] = [];
|
||||
for (const segment of key.split('.')) {
|
||||
const child = [...prefix, segment].join('.');
|
||||
if (value && typeof value === 'object' && child in value) {
|
||||
value = (value as { [key: string]: unknown })[child];
|
||||
prefix.length = 0;
|
||||
} else {
|
||||
prefix.push(segment);
|
||||
}
|
||||
}
|
||||
if (value === undefined || prefix.length > 0) { return; }
|
||||
return value as T;
|
||||
}
|
||||
|
||||
export function getConfigDefaultForKey<T>(key: string): T {
|
||||
if (configDefaults.has(key)) {
|
||||
return configDefaults.get(key) as T;
|
||||
}
|
||||
throw new Error(`Missing config default value: ${CopilotConfigPrefix}.${key}`);
|
||||
}
|
||||
|
||||
export function getOptionalConfigDefaultForKey<T>(key: string): T | undefined {
|
||||
return <T>configDefaults.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults for "hidden" config keys. These are supplemented by the defaults in package.json.
|
||||
*/
|
||||
const configDefaults = new Map<ConfigKeyType, unknown>([
|
||||
[ConfigKey.DebugOverrideCppHeadersEnableSwitch, false],
|
||||
[ConfigKey.RelatedFilesVSCodeCSharp, false],
|
||||
[ConfigKey.RelatedFilesVSCodeTypeScript, false],
|
||||
[ConfigKey.RelatedFilesVSCode, false],
|
||||
[ConfigKey.IncludeNeighboringFiles, false],
|
||||
[ConfigKey.ExcludeRelatedFiles, false],
|
||||
[ConfigKey.ContextProviders, []],
|
||||
[ConfigKey.DebugSnippyOverrideUrl, ''],
|
||||
[ConfigKey.UseSubsetMatching, null],
|
||||
[ConfigKey.ContextProviderTimeBudget, undefined],
|
||||
[ConfigKey.DebugOverrideCapiUrl, ''],
|
||||
[ConfigKey.DebugTestOverrideCapiUrl, ''],
|
||||
[ConfigKey.DebugOverrideProxyUrl, ''],
|
||||
[ConfigKey.DebugTestOverrideProxyUrl, ''],
|
||||
[ConfigKey.DebugOverrideEngine, ''],
|
||||
[ConfigKey.AlwaysRequestMultiline, undefined],
|
||||
[ConfigKey.CompletionsDebounce, undefined],
|
||||
[ConfigKey.CompletionsDelay, undefined],
|
||||
[ConfigKey.ModelAlwaysTerminatesSingleline, undefined],
|
||||
[ConfigKey.UseWorkspaceContextCoordinator, undefined],
|
||||
|
||||
|
||||
// These are only used for telemetry from LSP based editors and do not affect any behavior.
|
||||
[ConfigKey.ShowEditorCompletions, undefined],
|
||||
[ConfigKey.EnableAutoCompletions, undefined],
|
||||
[ConfigKey.DelayCompletions, undefined],
|
||||
[ConfigKey.FilterCompletions, undefined],
|
||||
[ConfigKey.UseSplitContextPrompt, true],
|
||||
|
||||
// These are defaults from package.json
|
||||
[ConfigKey.Enable, { '*': true, 'plaintext': false, 'markdown': false, 'scminput': false }],
|
||||
[ConfigKey.UserSelectedCompletionModel, ''],
|
||||
|
||||
// These are advanced defaults from package.json
|
||||
[ConfigKey.DebugOverrideEngineLegacy, ''],
|
||||
[ConfigKey.DebugOverrideProxyUrlLegacy, ''],
|
||||
[ConfigKey.DebugTestOverrideProxyUrlLegacy, ''],
|
||||
[ConfigKey.DebugOverrideCapiUrlLegacy, ''],
|
||||
[ConfigKey.DebugTestOverrideCapiUrlLegacy, ''],
|
||||
[ConfigKey.DebugFilterLogCategories, []],
|
||||
]);
|
||||
|
||||
export function getConfig<T>(accessor: ServicesAccessor, key: ConfigKeyType): T {
|
||||
return accessor.get(ICompletionsConfigProvider).getConfig(key);
|
||||
}
|
||||
|
||||
export function dumpForTelemetry(accessor: ServicesAccessor) {
|
||||
try {
|
||||
return accessor.get(ICompletionsConfigProvider).dumpForTelemetry();
|
||||
} catch (e) {
|
||||
console.error(`Error dumping config for telemetry: ${e}`);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export class BuildInfo {
|
||||
|
||||
static isPreRelease(): boolean {
|
||||
return this.getBuildType() === BuildType.NIGHTLY;
|
||||
}
|
||||
|
||||
static isProduction(): boolean {
|
||||
return this.getBuildType() !== BuildType.DEV;
|
||||
}
|
||||
|
||||
static getBuildType(): BuildType {
|
||||
const buildType = <'dev' | 'prod'>packageJson.buildType;
|
||||
if (buildType === 'prod') {
|
||||
return BuildInfo.getVersion().length === 15 ? BuildType.NIGHTLY : BuildType.PROD;
|
||||
}
|
||||
return BuildType.DEV;
|
||||
}
|
||||
|
||||
static getVersion(): string {
|
||||
return packageJson.version;
|
||||
}
|
||||
|
||||
static getBuild(): string {
|
||||
return packageJson.build;
|
||||
}
|
||||
}
|
||||
|
||||
type NameAndVersion = {
|
||||
name: string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
export type EditorInfo = NameAndVersion & {
|
||||
// The root directory of the installation, currently only used to simplify stack traces.
|
||||
root?: string;
|
||||
// A programmatic name, used for error reporting.
|
||||
devName?: string;
|
||||
};
|
||||
|
||||
export type EditorPluginInfo = NameAndVersion;
|
||||
|
||||
export type EditorPluginFilter = { filter: Filter; value: string; isVersion?: boolean };
|
||||
|
||||
export function formatNameAndVersion({ name, version }: NameAndVersion): string {
|
||||
return `${name}/${version}`;
|
||||
}
|
||||
|
||||
export const ICompletionsEditorAndPluginInfo = createServiceIdentifier<ICompletionsEditorAndPluginInfo>('ICompletionsEditorAndPluginInfo');
|
||||
export interface ICompletionsEditorAndPluginInfo {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
getEditorInfo(): EditorInfo;
|
||||
getEditorPluginInfo(): EditorPluginInfo;
|
||||
getRelatedPluginInfo(): EditorPluginInfo[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not use this in new code. Every endpoint has its own unique versioning.
|
||||
* Centralizing in a single constant was a mistake.
|
||||
* @deprecated
|
||||
*/
|
||||
export const apiVersion = '2025-05-01';
|
||||
|
||||
export function editorVersionHeaders(accessor: ServicesAccessor): { [key: string]: string } {
|
||||
const info = accessor.get(ICompletionsEditorAndPluginInfo);
|
||||
return {
|
||||
'Editor-Version': formatNameAndVersion(info.getEditorInfo()),
|
||||
'Editor-Plugin-Version': formatNameAndVersion(info.getEditorPluginInfo()),
|
||||
'Copilot-Language-Server-Version': BuildInfo.getVersion(),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user