Merge branch 'main' into dev/mjbvz/format-all

This commit is contained in:
Matt Bierner
2025-09-23 22:18:49 -07:00
6 changed files with 21 additions and 310 deletions

View File

@ -12,14 +12,14 @@
"@eslint/js": "^9.13.0",
"@stylistic/eslint-plugin": "^2.9.0",
"@types/node": "^22",
"@types/vscode": "^1.102.0",
"@types/vscode": "^1.104.0",
"@vscode/dts": "^0.4.1",
"eslint": "^9.13.0",
"typescript": "^5.9.2",
"typescript-eslint": "^8.39.0"
},
"engines": {
"vscode": "^1.103.0-20250721"
"vscode": "^1.104.0-20250828"
}
},
"node_modules/@eslint-community/eslint-utils": {
@ -360,9 +360,9 @@
}
},
"node_modules/@types/vscode": {
"version": "1.102.0",
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.102.0.tgz",
"integrity": "sha512-V9sFXmcXz03FtYTSUsYsu5K0Q9wH9w9V25slddcxrh5JgORD14LpnOA7ov0L9ALi+6HrTjskLJ/tY5zeRF3TFA==",
"version": "1.104.0",
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.104.0.tgz",
"integrity": "sha512-0KwoU2rZ2ecsTGFxo4K1+f+AErRsYW0fsp6A0zufzGuhyczc2IoKqYqcwXidKXmy2u8YB2GsYsOtiI9Izx3Tig==",
"dev": true,
"license": "MIT"
},

View File

