cleanup, refactor, and add doc comments

This commit is contained in:
Oleg Solomko
2025-04-03 17:52:40 -07:00
parent 85492c33c5
commit d2a63c033d
6 changed files with 511 additions and 508 deletions
@@ -0,0 +1,130 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Range } from '../../../../../../../../../editor/common/core/range.js';
import { IMarkdownString } from '../../../../../../../../../base/common/htmlContent.js';
import { BaseToken } from '../../../../../../../../../editor/common/codecs/baseToken.js';
import { ModelDecorationOptions } from '../../../../../../../../../editor/common/model/textModel.js';
import { IModelDecorationsChangeAccessor, TrackedRangeStickiness } from '../../../../../../../../../editor/common/model.js';
import { IColorTheme, ICssStyleCollector } from '../../../../../../../../../platform/theme/common/themeService.js';
/**
* Base class for all editor decorations.
*/
export abstract class DecorationBase<
TPromptToken extends BaseToken,
TCssClassName extends string = string,
> {
/**
* Description of the decoration.
*/
protected abstract get description(): string;
/**
* Default CSS class name of the decoration.
*/
protected abstract get className(): TCssClassName;
/**
* Inline CSS class name of the decoration.
*/
protected abstract get inlineClassName(): TCssClassName;
/**
* Indicates whether the decoration spans the whole line(s).
*/
protected get isWholeLine(): boolean {
return false;
}
/**
* Hover message of the decoration.
*/
protected get hoverMessage(): IMarkdownString | IMarkdownString[] | null {
return null;
}
/**
* ID of editor decoration it was registered with.
*/
public readonly id: string;
constructor(
accessor: Pick<IModelDecorationsChangeAccessor, 'addDecoration'>,
protected readonly token: TPromptToken,
) {
this.id = accessor.addDecoration(this.range, this.decorationOptions);
}
/**
* Range of the decoration.
*/
public get range(): Range {
return this.token.range;
}
/**
* Renders (updates) the decoration in the editor.
*/
public render(
accessor: Pick<IModelDecorationsChangeAccessor, 'changeDecoration' | 'changeDecorationOptions'>,
): this {
accessor.changeDecorationOptions(
this.id,
this.decorationOptions,
);
return this;
}
/**
* Removes associated editor decoration(s).
*/
public remove(
accessor: Pick<IModelDecorationsChangeAccessor, 'removeDecoration'>,
): this {
accessor.removeDecoration(this.id);
return this;
}
/**
* Get editor decoration options for this decorator.
*/
private get decorationOptions(): ModelDecorationOptions {
return ModelDecorationOptions.createDynamic({
description: this.description,
hoverMessage: this.hoverMessage,
className: this.className,
inlineClassName: this.inlineClassName,
isWholeLine: this.isWholeLine,
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
shouldFillLineOnLineBreak: true,
});
}
}
/**
* Type of a generic decoration class.
*/
export type TDecorationClass<TPromptToken extends BaseToken = BaseToken> = {
new(
accessor: Pick<IModelDecorationsChangeAccessor, 'addDecoration'>,
token: TPromptToken,
): DecorationBase<TPromptToken>;
/**
* Register CSS styles for the decoration.
*/
registerStyles(
theme: IColorTheme,
collector: ICssStyleCollector,
): void;
/**
* Whether the decoration class handles the provided token.
*/
handles(token: BaseToken): token is TPromptToken;
};
@@ -0,0 +1,100 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from '../../../../../../../../../nls.js';
import { ReactiveDecorationBase } from './reactiveDecorationBase.js';
import { Color, RGBA } from '../../../../../../../../../base/common/color.js';
import { BaseToken } from '../../../../../../../../../editor/common/codecs/baseToken.js';
import { registerColor } from '../../../../../../../../../platform/theme/common/colorUtils.js';
import { contrastBorder } from '../../../../../../../../../platform/theme/common/colorRegistry.js';
import { IColorTheme, ICssStyleCollector } from '../../../../../../../../../platform/theme/common/themeService.js';
import { FrontMatterHeader } from '../../../../../../../../../editor/common/codecs/markdownExtensionsCodec/tokens/frontMatterHeader.js';
/**
* Decoration CSS class names.
*/
export enum FrontMatterCssClassNames {
/**
* TODO: @legomushroom
*/
frontMatterHeader = 'prompt-front-matter-header',
frontMatterHeaderInlineInactive = 'prompt-front-matter-header-inline-inactive',
frontMatterHeaderInlineActive = 'prompt-front-matter-header-inline-active',
}
/**
* TODO: @legomushroom
*/
export class FrontMatterHeaderDecoration extends ReactiveDecorationBase<FrontMatterHeader, FrontMatterCssClassNames> {
protected override get isWholeLine(): boolean {
return true;
}
protected override get description(): string {
return 'Front Matter header editor decoration.';
}
protected override get className(): FrontMatterCssClassNames.frontMatterHeader {
return FrontMatterCssClassNames.frontMatterHeader;
}
protected override get inlineClassName(): FrontMatterCssClassNames.frontMatterHeaderInlineActive | FrontMatterCssClassNames.frontMatterHeaderInlineInactive {
return (this.active)
? FrontMatterCssClassNames.frontMatterHeaderInlineActive
: FrontMatterCssClassNames.frontMatterHeaderInlineInactive;
}
/**
* Whether current decoration class can decorate provided token.
*/
public static handles(
token: BaseToken,
): token is FrontMatterHeader {
return token instanceof FrontMatterHeader;
}
/**
* Register CSS styles of the decoration.
*/
public static registerStyles(
theme: IColorTheme,
collector: ICssStyleCollector,
) {
/**
* TODO: @legomushroom
*/
const frontMatterHeaderBackgroundColor = registerColor(
'chat.prompt.frontMatterBackground',
{ dark: new Color(new RGBA(0, 0, 0, 0.20)), light: new Color(new RGBA(0, 0, 0, 0.10)), hcDark: contrastBorder, hcLight: contrastBorder, },
localize('chat.prompt.frontMatterBackground', "background color of a Front Matter header block."),
);
const styles = [];
styles.push(
`background-color: ${theme.getColor(frontMatterHeaderBackgroundColor)};`,
);
const frontMatterHeaderCssSelector = `.monaco-editor .${FrontMatterCssClassNames.frontMatterHeader}`;
collector.addRule(
`${frontMatterHeaderCssSelector} { ${styles.join(' ')} }`,
);
const inlineInactiveStyles = [];
inlineInactiveStyles.push('color: var(--vscode-disabledForeground);');
const inlineActiveStyles = [];
inlineActiveStyles.push('color: var(--vscode-foreground);');
const frontMatterHeaderInlineActiveCssSelector = `.monaco-editor .${FrontMatterCssClassNames.frontMatterHeaderInlineActive}`;
collector.addRule(
`${frontMatterHeaderInlineActiveCssSelector} { ${inlineActiveStyles.join(' ')} }`,
);
const frontMatterHeaderInlineInactiveCssSelector = `.monaco-editor .${FrontMatterCssClassNames.frontMatterHeaderInlineInactive}`;
collector.addRule(
`${frontMatterHeaderInlineInactiveCssSelector} { ${inlineInactiveStyles.join(' ')} }`,
);
}
}
@@ -0,0 +1,98 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DecorationBase } from './decorationBase.js';
import { Position } from '../../../../../../../../../editor/common/core/position.js';
import { BaseToken } from '../../../../../../../../../editor/common/codecs/baseToken.js';
import { IModelDecorationsChangeAccessor } from '../../../../../../../../../editor/common/model.js';
/**
* Base class for all reactive editor decorations. A reactive decoration
* is a decoration that can change its appearance based on current cursor
* position in the editor, hence can "react" to the user's actions.
*/
export abstract class ReactiveDecorationBase<
TPromptToken extends BaseToken,
TCssClassName extends string = string,
> extends DecorationBase<TPromptToken, TCssClassName> {
/**
* Whether the decoration has changed since the last {@link render}.
*/
public get changed(): boolean {
return this.didChange;
}
/**
* Current position of cursor in the editor.
*/
private cursorPosition?: Position | null;
/**
* Private field for the {@link changed} property.
*/
private didChange = true;
constructor(
accessor: Pick<IModelDecorationsChangeAccessor, 'addDecoration'>,
token: TPromptToken,
) {
super(accessor, token);
}
/**
* Whether cursor is currently inside the decoration range.
*/
protected get active(): boolean {
if (!this.cursorPosition) {
return false;
}
// when cursor is at the end of a range, the range considered to
// not contain the position, but we want to include it
const atEnd = (this.range.endLineNumber === this.cursorPosition.lineNumber)
&& (this.range.endColumn === this.cursorPosition.column);
return atEnd || this.range.containsPosition(this.cursorPosition);
}
/**
* Set cursor position and update {@link changed} property if needed.
*/
public setCursorPosition(position: Position | null | undefined): this is { readonly changed: true } {
if (this.cursorPosition === position) {
return false;
}
if (this.cursorPosition && position) {
if (this.cursorPosition.equals(position)) {
return false;
}
}
const wasActive = this.active;
this.cursorPosition = position;
this.didChange = (wasActive !== this.active);
return this.didChange;
}
public override render(
accessor: Pick<IModelDecorationsChangeAccessor, 'changeDecoration' | 'changeDecorationOptions'>,
): this {
if (this.didChange === false) {
return this;
}
super.render(accessor);
this.didChange = false;
return this;
}
}
/**
* Type for a decorator with {@link ReactiveDecorationBase.changed changed} property set to `true`.
*/
export type TChangedDecorator = ReactiveDecorationBase<BaseToken> & { readonly changed: true };
@@ -0,0 +1,181 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IPromptsService } from '../../../service/types.js';
import { ProviderInstanceBase } from '../providerInstanceBase.js';
import { toDisposable } from '../../../../../../../../base/common/lifecycle.js';
import { DecorationBase, TDecorationClass } from './decorations/decorationBase.js';
import { FrontMatterHeaderDecoration } from './decorations/frontMatterDecoration.js';
import { BaseToken } from '../../../../../../../../editor/common/codecs/baseToken.js';
import { IPromptFileEditor, ProviderInstanceManagerBase } from '../providerInstanceManagerBase.js';
import { ReactiveDecorationBase, TChangedDecorator } from './decorations/reactiveDecorationBase.js';
import { registerThemingParticipant } from '../../../../../../../../platform/theme/common/themeService.js';
import { FrontMatterHeader } from '../../../../../../../../editor/common/codecs/markdownExtensionsCodec/tokens/frontMatterHeader.js';
/**
* Prompt tokens that are decorated by this provider.
*/
type TDecoratedToken = FrontMatterHeader;
/**
* List of all supported decorations.
*/
const SUPPORTED_DECORATIONS: readonly TDecorationClass<TDecoratedToken>[] = Object.freeze([
FrontMatterHeaderDecoration,
]);
/**
* Prompt syntax decorations provider for text models.
*/
export class TextModelPromptDecorator extends ProviderInstanceBase {
/**
* Currently active decorations.
*/
private readonly decorations: DecorationBase<BaseToken>[] = [];
constructor(
editor: IPromptFileEditor,
@IPromptsService promptsService: IPromptsService,
) {
super(editor, promptsService);
this.watchCursorPosition();
}
// TODO: @legomushroom - update existing decorations instead of recreating them every time
protected override async onPromptParserUpdate(): Promise<this> {
await this.parser.allSettled();
this.removeAllDecorations();
this.addDecorations(this.parser.tokens);
return this;
}
/**
* Watch editor cursor position and update reactive decorations accordingly.
*/
private watchCursorPosition(): this {
const interval = setInterval(() => {
const cursorPosition = this.editor.getPosition();
const changedDecorations: TChangedDecorator[] = [];
for (const decoration of this.decorations) {
if ((decoration instanceof ReactiveDecorationBase) === false) {
continue;
}
if (decoration.setCursorPosition(cursorPosition) === true) {
changedDecorations.push(decoration);
}
}
if (changedDecorations.length === 0) {
return;
}
this.changeEditorDecorations(changedDecorations);
}, 25);
this._register(toDisposable(() => {
clearInterval(interval);
}));
return this;
}
/**
*
*/
private changeEditorDecorations(
decorations: readonly TChangedDecorator[],
): this {
this.editor.changeDecorations((accessor) => {
for (const decoration of decorations) {
decoration.render(accessor);
}
});
return this;
}
/**
* Add a decorations for all prompt tokens.
*/
private addDecorations(
tokens: readonly BaseToken[],
): this {
if (tokens.length === 0) {
return this;
}
this.editor.changeDecorations((accessor) => {
for (const token of tokens) {
for (const Decoration of SUPPORTED_DECORATIONS) {
if (Decoration.handles(token) === false) {
continue;
}
this.decorations.push(
new Decoration(accessor, token),
);
break;
}
}
});
return this;
}
/**
* Remove all existing decorations.
*/
private removeAllDecorations(): this {
if (this.decorations.length === 0) {
return this;
}
this.editor.changeDecorations((accessor) => {
for (const decoration of this.decorations) {
decoration.remove(accessor);
}
this.decorations.splice(0);
});
return this;
}
public override dispose(): void {
this.removeAllDecorations();
super.dispose();
}
/**
* Returns a string representation of this object.
*/
public override toString() {
return `text-model-prompt-decorator:${this.model.uri.path}`;
}
}
/**
* Register CSS styles of the supported decorations.
*/
registerThemingParticipant((theme, collector) => {
for (const Decoration of SUPPORTED_DECORATIONS) {
Decoration.registerStyles(theme, collector);
}
});
/**
* Provider for prompt syntax decorators on text models.
*/
export class PromptDecorationsProviderInstanceManager extends ProviderInstanceManagerBase<TextModelPromptDecorator> {
protected override get InstanceClass() {
return TextModelPromptDecorator;
}
}
@@ -6,10 +6,10 @@
import { PromptLinkProvider } from './promptLinkProvider.js';
import { isWindows } from '../../../../../../../base/common/platform.js';
import { PromptPathAutocompletion } from './promptPathAutocompletion.js';
import { PromptDecoratorsInstanceManager } from './textModelPromptDecorator.js';
import { Registry } from '../../../../../../../platform/registry/common/platform.js';
import { LifecyclePhase } from '../../../../../../services/lifecycle/common/lifecycle.js';
import { PromptLinkDiagnosticsInstanceManager } from './promptLinkDiagnosticsProvider.js';
import { PromptDecorationsProviderInstanceManager } from './decorationsProvider/promptDecorationsProvider.js';
import { BrandedService } from '../../../../../../../platform/instantiation/common/instantiation.js';
import { IWorkbenchContributionsRegistry, Extensions, IWorkbenchContribution } from '../../../../../../common/contributions.js';
@@ -18,8 +18,8 @@ import { IWorkbenchContributionsRegistry, Extensions, IWorkbenchContribution } f
*/
export const registerReusablePromptLanguageFeatures = () => {
registerContribution(PromptLinkProvider);
registerContribution(PromptDecoratorsInstanceManager);
registerContribution(PromptLinkDiagnosticsInstanceManager);
registerContribution(PromptDecorationsProviderInstanceManager);
/**
* We restrict this provider to `Unix` machines for now because of
@@ -42,5 +42,4 @@ const registerContribution = <TServices extends BrandedService[]>(
) => {
Registry.as<IWorkbenchContributionsRegistry>(Extensions.Workbench)
.registerWorkbenchContribution(contribution, LifecyclePhase.Eventually);
};
@@ -1,505 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from '../../../../../../../nls.js';
import { IPromptsService } from '../../service/types.js';
import { ProviderInstanceBase } from './providerInstanceBase.js';
import { chatSlashCommandBackground } from '../../../chatColors.js';
import { Color, RGBA } from '../../../../../../../base/common/color.js';
import { assertNever } from '../../../../../../../base/common/assert.js';
import { toDisposable } from '../../../../../../../base/common/lifecycle.js';
import { Position } from '../../../../../../../editor/common/core/position.js';
import { Range, IRange } from '../../../../../../../editor/common/core/range.js';
import { IMarkdownString } from '../../../../../../../base/common/htmlContent.js';
import { BaseToken } from '../../../../../../../editor/common/codecs/baseToken.js';
import { ModelDecorationOptions } from '../../../../../../../editor/common/model/textModel.js';
import { IPromptFileEditor, ProviderInstanceManagerBase } from './providerInstanceManagerBase.js';
import { contrastBorder, registerColor } from '../../../../../../../platform/theme/common/colorRegistry.js';
import { IModelDecorationsChangeAccessor, TrackedRangeStickiness } from '../../../../../../../editor/common/model.js';
import { FrontMatterHeader } from '../../../../../../../editor/common/codecs/markdownExtensionsCodec/tokens/frontMatterHeader.js';
import { IColorTheme, ICssStyleCollector, registerThemingParticipant } from '../../../../../../../platform/theme/common/themeService.js';
/**
* TODO: @legomushroom - list
* - add active/inactive logic for front matter header
*/
/**
* TODO: @legomushroom
*/
abstract class Decoration<TPromptToken extends BaseToken, TCssClassName extends string = string> {
/**
* TODO: @legomushroom
*/
protected abstract get description(): string;
/**
* TODO: @legomushroom
*/
protected abstract get className(): TCssClassName;
/**
* TODO: @legomushroom
*/
protected abstract get inlineClassName(): TCssClassName;
/**
* TODO: @legomushroom
*/
protected get isWholeLine(): boolean {
return false;
}
/**
* TODO: @legomushroom
*/
protected get hoverMessage(): IMarkdownString | IMarkdownString[] | null {
return null;
}
/**
* TODO: @legomushroom
*/
public readonly id: string;
constructor(
accessor: Pick<IModelDecorationsChangeAccessor, 'addDecoration'>,
protected readonly token: TPromptToken,
) {
this.id = accessor.addDecoration(this.range, this.decorationOptions);
}
/**
* Range of the decoration.
*/
public get range(): Range {
return this.token.range;
}
/**
* TODO: @legomushroom
*/
public render(
accessor: Pick<IModelDecorationsChangeAccessor, 'changeDecoration' | 'changeDecorationOptions'>,
): this {
accessor.changeDecorationOptions(
this.id,
this.decorationOptions,
);
return this;
}
/**
* TODO: @legomushroom
*/
protected get decorationOptions(): ModelDecorationOptions {
return ModelDecorationOptions.createDynamic({
description: this.description,
hoverMessage: this.hoverMessage,
className: this.className,
inlineClassName: this.inlineClassName,
isWholeLine: this.isWholeLine,
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
shouldFillLineOnLineBreak: true,
});
}
}
/**
* TODO: @legomushroom
*/
abstract class ReactiveDecoration<TPromptToken extends BaseToken, TCssClassName extends string = string> extends Decoration<TPromptToken, TCssClassName> {
/**
* Whether the decoration has changed since the last {@link render}.
*/
public get changed(): boolean {
return this.didChange;
}
/**
* TODO: @legomushroom
*/
private cursorPosition?: Position | null;
/**
* Private field for the {@link changed} property.
*/
private didChange = true;
constructor(
accessor: Pick<IModelDecorationsChangeAccessor, 'addDecoration'>,
token: TPromptToken,
) {
super(accessor, token);
}
/**
* Whether cursor is currently inside the decoration range.
*/
protected get active(): boolean {
if (!this.cursorPosition) {
return false;
}
// when cursor is at the end of a range, the range considered to
// not contain the position, but we want to include it
const atEnd = (this.range.endLineNumber === this.cursorPosition.lineNumber)
&& (this.range.endColumn === this.cursorPosition.column);
return atEnd || this.range.containsPosition(this.cursorPosition);
}
/**
* TODO: @legomushroom
*/
public setCursorPosition(position: Position | null | undefined): boolean {
if (this.cursorPosition === position) {
return false;
}
if (this.cursorPosition && position) {
if (this.cursorPosition.equals(position)) {
return false;
}
}
const wasActive = this.active;
this.cursorPosition = position;
this.didChange = (wasActive !== this.active);
return this.didChange;
}
public override render(
accessor: Pick<IModelDecorationsChangeAccessor, 'changeDecoration' | 'changeDecorationOptions'>,
): this {
if (this.didChange === false) {
return this;
}
super.render(accessor);
this.didChange = false;
return this;
}
}
/**
* Decoration CSS class names.
*/
export enum FrontMatterCssClassNames {
/**
* TODO: @legomushroom
*/
frontMatterHeader = 'prompt-front-matter-header',
frontMatterHeaderInlineInactive = 'prompt-front-matter-header-inline-inactive',
frontMatterHeaderInlineActive = 'prompt-front-matter-header-inline-active',
}
/**
* TODO: @legomushroom
*/
class FrontMatterHeaderDecoration extends ReactiveDecoration<FrontMatterHeader, FrontMatterCssClassNames> {
protected override get isWholeLine(): boolean {
return true;
}
protected override get description(): string {
return 'Front Matter header decoration.';
}
protected override get className(): FrontMatterCssClassNames.frontMatterHeader {
return FrontMatterCssClassNames.frontMatterHeader;
}
protected override get inlineClassName(): FrontMatterCssClassNames.frontMatterHeaderInlineActive | FrontMatterCssClassNames.frontMatterHeaderInlineInactive {
return (this.active)
? FrontMatterCssClassNames.frontMatterHeaderInlineActive
: FrontMatterCssClassNames.frontMatterHeaderInlineInactive;
}
}
/**
* Decoration object.
*/
export interface ITextModelDecoration {
/**
* Range of the decoration.
*/
range: IRange;
/**
* Associated decoration options.
*/
options: ModelDecorationOptions;
}
/**
* Decoration CSS class names.
*/
export enum DecorationClassNames {
/**
* CSS class name for `default` prompt syntax decoration.
*/
default = 'prompt-decoration',
/**
* CSS class name for `reference` prompt syntax decoration.
*/
reference = 'prompt-reference',
}
/**
* Decoration CSS class name modifiers.
*/
export enum DecorationClassNameModifiers {
/**
* CSS class name for `warning` modifier.
*/
warning = 'squiggly-warning',
/**
* CSS class name for `error` modifier.
*/
error = 'squiggly-error', // TODO: @legomushroom - use "markers" instead?
}
/**
* TODO: @legomushroom
*/
type TDecoratedToken = FrontMatterHeader;
/**
* Prompt syntax decorations provider for text models.
*/
export class TextModelPromptDecorator extends ProviderInstanceBase {
/**
* TODO: @legomushroom
*/
private readonly decorations: Decoration<BaseToken>[] = [];
constructor(
editor: IPromptFileEditor,
@IPromptsService promptsService: IPromptsService,
) {
super(editor, promptsService);
this.watchCursorPosition();
}
/**
* Handler for the prompt parser update event.
*/
// TODO: @legomushroom - update existing decorations instead of recreating them every time
protected override async onPromptParserUpdate(): Promise<this> {
await this.parser.allSettled();
this.removeAllDecorations();
this.addDecorations(this.parser.tokens);
return this;
}
/**
* TODO: @legomushroom
*/
private watchCursorPosition(): this {
const interval = setInterval(() => {
const cursorPosition = this.editor.getPosition();
const changedDecorations: Decoration<BaseToken>[] = [];
for (const decoration of this.decorations) {
if ((decoration instanceof ReactiveDecoration) === false) {
continue;
}
if (decoration.setCursorPosition(cursorPosition) === true) {
changedDecorations.push(decoration);
}
}
if (changedDecorations.length === 0) {
return;
}
this.changeEditorDecorations(changedDecorations);
}, 25);
this._register(toDisposable(() => {
clearInterval(interval);
}));
return this;
}
/**
* TODO: @legomushroom
*/
private changeEditorDecorations(
decorations: readonly Decoration<BaseToken>[],
): this {
this.editor.changeDecorations((accessor) => {
for (const decoration of decorations) {
decoration.render(accessor);
}
});
return this;
}
/**
* Add a decorations for all prompt tokens.
*/
private addDecorations(
tokens: readonly BaseToken[],
): this {
if (tokens.length === 0) {
return this;
}
const decoratedTokens: TDecoratedToken[] = [];
for (const token of tokens) {
if (token instanceof FrontMatterHeader) {
decoratedTokens.push(token);
}
}
if (decoratedTokens.length === 0) {
return this;
}
this.editor.changeDecorations((accessor) => {
for (const token of decoratedTokens) {
if (token instanceof FrontMatterHeader) {
const decoration = new FrontMatterHeaderDecoration(
accessor,
token,
);
this.decorations.push(decoration);
continue;
}
assertNever(
token,
`Unexpected decorated token '${token}'.`,
);
}
});
return this;
}
/**
* Remove all existing decorations.
*/
private removeAllDecorations(): this {
if (this.decorations.length === 0) {
return this;
}
this.editor.changeDecorations((accessor) => {
for (const decoration of this.decorations) {
accessor.removeDecoration(decoration.id);
}
this.decorations.splice(0);
});
return this;
}
/**
* Returns a string representation of this object.
*/
public override toString() {
return `text-model-prompt-decorator:${this.model.uri.path}`;
}
public override dispose(): void {
this.removeAllDecorations();
super.dispose();
}
}
/**
* Register CSS styles.
*/
registerThemingParticipant((theme, collector) => {
const styles = ['border-radius: 3px;'];
const backgroundColor = theme.getColor(chatSlashCommandBackground);
if (backgroundColor) {
styles.push(`background-color: ${backgroundColor};`);
}
// TODO: @legomushroom
// const color = theme.getColor(chatSlashCommandForeground);
// if (color) {
// styles.push(`color: ${color};`);
// }
const defaultCssSelector = `.monaco-editor .${DecorationClassNames.default}`;
collector.addRule(
`${defaultCssSelector} { ${styles.join(' ')} }`,
);
registerFrontMatterStyles(theme, collector);
});
/**
* TODO: @legomushroom
*/
const frontMatterHeaderBackgroundColor = registerColor(
'chat.prompt.frontMatterBackground',
{ dark: new Color(new RGBA(0, 0, 0, 0.20)), light: new Color(new RGBA(0, 0, 0, 0.10)), hcDark: contrastBorder, hcLight: contrastBorder, },
localize('chat.prompt.frontMatterBackground', "background color of a Front Matter header block."),
);
/**
* TODO: @legomushroom
*/
const registerFrontMatterStyles = (
theme: IColorTheme,
collector: ICssStyleCollector,
) => {
const styles = [];
styles.push(
`background-color: ${theme.getColor(frontMatterHeaderBackgroundColor)};`,
);
const frontMatterHeaderCssSelector = `.monaco-editor .${FrontMatterCssClassNames.frontMatterHeader}`;
collector.addRule(
`${frontMatterHeaderCssSelector} { ${styles.join(' ')} }`,
);
const inlineInactiveStyles = [];
inlineInactiveStyles.push('color: var(--vscode-disabledForeground);');
const inlineActiveStyles = [];
inlineActiveStyles.push('color: var(--vscode-foreground);');
const frontMatterHeaderInlineActiveCssSelector = `.monaco-editor .${FrontMatterCssClassNames.frontMatterHeaderInlineActive}`;
collector.addRule(
`${frontMatterHeaderInlineActiveCssSelector} { ${inlineActiveStyles.join(' ')} }`,
);
const frontMatterHeaderInlineInactiveCssSelector = `.monaco-editor .${FrontMatterCssClassNames.frontMatterHeaderInlineInactive}`;
collector.addRule(
`${frontMatterHeaderInlineInactiveCssSelector} { ${inlineInactiveStyles.join(' ')} }`,
);
};
/**
* Provider for prompt syntax decorators on text models.
*/
export class PromptDecoratorsInstanceManager extends ProviderInstanceManagerBase<TextModelPromptDecorator> {
protected override get InstanceClass() {
return TextModelPromptDecorator;
}
}