@ -1,8 +1,5 @@
{
"name": "chat-model-provider-sample",
"enabledApiProposals": [
"chatProvider"
],
"publisher": "vscode-samples",
"displayName": "Copilot Model Provider Sample",
"description": "Sample extension which provides chat models via the LM API.",
@ -12,17 +9,14 @@
},
"version": "0.1.0",
"engines": {
"vscode": "^1.103.0-20250721"
"vscode": "^1.104"
},
"categories": [
"AI",
"Chat"
],
"activationEvents": [
"onStartupFinished"
],
"contributes": {
"languageModels": [
"languageModelChatProviders": [
{
"vendor": "sample",
"displayName": "Sample Model Vendor"
@ -39,12 +33,13 @@
"lint": "eslint",
"watch": "tsc -watch -p ./"
},
"dependencies": { },
"devDependencies": {
"@eslint/js": "^9.13.0",
"@stylistic/eslint-plugin": "^2.9.0",
"@types/node": "^22",
"@types/vscode": "^1.102.0",
"@vscode/dts": "^0.4.1",
"@types/vscode": "^1.104.0",
"eslint": "^9.13.0",
"typescript": "^5.9.2",
"typescript-eslint": "^8.39.0"

View File

@ -2,7 +2,7 @@ import * as vscode from 'vscode';
import { SampleChatModelProvider } from './provider';
export function activate(_: vscode.ExtensionContext) {
vscode.lm.registerChatModelProvider('sample', new SampleChatModelProvider());
vscode.lm.registerLanguageModelChatProvider('sample', new SampleChatModelProvider());
}
export function deactivate() { }

View File

@ -1,35 +1,35 @@
import { CancellationToken, ChatResponseFragment2, LanguageModelChatInformation, LanguageModelChatMessage, LanguageModelChatProvider2, LanguageModelChatRequestHandleOptions, LanguageModelTextPart, Progress, ProviderResult } from "vscode";
import { CancellationToken, LanguageModelChatInformation, LanguageModelChatMessage, LanguageModelChatProvider, LanguageModelResponsePart, LanguageModelTextPart, Progress, ProvideLanguageModelChatResponseOptions, ProviderResult } from "vscode";
function getChatModelInfo(id: string, name: string): LanguageModelChatInformation {
return {
id,
name,
description: "A sample chat model for demonstration purposes.",
tooltip: "A sample chat model for demonstration purposes.",
family: "sample-family",
maxInputTokens: 120000,
maxOutputTokens: 8192,
version: "1.0.0",
capabilities: {
toolCalling: true,
vision: true,
imageInput: true,
}
};
}
export class SampleChatModelProvider implements LanguageModelChatProvider2 {
prepareLanguageModelChat(_options: { silent: boolean; }, _token: CancellationToken): ProviderResult<LanguageModelChatInformation[]> {
export class SampleChatModelProvider implements LanguageModelChatProvider {
provideLanguageModelChatInformation(_options: { silent: boolean; }, _token: CancellationToken): ProviderResult<LanguageModelChatInformation[]> {
return [
getChatModelInfo("sample-dog-model", "Dog Model"),
getChatModelInfo("sample-cat-model", "Cat Model"),
];
}
async provideLanguageModelChatResponse(model: LanguageModelChatInformation, _messages: Array<LanguageModelChatMessage>, _options: LanguageModelChatRequestHandleOptions, progress: Progress<ChatResponseFragment2>, _token: CancellationToken): Promise<void> {
async provideLanguageModelChatResponse(model: LanguageModelChatInformation, _messages: Array<LanguageModelChatMessage>, _options: ProvideLanguageModelChatResponseOptions, progress: Progress<LanguageModelResponsePart>, _token: CancellationToken): Promise<void> {
if (model.id === "sample-dog-model") {
progress.report({ index: 0, part: new LanguageModelTextPart("Woof! This is a dog model response.") });
progress.report(new LanguageModelTextPart("Woof! This is a dog model response."));
} else if (model.id === "sample-cat-model") {
progress.report({ index: 0, part: new LanguageModelTextPart("Meow! This is a cat model response.") });
progress.report(new LanguageModelTextPart("Meow! This is a cat model response."));
} else {
progress.report({ index: 0, part: new LanguageModelTextPart("Unknown model.") });
progress.report(new LanguageModelTextPart("Unknown model."));
}
}
async provideTokenCount(_model: LanguageModelChatInformation, _text: string | LanguageModelChatMessage, _token: CancellationToken): Promise<number> {

View File

@ -14,10 +14,5 @@
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
},
"include": [
"src/**/*",
"vscode.d.ts",
"vscode.proposed.chatProvider.d.ts"
]
}
}
}

View File

@ -3,24 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextDocument, Position, Range } from 'vscode-languageclient';
import { LanguageService, TokenType } from 'vscode-html-languageservice';
export interface LanguageRange extends Range {
languageId: string | undefined;
attributeValue?: boolean;
}
export interface HTMLDocumentRegions {
getEmbeddedDocument(languageId: string, ignoreAttributeValues?: boolean): TextDocument;
getLanguageRanges(range: Range): LanguageRange[];
getLanguageAtPosition(position: Position): string | undefined;
getLanguagesInDocument(): string[];
getImportedScripts(): string[];
}
export const CSS_STYLE_RULE = '__';
interface EmbeddedRegion {
languageId: string | undefined;
start: number;
@ -143,269 +127,6 @@ export function getCSSVirtualContent(
return content;
}
export function getDocumentRegions(
languageService: LanguageService,
document: TextDocument
): HTMLDocumentRegions {
const regions: EmbeddedRegion[] = [];
const scanner = languageService.createScanner(document.getText());
let lastTagName = '';
let lastAttributeName: string | null = null;
let languageIdFromType: string | undefined = undefined;
const importedScripts: string[] = [];
let token = scanner.scan();
while (token !== TokenType.EOS) {
switch (token) {
case TokenType.StartTag:
lastTagName = scanner.getTokenText();
lastAttributeName = null;
languageIdFromType = 'javascript';
break;
case TokenType.Styles:
regions.push({
languageId: 'css',
start: scanner.getTokenOffset(),
end: scanner.getTokenEnd()
});
break;
case TokenType.Script:
regions.push({
languageId: languageIdFromType,
start: scanner.getTokenOffset(),
end: scanner.getTokenEnd()
});
break;
case TokenType.AttributeName:
lastAttributeName = scanner.getTokenText();
break;
case TokenType.AttributeValue:
if (lastAttributeName === 'src' && lastTagName.toLowerCase() === 'script') {
let value = scanner.getTokenText();
if (value[0] === "'" || value[0] === '"') {
value = value.substr(1, value.length - 1);
}
importedScripts.push(value);
} else if (lastAttributeName === 'type' && lastTagName.toLowerCase() === 'script') {
if (
/["'](module|(text|application)\/(java|ecma)script|text\/babel)["']/.test(
scanner.getTokenText()
)
) {
languageIdFromType = 'javascript';
} else if (/["']text\/typescript["']/.test(scanner.getTokenText())) {
languageIdFromType = 'typescript';
} else {
languageIdFromType = undefined;
}
} else {
const attributeLanguageId = getAttributeLanguage(lastAttributeName!);
if (attributeLanguageId) {
let start = scanner.getTokenOffset();
let end = scanner.getTokenEnd();
const firstChar = document.getText()[start];
if (firstChar === "'" || firstChar === '"') {
start++;
end--;
}
regions.push({
languageId: attributeLanguageId,
start,
end,
attributeValue: true
});
}
}
lastAttributeName = null;
break;
}
token = scanner.scan();
}
return {
getLanguageRanges: (range: Range) => getLanguageRanges(document, regions, range),
getEmbeddedDocument: (languageId: string, ignoreAttributeValues: boolean) =>
getEmbeddedDocument(document, regions, languageId, ignoreAttributeValues),
getLanguageAtPosition: (position: Position) =>
getLanguageAtPosition(document, regions, position),
getLanguagesInDocument: () => getLanguagesInDocument(document, regions),
getImportedScripts: () => importedScripts
};
}
function getLanguageRanges(
document: TextDocument,
regions: EmbeddedRegion[],
range: Range
): LanguageRange[] {
const result: LanguageRange[] = [];
let currentPos = range ? range.start : Position.create(0, 0);
let currentOffset = range ? document.offsetAt(range.start) : 0;
const endOffset = range ? document.offsetAt(range.end) : document.getText().length;
for (const region of regions) {
if (region.end > currentOffset && region.start < endOffset) {
const start = Math.max(region.start, currentOffset);
const startPos = document.positionAt(start);
if (currentOffset < region.start) {
result.push({
start: currentPos,
end: startPos,
languageId: 'html'
});
}
const end = Math.min(region.end, endOffset);
const endPos = document.positionAt(end);
if (end > region.start) {
result.push({
start: startPos,
end: endPos,
languageId: region.languageId,
attributeValue: region.attributeValue
});
}
currentOffset = end;
currentPos = endPos;
}
}
if (currentOffset < endOffset) {
const endPos = range ? range.end : document.positionAt(endOffset);
result.push({
start: currentPos,
end: endPos,
languageId: 'html'
});
}
return result;
}
function getLanguagesInDocument(
_document: TextDocument,
regions: EmbeddedRegion[]
): string[] {
const result = [];
for (const region of regions) {
if (region.languageId && result.indexOf(region.languageId) === -1) {
result.push(region.languageId);
if (result.length === 3) {
return result;
}
}
}
result.push('html');
return result;
}
function getLanguageAtPosition(
document: TextDocument,
regions: EmbeddedRegion[],
position: Position
): string | undefined {
const offset = document.offsetAt(position);
for (const region of regions) {
if (region.start <= offset) {
if (offset <= region.end) {
return region.languageId;
}
} else {
break;
}
}
return 'html';
}
function getEmbeddedDocument(
document: TextDocument,
contents: EmbeddedRegion[],
languageId: string,
ignoreAttributeValues: boolean
): TextDocument {
let currentPos = 0;
const oldContent = document.getText();
let result = '';
let lastSuffix = '';
for (const c of contents) {
if (c.languageId === languageId && (!ignoreAttributeValues || !c.attributeValue)) {
result = substituteWithWhitespace(
result,
currentPos,
c.start,
oldContent,
lastSuffix,
getPrefix(c)
);
result += oldContent.substring(c.start, c.end);
currentPos = c.end;
lastSuffix = getSuffix(c);
}
}
result = substituteWithWhitespace(
result,
currentPos,
oldContent.length,
oldContent,
lastSuffix,
''
);
return TextDocument.create(document.uri, languageId, document.version, result);
}
function getPrefix(c: EmbeddedRegion) {
if (c.attributeValue) {
switch (c.languageId) {
case 'css':
return CSS_STYLE_RULE + '{';
}
}
return '';
}
function getSuffix(c: EmbeddedRegion) {
if (c.attributeValue) {
switch (c.languageId) {
case 'css':
return '}';
case 'javascript':
return ';';
}
}
return '';
}
function substituteWithWhitespace(
result: string,
start: number,
end: number,
oldContent: string,
before: string,
after: string
) {
let accumulatedWS = 0;
result += before;
for (let i = start + before.length; i < end; i++) {
const ch = oldContent[i];
if (ch === '\n' || ch === '\r') {
// only write new lines, skip the whitespace
accumulatedWS = 0;
result += ch;
} else {
accumulatedWS++;
}
}
result = append(result, ' ', accumulatedWS - after.length);
result += after;
return result;
}
function append(result: string, str: string, n: number): string {
while (n > 0) {
if (n & 1) {
result += str;
}
n >>= 1;
str += str;
}
return result;
}
function getAttributeLanguage(attributeName: string): string | null {
const match = attributeName.match(/^(style)$|^(on\w+)$/i);
if (!match) {