mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-24 20:26:08 +00:00
Check for cyclic dependencies during compile (#235808)
* Check for cyclic dependencies during compile Changes gulp-tsb to check the emitted JS code for cyclic dependencies. Historically we never cared about cycles between TS files as long as they dissappeared after compile (e.g type-dependencies, not runtime dependencies) https://github.com/microsoft/vscode-internalbacklog/issues/5271 * fix cycling dependencies fyi @aeschli @aiday-mar * remove cyclic dependency with unused `BasedTextEdit` fyi @hediet * remove cycle between chatEditService and chatEditingSession fyi @alexdima * remove cyclic dependency between chatSetup and chatViewPane fyi @roblourens * better cycle detection * don't check cycles when not needed * clear graph when reprocessing file dependencies * remove cycle between with `notebookChatEditController` fyi @DonJayamanne * modernize and cleanup tsb/utils
This commit is contained in:
@@ -26,6 +26,8 @@ function normalize(path) {
|
||||
function createTypeScriptBuilder(config, projectFile, cmd) {
|
||||
const _log = config.logFn;
|
||||
const host = new LanguageServiceHost(cmd, projectFile, _log);
|
||||
const outHost = new LanguageServiceHost({ ...cmd, options: { ...cmd.options, sourceRoot: cmd.options.outDir } }, cmd.options.outDir ?? '', _log);
|
||||
let lastCycleCheckVersion;
|
||||
const service = ts.createLanguageService(host, ts.createDocumentRegistry());
|
||||
const lastBuildVersion = Object.create(null);
|
||||
const lastDtsHash = Object.create(null);
|
||||
@@ -251,6 +253,11 @@ function createTypeScriptBuilder(config, projectFile, cmd) {
|
||||
lastDtsHash[fileName] = value.signature;
|
||||
filesWithChangedSignature.push(fileName);
|
||||
}
|
||||
// line up for cycle check
|
||||
const jsValue = value.files.find(candidate => candidate.basename.endsWith('.js'));
|
||||
if (jsValue) {
|
||||
outHost.addScriptSnapshot(jsValue.path, new ScriptSnapshot(String(jsValue.contents), new Date()));
|
||||
}
|
||||
}).catch(e => {
|
||||
// can't just skip this or make a result up..
|
||||
host.error(`ERROR emitting ${fileName}`);
|
||||
@@ -341,16 +348,37 @@ function createTypeScriptBuilder(config, projectFile, cmd) {
|
||||
});
|
||||
}
|
||||
workOnNext();
|
||||
}).then(() => {
|
||||
// check for cyclic dependencies
|
||||
const thisCycleCheckVersion = outHost.getProjectVersion();
|
||||
if (thisCycleCheckVersion === lastCycleCheckVersion) {
|
||||
return;
|
||||
}
|
||||
const oneCycle = outHost.hasCyclicDependency();
|
||||
lastCycleCheckVersion = thisCycleCheckVersion;
|
||||
delete oldErrors[projectFile];
|
||||
if (oneCycle) {
|
||||
const cycleError = {
|
||||
category: ts.DiagnosticCategory.Error,
|
||||
code: 1,
|
||||
file: undefined,
|
||||
start: undefined,
|
||||
length: undefined,
|
||||
messageText: `CYCLIC dependency between ${oneCycle}`
|
||||
};
|
||||
onError(cycleError);
|
||||
newErrors[projectFile] = [cycleError];
|
||||
}
|
||||
}).then(() => {
|
||||
// store the build versions to not rebuilt the next time
|
||||
newLastBuildVersion.forEach((value, key) => {
|
||||
lastBuildVersion[key] = value;
|
||||
});
|
||||
// print old errors and keep them
|
||||
utils.collections.forEach(oldErrors, entry => {
|
||||
entry.value.forEach(diag => onError(diag));
|
||||
newErrors[entry.key] = entry.value;
|
||||
});
|
||||
for (const [key, value] of Object.entries(oldErrors)) {
|
||||
value.forEach(diag => onError(diag));
|
||||
newErrors[key] = value;
|
||||
}
|
||||
oldErrors = newErrors;
|
||||
// print stats
|
||||
const headNow = process.memoryUsage().heapUsed;
|
||||
@@ -415,7 +443,7 @@ class LanguageServiceHost {
|
||||
this._snapshots = Object.create(null);
|
||||
this._filesInProject = new Set(_cmdLine.fileNames);
|
||||
this._filesAdded = new Set();
|
||||
this._dependencies = new utils.graph.Graph(s => s);
|
||||
this._dependencies = new utils.graph.Graph();
|
||||
this._dependenciesRecomputeList = [];
|
||||
this._fileNameToDeclaredModule = Object.create(null);
|
||||
this._projectVersion = 1;
|
||||
@@ -478,10 +506,6 @@ class LanguageServiceHost {
|
||||
}
|
||||
if (!old || old.getVersion() !== snapshot.getVersion()) {
|
||||
this._dependenciesRecomputeList.push(filename);
|
||||
const node = this._dependencies.lookup(filename);
|
||||
if (node) {
|
||||
node.outgoing = Object.create(null);
|
||||
}
|
||||
// (cheap) check for declare module
|
||||
LanguageServiceHost._declareModule.lastIndex = 0;
|
||||
let match;
|
||||
@@ -523,9 +547,19 @@ class LanguageServiceHost {
|
||||
filename = normalize(filename);
|
||||
const node = this._dependencies.lookup(filename);
|
||||
if (node) {
|
||||
utils.collections.forEach(node.incoming, entry => target.push(entry.key));
|
||||
node.incoming.forEach(entry => target.push(entry.data));
|
||||
}
|
||||
}
|
||||
hasCyclicDependency() {
|
||||
// Ensure dependencies are up to date
|
||||
while (this._dependenciesRecomputeList.length) {
|
||||
this._processFile(this._dependenciesRecomputeList.pop());
|
||||
}
|
||||
const cycle = this._dependencies.findCycle();
|
||||
return cycle
|
||||
? cycle.join(' -> ')
|
||||
: undefined;
|
||||
}
|
||||
_processFile(filename) {
|
||||
if (filename.match(/.*\.d\.ts$/)) {
|
||||
return;
|
||||
@@ -537,6 +571,8 @@ class LanguageServiceHost {
|
||||
return;
|
||||
}
|
||||
const info = ts.preProcessFile(snapshot.getText(0, snapshot.getLength()), true);
|
||||
// (0) clear out old dependencies
|
||||
this._dependencies.resetNode(filename);
|
||||
// (1) ///-references
|
||||
info.referencedFiles.forEach(ref => {
|
||||
const resolvedPath = path.resolve(path.dirname(filename), ref.fileName);
|
||||
@@ -545,6 +581,10 @@ class LanguageServiceHost {
|
||||
});
|
||||
// (2) import-require statements
|
||||
info.importedFiles.forEach(ref => {
|
||||
if (!ref.fileName.startsWith('.') || path.extname(ref.fileName) === '') {
|
||||
// node module?
|
||||
return;
|
||||
}
|
||||
const stopDirname = normalize(this.getCurrentDirectory());
|
||||
let dirname = filename;
|
||||
let found = false;
|
||||
@@ -563,6 +603,10 @@ class LanguageServiceHost {
|
||||
this._dependencies.inertEdge(filename, normalizedPath + '.d.ts');
|
||||
found = true;
|
||||
}
|
||||
else if (this.getScriptSnapshot(normalizedPath + '.js')) {
|
||||
this._dependencies.inertEdge(filename, normalizedPath + '.js');
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
for (const key in this._fileNameToDeclaredModule) {
|
||||
|
||||
@@ -42,6 +42,10 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str
|
||||
const _log = config.logFn;
|
||||
|
||||
const host = new LanguageServiceHost(cmd, projectFile, _log);
|
||||
|
||||
const outHost = new LanguageServiceHost({ ...cmd, options: { ...cmd.options, sourceRoot: cmd.options.outDir } }, cmd.options.outDir ?? '', _log);
|
||||
let lastCycleCheckVersion: string;
|
||||
|
||||
const service = ts.createLanguageService(host, ts.createDocumentRegistry());
|
||||
const lastBuildVersion: { [path: string]: string } = Object.create(null);
|
||||
const lastDtsHash: { [path: string]: string } = Object.create(null);
|
||||
@@ -305,6 +309,13 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str
|
||||
lastDtsHash[fileName] = value.signature;
|
||||
filesWithChangedSignature.push(fileName);
|
||||
}
|
||||
|
||||
// line up for cycle check
|
||||
const jsValue = value.files.find(candidate => candidate.basename.endsWith('.js'));
|
||||
if (jsValue) {
|
||||
outHost.addScriptSnapshot(jsValue.path, new ScriptSnapshot(String(jsValue.contents), new Date()));
|
||||
}
|
||||
|
||||
}).catch(e => {
|
||||
// can't just skip this or make a result up..
|
||||
host.error(`ERROR emitting ${fileName}`);
|
||||
@@ -389,6 +400,7 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// (last) done
|
||||
else {
|
||||
resolve();
|
||||
@@ -410,16 +422,40 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str
|
||||
workOnNext();
|
||||
|
||||
}).then(() => {
|
||||
// check for cyclic dependencies
|
||||
const thisCycleCheckVersion = outHost.getProjectVersion();
|
||||
if (thisCycleCheckVersion === lastCycleCheckVersion) {
|
||||
return;
|
||||
}
|
||||
const oneCycle = outHost.hasCyclicDependency();
|
||||
lastCycleCheckVersion = thisCycleCheckVersion;
|
||||
delete oldErrors[projectFile];
|
||||
|
||||
if (oneCycle) {
|
||||
const cycleError: ts.Diagnostic = {
|
||||
category: ts.DiagnosticCategory.Error,
|
||||
code: 1,
|
||||
file: undefined,
|
||||
start: undefined,
|
||||
length: undefined,
|
||||
messageText: `CYCLIC dependency between ${oneCycle}`
|
||||
};
|
||||
onError(cycleError);
|
||||
newErrors[projectFile] = [cycleError];
|
||||
}
|
||||
|
||||
}).then(() => {
|
||||
|
||||
// store the build versions to not rebuilt the next time
|
||||
newLastBuildVersion.forEach((value, key) => {
|
||||
lastBuildVersion[key] = value;
|
||||
});
|
||||
|
||||
// print old errors and keep them
|
||||
utils.collections.forEach(oldErrors, entry => {
|
||||
entry.value.forEach(diag => onError(diag));
|
||||
newErrors[entry.key] = entry.value;
|
||||
});
|
||||
for (const [key, value] of Object.entries(oldErrors)) {
|
||||
value.forEach(diag => onError(diag));
|
||||
newErrors[key] = value;
|
||||
}
|
||||
oldErrors = newErrors;
|
||||
|
||||
// print stats
|
||||
@@ -503,7 +539,7 @@ class LanguageServiceHost implements ts.LanguageServiceHost {
|
||||
this._snapshots = Object.create(null);
|
||||
this._filesInProject = new Set(_cmdLine.fileNames);
|
||||
this._filesAdded = new Set();
|
||||
this._dependencies = new utils.graph.Graph<string>(s => s);
|
||||
this._dependencies = new utils.graph.Graph<string>();
|
||||
this._dependenciesRecomputeList = [];
|
||||
this._fileNameToDeclaredModule = Object.create(null);
|
||||
|
||||
@@ -576,10 +612,6 @@ class LanguageServiceHost implements ts.LanguageServiceHost {
|
||||
}
|
||||
if (!old || old.getVersion() !== snapshot.getVersion()) {
|
||||
this._dependenciesRecomputeList.push(filename);
|
||||
const node = this._dependencies.lookup(filename);
|
||||
if (node) {
|
||||
node.outgoing = Object.create(null);
|
||||
}
|
||||
|
||||
// (cheap) check for declare module
|
||||
LanguageServiceHost._declareModule.lastIndex = 0;
|
||||
@@ -628,10 +660,21 @@ class LanguageServiceHost implements ts.LanguageServiceHost {
|
||||
filename = normalize(filename);
|
||||
const node = this._dependencies.lookup(filename);
|
||||
if (node) {
|
||||
utils.collections.forEach(node.incoming, entry => target.push(entry.key));
|
||||
node.incoming.forEach(entry => target.push(entry.data));
|
||||
}
|
||||
}
|
||||
|
||||
hasCyclicDependency(): string | undefined {
|
||||
// Ensure dependencies are up to date
|
||||
while (this._dependenciesRecomputeList.length) {
|
||||
this._processFile(this._dependenciesRecomputeList.pop()!);
|
||||
}
|
||||
const cycle = this._dependencies.findCycle();
|
||||
return cycle
|
||||
? cycle.join(' -> ')
|
||||
: undefined;
|
||||
}
|
||||
|
||||
_processFile(filename: string): void {
|
||||
if (filename.match(/.*\.d\.ts$/)) {
|
||||
return;
|
||||
@@ -644,6 +687,9 @@ class LanguageServiceHost implements ts.LanguageServiceHost {
|
||||
}
|
||||
const info = ts.preProcessFile(snapshot.getText(0, snapshot.getLength()), true);
|
||||
|
||||
// (0) clear out old dependencies
|
||||
this._dependencies.resetNode(filename);
|
||||
|
||||
// (1) ///-references
|
||||
info.referencedFiles.forEach(ref => {
|
||||
const resolvedPath = path.resolve(path.dirname(filename), ref.fileName);
|
||||
@@ -654,10 +700,18 @@ class LanguageServiceHost implements ts.LanguageServiceHost {
|
||||
|
||||
// (2) import-require statements
|
||||
info.importedFiles.forEach(ref => {
|
||||
|
||||
if (!ref.fileName.startsWith('.') || path.extname(ref.fileName) === '') {
|
||||
// node module?
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const stopDirname = normalize(this.getCurrentDirectory());
|
||||
let dirname = filename;
|
||||
let found = false;
|
||||
|
||||
|
||||
while (!found && dirname.indexOf(stopDirname) === 0) {
|
||||
dirname = path.dirname(dirname);
|
||||
let resolvedPath = path.resolve(dirname, ref.fileName);
|
||||
@@ -673,6 +727,10 @@ class LanguageServiceHost implements ts.LanguageServiceHost {
|
||||
} else if (this.getScriptSnapshot(normalizedPath + '.d.ts')) {
|
||||
this._dependencies.inertEdge(filename, normalizedPath + '.d.ts');
|
||||
found = true;
|
||||
|
||||
} else if (this.getScriptSnapshot(normalizedPath + '.js')) {
|
||||
this._dependencies.inertEdge(filename, normalizedPath + '.js');
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,54 +4,9 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.graph = exports.strings = exports.collections = void 0;
|
||||
var collections;
|
||||
(function (collections) {
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
function lookup(collection, key) {
|
||||
if (hasOwnProperty.call(collection, key)) {
|
||||
return collection[key];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
collections.lookup = lookup;
|
||||
function insert(collection, key, value) {
|
||||
collection[key] = value;
|
||||
}
|
||||
collections.insert = insert;
|
||||
function lookupOrInsert(collection, key, value) {
|
||||
if (hasOwnProperty.call(collection, key)) {
|
||||
return collection[key];
|
||||
}
|
||||
else {
|
||||
collection[key] = value;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
collections.lookupOrInsert = lookupOrInsert;
|
||||
function forEach(collection, callback) {
|
||||
for (const key in collection) {
|
||||
if (hasOwnProperty.call(collection, key)) {
|
||||
callback({
|
||||
key: key,
|
||||
value: collection[key]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
collections.forEach = forEach;
|
||||
function contains(collection, key) {
|
||||
return hasOwnProperty.call(collection, key);
|
||||
}
|
||||
collections.contains = contains;
|
||||
})(collections || (exports.collections = collections = {}));
|
||||
exports.graph = exports.strings = void 0;
|
||||
var strings;
|
||||
(function (strings) {
|
||||
/**
|
||||
* The empty string. The one and only.
|
||||
*/
|
||||
strings.empty = '';
|
||||
strings.eolUnix = '\r\n';
|
||||
function format(value, ...rest) {
|
||||
return value.replace(/({\d+})/g, function (match) {
|
||||
const index = Number(match.substring(1, match.length - 1));
|
||||
@@ -62,63 +17,83 @@ var strings;
|
||||
})(strings || (exports.strings = strings = {}));
|
||||
var graph;
|
||||
(function (graph) {
|
||||
function newNode(data) {
|
||||
return {
|
||||
data: data,
|
||||
incoming: {},
|
||||
outgoing: {}
|
||||
};
|
||||
class Node {
|
||||
data;
|
||||
incoming = new Map();
|
||||
outgoing = new Map();
|
||||
constructor(data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
graph.newNode = newNode;
|
||||
graph.Node = Node;
|
||||
class Graph {
|
||||
_hashFn;
|
||||
_nodes = {};
|
||||
constructor(_hashFn) {
|
||||
this._hashFn = _hashFn;
|
||||
// empty
|
||||
}
|
||||
traverse(start, inwards, callback) {
|
||||
const startNode = this.lookup(start);
|
||||
if (!startNode) {
|
||||
return;
|
||||
}
|
||||
this._traverse(startNode, inwards, {}, callback);
|
||||
}
|
||||
_traverse(node, inwards, seen, callback) {
|
||||
const key = this._hashFn(node.data);
|
||||
if (collections.contains(seen, key)) {
|
||||
return;
|
||||
}
|
||||
seen[key] = true;
|
||||
callback(node.data);
|
||||
const nodes = inwards ? node.outgoing : node.incoming;
|
||||
collections.forEach(nodes, (entry) => this._traverse(entry.value, inwards, seen, callback));
|
||||
}
|
||||
_nodes = new Map();
|
||||
inertEdge(from, to) {
|
||||
const fromNode = this.lookupOrInsertNode(from);
|
||||
const toNode = this.lookupOrInsertNode(to);
|
||||
fromNode.outgoing[this._hashFn(to)] = toNode;
|
||||
toNode.incoming[this._hashFn(from)] = fromNode;
|
||||
fromNode.outgoing.set(toNode.data, toNode);
|
||||
toNode.incoming.set(fromNode.data, fromNode);
|
||||
}
|
||||
removeNode(data) {
|
||||
const key = this._hashFn(data);
|
||||
delete this._nodes[key];
|
||||
collections.forEach(this._nodes, (entry) => {
|
||||
delete entry.value.outgoing[key];
|
||||
delete entry.value.incoming[key];
|
||||
});
|
||||
resetNode(data) {
|
||||
const node = this._nodes.get(data);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
for (const outDep of node.outgoing.values()) {
|
||||
outDep.incoming.delete(node.data);
|
||||
}
|
||||
node.outgoing.clear();
|
||||
}
|
||||
lookupOrInsertNode(data) {
|
||||
const key = this._hashFn(data);
|
||||
let node = collections.lookup(this._nodes, key);
|
||||
let node = this._nodes.get(data);
|
||||
if (!node) {
|
||||
node = newNode(data);
|
||||
this._nodes[key] = node;
|
||||
node = new Node(data);
|
||||
this._nodes.set(data, node);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
lookup(data) {
|
||||
return collections.lookup(this._nodes, this._hashFn(data));
|
||||
return this._nodes.get(data) ?? null;
|
||||
}
|
||||
findCycle() {
|
||||
let result;
|
||||
let foundStartNodes = false;
|
||||
const checked = new Set();
|
||||
for (const [_start, value] of this._nodes) {
|
||||
if (Object.values(value.incoming).length > 0) {
|
||||
continue;
|
||||
}
|
||||
foundStartNodes = true;
|
||||
const dfs = (node, visited) => {
|
||||
if (checked.has(node)) {
|
||||
return;
|
||||
}
|
||||
if (visited.has(node)) {
|
||||
result = [...visited, node].map(n => n.data);
|
||||
const idx = result.indexOf(node.data);
|
||||
result = result.slice(idx);
|
||||
return;
|
||||
}
|
||||
visited.add(node);
|
||||
for (const child of Object.values(node.outgoing)) {
|
||||
dfs(child, visited);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
visited.delete(node);
|
||||
checked.add(node);
|
||||
};
|
||||
dfs(value, new Set());
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundStartNodes) {
|
||||
// everything is a cycle
|
||||
return Array.from(this._nodes.keys());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
graph.Graph = Graph;
|
||||
|
||||
@@ -3,54 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export module collections {
|
||||
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
export function lookup<T>(collection: { [keys: string]: T }, key: string): T | null {
|
||||
if (hasOwnProperty.call(collection, key)) {
|
||||
return collection[key];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function insert<T>(collection: { [keys: string]: T }, key: string, value: T): void {
|
||||
collection[key] = value;
|
||||
}
|
||||
|
||||
export function lookupOrInsert<T>(collection: { [keys: string]: T }, key: string, value: T): T {
|
||||
if (hasOwnProperty.call(collection, key)) {
|
||||
return collection[key];
|
||||
} else {
|
||||
collection[key] = value;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
export function forEach<T>(collection: { [keys: string]: T }, callback: (entry: { key: string; value: T }) => void): void {
|
||||
for (const key in collection) {
|
||||
if (hasOwnProperty.call(collection, key)) {
|
||||
callback({
|
||||
key: key,
|
||||
value: collection[key]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function contains(collection: { [keys: string]: any }, key: string): boolean {
|
||||
return hasOwnProperty.call(collection, key);
|
||||
}
|
||||
}
|
||||
|
||||
export module strings {
|
||||
|
||||
/**
|
||||
* The empty string. The one and only.
|
||||
*/
|
||||
export const empty = '';
|
||||
|
||||
export const eolUnix = '\r\n';
|
||||
export namespace strings {
|
||||
|
||||
export function format(value: string, ...rest: any[]): string {
|
||||
return value.replace(/({\d+})/g, function (match) {
|
||||
@@ -60,80 +13,104 @@ export module strings {
|
||||
}
|
||||
}
|
||||
|
||||
export module graph {
|
||||
export namespace graph {
|
||||
|
||||
export interface Node<T> {
|
||||
data: T;
|
||||
incoming: { [key: string]: Node<T> };
|
||||
outgoing: { [key: string]: Node<T> };
|
||||
}
|
||||
export class Node<T> {
|
||||
|
||||
export function newNode<T>(data: T): Node<T> {
|
||||
return {
|
||||
data: data,
|
||||
incoming: {},
|
||||
outgoing: {}
|
||||
};
|
||||
readonly incoming = new Map<T, Node<T>>();
|
||||
readonly outgoing = new Map<T, Node<T>>();
|
||||
|
||||
constructor(readonly data: T) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class Graph<T> {
|
||||
|
||||
private _nodes: { [key: string]: Node<T> } = {};
|
||||
|
||||
constructor(private _hashFn: (element: T) => string) {
|
||||
// empty
|
||||
}
|
||||
|
||||
traverse(start: T, inwards: boolean, callback: (data: T) => void): void {
|
||||
const startNode = this.lookup(start);
|
||||
if (!startNode) {
|
||||
return;
|
||||
}
|
||||
this._traverse(startNode, inwards, {}, callback);
|
||||
}
|
||||
|
||||
private _traverse(node: Node<T>, inwards: boolean, seen: { [key: string]: boolean }, callback: (data: T) => void): void {
|
||||
const key = this._hashFn(node.data);
|
||||
if (collections.contains(seen, key)) {
|
||||
return;
|
||||
}
|
||||
seen[key] = true;
|
||||
callback(node.data);
|
||||
const nodes = inwards ? node.outgoing : node.incoming;
|
||||
collections.forEach(nodes, (entry) => this._traverse(entry.value, inwards, seen, callback));
|
||||
}
|
||||
private _nodes = new Map<T, Node<T>>();
|
||||
|
||||
inertEdge(from: T, to: T): void {
|
||||
const fromNode = this.lookupOrInsertNode(from);
|
||||
const toNode = this.lookupOrInsertNode(to);
|
||||
|
||||
fromNode.outgoing[this._hashFn(to)] = toNode;
|
||||
toNode.incoming[this._hashFn(from)] = fromNode;
|
||||
fromNode.outgoing.set(toNode.data, toNode);
|
||||
toNode.incoming.set(fromNode.data, fromNode);
|
||||
}
|
||||
|
||||
removeNode(data: T): void {
|
||||
const key = this._hashFn(data);
|
||||
delete this._nodes[key];
|
||||
collections.forEach(this._nodes, (entry) => {
|
||||
delete entry.value.outgoing[key];
|
||||
delete entry.value.incoming[key];
|
||||
});
|
||||
resetNode(data: T): void {
|
||||
const node = this._nodes.get(data);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
for (const outDep of node.outgoing.values()) {
|
||||
outDep.incoming.delete(node.data);
|
||||
}
|
||||
node.outgoing.clear();
|
||||
}
|
||||
|
||||
lookupOrInsertNode(data: T): Node<T> {
|
||||
const key = this._hashFn(data);
|
||||
let node = collections.lookup(this._nodes, key);
|
||||
let node = this._nodes.get(data);
|
||||
|
||||
if (!node) {
|
||||
node = newNode(data);
|
||||
this._nodes[key] = node;
|
||||
node = new Node(data);
|
||||
this._nodes.set(data, node);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
lookup(data: T): Node<T> | null {
|
||||
return collections.lookup(this._nodes, this._hashFn(data));
|
||||
return this._nodes.get(data) ?? null;
|
||||
}
|
||||
|
||||
findCycle(): T[] | undefined {
|
||||
|
||||
let result: T[] | undefined;
|
||||
let foundStartNodes = false;
|
||||
const checked = new Set<Node<T>>();
|
||||
|
||||
for (const [_start, value] of this._nodes) {
|
||||
|
||||
if (Object.values(value.incoming).length > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foundStartNodes = true;
|
||||
|
||||
const dfs = (node: Node<T>, visited: Set<Node<T>>) => {
|
||||
|
||||
if (checked.has(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (visited.has(node)) {
|
||||
result = [...visited, node].map(n => n.data);
|
||||
const idx = result.indexOf(node.data);
|
||||
result = result.slice(idx);
|
||||
return;
|
||||
}
|
||||
visited.add(node);
|
||||
for (const child of Object.values(node.outgoing)) {
|
||||
dfs(child, visited);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
visited.delete(node);
|
||||
checked.add(node);
|
||||
};
|
||||
dfs(value, new Set());
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundStartNodes) {
|
||||
// everything is a cycle
|
||||
return Array.from(this._nodes.keys());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import { assert, assertFn, checkAdjacentItems } from '../../../base/common/asser
|
||||
import { BugIndicatingError } from '../../../base/common/errors.js';
|
||||
import { commonPrefixLength, commonSuffixLength, splitLines } from '../../../base/common/strings.js';
|
||||
import { ISingleEditOperation } from './editOperation.js';
|
||||
import { LineEdit } from './lineEdit.js';
|
||||
import { LineRange } from './lineRange.js';
|
||||
import { OffsetEdit } from './offsetEdit.js';
|
||||
import { Position } from './position.js';
|
||||
@@ -399,16 +398,3 @@ export class StringText extends AbstractText {
|
||||
return this._t.textLength;
|
||||
}
|
||||
}
|
||||
|
||||
export class BasedTextEdit {
|
||||
constructor(
|
||||
public readonly base: AbstractText,
|
||||
public readonly edit: TextEdit,
|
||||
) {
|
||||
}
|
||||
|
||||
toString() {
|
||||
const lineEdit = LineEdit.fromTextEdit(this.edit, this.base);
|
||||
return lineEdit.humanReadablePatch(this.base.getLines());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IPartialEditorMouseEvent, MouseTargetType } from '../../../../browser/editorBrowser.js';
|
||||
import { ColorDecorationInjectedTextMarker } from '../colorDetector.js';
|
||||
|
||||
|
||||
export function isOnColorDecorator(mouseEvent: IPartialEditorMouseEvent): boolean {
|
||||
const target = mouseEvent.target;
|
||||
return !!target
|
||||
&& target.type === MouseTargetType.CONTENT_TEXT
|
||||
&& target.detail.injectedText?.options.attachedData === ColorDecorationInjectedTextMarker;
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from '../../../../../base/common/lifecycle.js';
|
||||
import { ICodeEditor, IEditorMouseEvent, IPartialEditorMouseEvent, MouseTargetType } from '../../../../browser/editorBrowser.js';
|
||||
import { ICodeEditor, IEditorMouseEvent } from '../../../../browser/editorBrowser.js';
|
||||
import { EditorOption } from '../../../../common/config/editorOptions.js';
|
||||
import { Range } from '../../../../common/core/range.js';
|
||||
import { IEditorContribution } from '../../../../common/editorCommon.js';
|
||||
import { ColorDecorationInjectedTextMarker } from '../colorDetector.js';
|
||||
import { ContentHoverController } from '../../../hover/browser/contentHoverController.js';
|
||||
import { HoverStartMode, HoverStartSource } from '../../../hover/browser/hoverOperation.js';
|
||||
import { isOnColorDecorator } from './hoverColorPicker.js';
|
||||
|
||||
export class HoverColorPickerContribution extends Disposable implements IEditorContribution {
|
||||
|
||||
@@ -52,10 +52,3 @@ export class HoverColorPickerContribution extends Disposable implements IEditorC
|
||||
hoverController.showContentHover(range, HoverStartMode.Immediate, HoverStartSource.Click, false);
|
||||
}
|
||||
}
|
||||
|
||||
export function isOnColorDecorator(mouseEvent: IPartialEditorMouseEvent): boolean {
|
||||
const target = mouseEvent.target;
|
||||
return !!target
|
||||
&& target.type === MouseTargetType.CONTENT_TEXT
|
||||
&& target.detail.injectedText?.options.attachedData === ColorDecorationInjectedTextMarker;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import { isMousePositionWithinElement } from './hoverUtils.js';
|
||||
import { ContentHoverWidgetWrapper } from './contentHoverWidgetWrapper.js';
|
||||
import './hover.css';
|
||||
import { Emitter } from '../../../../base/common/event.js';
|
||||
import { isOnColorDecorator } from '../../colorPicker/browser/hoverColorPicker/hoverColorPickerContribution.js';
|
||||
import { isOnColorDecorator } from '../../colorPicker/browser/hoverColorPicker/hoverColorPicker.js';
|
||||
|
||||
// sticky hover widget which doesn't disappear on focus out and such
|
||||
const _sticky = false
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AsyncIterableObject } from '../../../../../base/common/async.js';
|
||||
import { CancellationToken } from '../../../../../base/common/cancellation.js';
|
||||
import { Codicon } from '../../../../../base/common/codicons.js';
|
||||
import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js';
|
||||
import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js';
|
||||
@@ -20,6 +22,7 @@ import { TerminalLocation } from '../../../../../platform/terminal/common/termin
|
||||
import { IUntitledTextResourceEditorInput } from '../../../../common/editor.js';
|
||||
import { IEditorService } from '../../../../services/editor/common/editorService.js';
|
||||
import { accessibleViewInCodeBlock } from '../../../accessibility/browser/accessibilityConfiguration.js';
|
||||
import { InlineChatController } from '../../../inlineChat/browser/inlineChatController.js';
|
||||
import { ITerminalEditorService, ITerminalGroupService, ITerminalService } from '../../../terminal/browser/terminal.js';
|
||||
import { ChatAgentLocation } from '../../common/chatAgents.js';
|
||||
import { ChatContextKeys } from '../../common/chatContextKeys.js';
|
||||
@@ -529,10 +532,38 @@ export function registerChatCodeCompareBlockActions() {
|
||||
|
||||
async runWithContext(accessor: ServicesAccessor, context: ICodeCompareBlockActionContext): Promise<any> {
|
||||
|
||||
const instaService = accessor.get(IInstantiationService);
|
||||
const editorService = accessor.get(ICodeEditorService);
|
||||
|
||||
const editor = instaService.createInstance(DefaultChatTextEditor);
|
||||
await editor.preview(context.element, context.edit);
|
||||
const item = context.edit;
|
||||
const response = context.element;
|
||||
|
||||
if (item.state?.applied) {
|
||||
// already applied
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!response.response.value.includes(item)) {
|
||||
// bogous item
|
||||
return false;
|
||||
}
|
||||
|
||||
const firstEdit = item.edits[0]?.[0];
|
||||
if (!firstEdit) {
|
||||
return false;
|
||||
}
|
||||
const textEdits = AsyncIterableObject.fromArray(item.edits);
|
||||
|
||||
const editorToApply = await editorService.openCodeEditor({ resource: item.uri }, null);
|
||||
if (editorToApply) {
|
||||
const inlineChatController = InlineChatController.get(editorToApply);
|
||||
if (inlineChatController) {
|
||||
editorToApply.revealLineInCenterIfOutsideViewport(firstEdit.range.startLineNumber);
|
||||
inlineChatController.reviewEdits(firstEdit.range, textEdits, CancellationToken.None);
|
||||
response.setEditApplied(item, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -487,14 +487,6 @@ class ChatDecorationsProvider extends Disposable implements IDecorationsProvider
|
||||
}
|
||||
|
||||
export class ChatEditingMultiDiffSourceResolver implements IMultiDiffSourceResolver {
|
||||
public static readonly scheme = CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME;
|
||||
|
||||
public static getMultiDiffSourceUri(): URI {
|
||||
return URI.from({
|
||||
scheme: ChatEditingMultiDiffSourceResolver.scheme,
|
||||
path: '',
|
||||
});
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly _currentSession: IObservable<ChatEditingSession | null>,
|
||||
@@ -502,7 +494,7 @@ export class ChatEditingMultiDiffSourceResolver implements IMultiDiffSourceResol
|
||||
) { }
|
||||
|
||||
canHandleUri(uri: URI): boolean {
|
||||
return uri.scheme === ChatEditingMultiDiffSourceResolver.scheme;
|
||||
return uri.scheme === CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME;
|
||||
}
|
||||
|
||||
async resolveDiffSource(uri: URI): Promise<IResolvedMultiDiffSource> {
|
||||
|
||||
@@ -30,9 +30,8 @@ import { IEditorService } from '../../../../services/editor/common/editorService
|
||||
import { MultiDiffEditor } from '../../../multiDiffEditor/browser/multiDiffEditor.js';
|
||||
import { MultiDiffEditorInput } from '../../../multiDiffEditor/browser/multiDiffEditorInput.js';
|
||||
import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js';
|
||||
import { ChatEditingSessionChangeType, ChatEditingSessionState, ChatEditKind, IChatEditingSession, IModifiedFileEntry, WorkingSetDisplayMetadata, WorkingSetEntryRemovalReason, WorkingSetEntryState } from '../../common/chatEditingService.js';
|
||||
import { ChatEditingSessionChangeType, ChatEditingSessionState, ChatEditKind, getMultiDiffSourceUri, IChatEditingSession, IModifiedFileEntry, WorkingSetDisplayMetadata, WorkingSetEntryRemovalReason, WorkingSetEntryState } from '../../common/chatEditingService.js';
|
||||
import { IChatResponseModel } from '../../common/chatModel.js';
|
||||
import { ChatEditingMultiDiffSourceResolver } from './chatEditingService.js';
|
||||
import { ChatEditingModifiedFileEntry, IModifiedEntryTelemetryInfo, ISnapshotEntry } from './chatEditingModifiedFileEntry.js';
|
||||
import { ChatEditingTextModelContentProvider } from './chatEditingTextModelContentProviders.js';
|
||||
import { Schemas } from '../../../../../base/common/network.js';
|
||||
@@ -484,7 +483,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
|
||||
}
|
||||
}
|
||||
const input = MultiDiffEditorInput.fromResourceMultiDiffEditorInput({
|
||||
multiDiffSource: ChatEditingMultiDiffSourceResolver.getMultiDiffSourceUri(),
|
||||
multiDiffSource: getMultiDiffSourceUri(),
|
||||
label: localize('multiDiffEditorInput.name', "Suggested Edits")
|
||||
}, this._instantiationService);
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import { ChatContextKeys } from '../common/chatContextKeys.js';
|
||||
import { isEqual } from '../../../../base/common/resources.js';
|
||||
import { Range } from '../../../../editor/common/core/range.js';
|
||||
import { getNotebookEditorFromEditorPane } from '../../notebook/browser/notebookBrowser.js';
|
||||
import { ctxNotebookHasEditorModification } from '../../notebook/browser/contrib/chatEdit/notebookChatEditController.js';
|
||||
import { ctxNotebookHasEditorModification } from '../../notebook/browser/contrib/chatEdit/notebookChatEditContext.js';
|
||||
|
||||
abstract class NavigateAction extends Action2 {
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ import { IChatAgentService } from '../common/chatAgents.js';
|
||||
import { ChatContextKeys } from '../common/chatContextKeys.js';
|
||||
import { CHAT_CATEGORY } from './actions/chatActions.js';
|
||||
import { ChatViewId, EditsViewId, ensureSideBarChatViewSize, IChatWidget, showChatView, showEditsView } from './chat.js';
|
||||
import { CHAT_EDITING_SIDEBAR_PANEL_ID, CHAT_SIDEBAR_PANEL_ID } from './chatViewPane.js';
|
||||
import { CHAT_EDITING_SIDEBAR_PANEL_ID, CHAT_SIDEBAR_PANEL_ID, SetupWelcomeViewCondition } from './chatViewPane.js';
|
||||
import { ChatViewsWelcomeExtensions, IChatViewsWelcomeContributionRegistry } from './viewsWelcome/chatViewsWelcome.js';
|
||||
import { IChatQuotasService } from './chatQuotasService.js';
|
||||
import { mainWindow } from '../../../../base/browser/window.js';
|
||||
@@ -95,25 +95,6 @@ enum ChatEntitlement {
|
||||
const TRIGGER_SETUP_COMMAND_ID = 'workbench.action.chat.triggerSetup';
|
||||
const TRIGGER_SETUP_COMMAND_LABEL = localize2('triggerChatSetup', "Use AI Features with Copilot for Free...");
|
||||
|
||||
export const SetupWelcomeViewKeys = new Set([ChatContextKeys.Setup.triggered.key, ChatContextKeys.Setup.installed.key, ChatContextKeys.Setup.signedOut.key, ChatContextKeys.Setup.canSignUp.key]);
|
||||
export const SetupWelcomeViewCondition = ContextKeyExpr.and(
|
||||
ContextKeyExpr.has('config.chat.experimental.offerSetup'),
|
||||
ContextKeyExpr.or(
|
||||
ContextKeyExpr.and(
|
||||
ChatContextKeys.Setup.triggered,
|
||||
ChatContextKeys.Setup.installed.negate()
|
||||
),
|
||||
ContextKeyExpr.and(
|
||||
ChatContextKeys.Setup.canSignUp,
|
||||
ChatContextKeys.Setup.installed
|
||||
),
|
||||
ContextKeyExpr.and(
|
||||
ChatContextKeys.Setup.signedOut,
|
||||
ChatContextKeys.Setup.installed
|
||||
)
|
||||
)
|
||||
)!;
|
||||
|
||||
export class ChatSetupContribution extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
static readonly ID = 'workbench.chat.setup';
|
||||
|
||||
@@ -8,7 +8,7 @@ import { CancellationToken } from '../../../../base/common/cancellation.js';
|
||||
import { DisposableStore } from '../../../../base/common/lifecycle.js';
|
||||
import { MarshalledId } from '../../../../base/common/marshallingIds.js';
|
||||
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
|
||||
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
|
||||
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
|
||||
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
@@ -27,10 +27,10 @@ import { SIDE_BAR_FOREGROUND } from '../../../common/theme.js';
|
||||
import { IViewDescriptorService } from '../../../common/views.js';
|
||||
import { IChatViewTitleActionContext } from '../common/chatActions.js';
|
||||
import { ChatAgentLocation, IChatAgentService } from '../common/chatAgents.js';
|
||||
import { ChatContextKeys } from '../common/chatContextKeys.js';
|
||||
import { ChatModelInitState, IChatModel } from '../common/chatModel.js';
|
||||
import { CHAT_PROVIDER_ID } from '../common/chatParticipantContribTypes.js';
|
||||
import { IChatService } from '../common/chatService.js';
|
||||
import { SetupWelcomeViewCondition, SetupWelcomeViewKeys } from './chatSetup.js';
|
||||
import { ChatWidget, IChatViewState } from './chatWidget.js';
|
||||
import { ChatViewWelcomeController, IViewWelcomeDelegate } from './viewsWelcome/chatViewWelcomeController.js';
|
||||
|
||||
@@ -279,3 +279,22 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const SetupWelcomeViewKeys = new Set([ChatContextKeys.Setup.triggered.key, ChatContextKeys.Setup.installed.key, ChatContextKeys.Setup.signedOut.key, ChatContextKeys.Setup.canSignUp.key]);
|
||||
export const SetupWelcomeViewCondition = ContextKeyExpr.and(
|
||||
ContextKeyExpr.has('config.chat.experimental.offerSetup'),
|
||||
ContextKeyExpr.or(
|
||||
ContextKeyExpr.and(
|
||||
ChatContextKeys.Setup.triggered,
|
||||
ChatContextKeys.Setup.installed.negate()
|
||||
),
|
||||
ContextKeyExpr.and(
|
||||
ChatContextKeys.Setup.canSignUp,
|
||||
ChatContextKeys.Setup.installed
|
||||
),
|
||||
ContextKeyExpr.and(
|
||||
ChatContextKeys.Setup.signedOut,
|
||||
ChatContextKeys.Setup.installed
|
||||
)
|
||||
)
|
||||
)!;
|
||||
|
||||
@@ -8,7 +8,7 @@ import './codeBlockPart.css';
|
||||
import * as dom from '../../../../base/browser/dom.js';
|
||||
import { renderFormattedText } from '../../../../base/browser/formattedTextRenderer.js';
|
||||
import { Button } from '../../../../base/browser/ui/button/button.js';
|
||||
import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';
|
||||
import { CancellationToken } from '../../../../base/common/cancellation.js';
|
||||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { combinedDisposable, Disposable, MutableDisposable } from '../../../../base/common/lifecycle.js';
|
||||
@@ -69,8 +69,6 @@ import { ChatTreeItem } from './chat.js';
|
||||
import { IChatRendererDelegate } from './chatListRenderer.js';
|
||||
import { ChatEditorOptions } from './chatOptions.js';
|
||||
import { emptyProgressRunner, IEditorProgressService } from '../../../../platform/progress/common/progress.js';
|
||||
import { AsyncIterableObject } from '../../../../base/common/async.js';
|
||||
import { InlineChatController } from '../../inlineChat/browser/inlineChatController.js';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
@@ -941,37 +939,5 @@ export class DefaultChatTextEditor {
|
||||
response.setEditApplied(item, -1);
|
||||
}
|
||||
|
||||
async preview(response: IChatResponseModel | IChatResponseViewModel, item: IChatTextEditGroup) {
|
||||
if (item.state?.applied) {
|
||||
// already applied
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!response.response.value.includes(item)) {
|
||||
// bogous item
|
||||
return false;
|
||||
}
|
||||
|
||||
const firstEdit = item.edits[0]?.[0];
|
||||
if (!firstEdit) {
|
||||
return false;
|
||||
}
|
||||
const textEdits = AsyncIterableObject.fromArray(item.edits);
|
||||
|
||||
const editorToApply = await this.editorService.openCodeEditor({ resource: item.uri }, null);
|
||||
if (editorToApply) {
|
||||
const inlineChatController = InlineChatController.get(editorToApply);
|
||||
if (inlineChatController) {
|
||||
const tokenSource = new CancellationTokenSource();
|
||||
editorToApply.revealLineInCenterIfOutsideViewport(firstEdit.range.startLineNumber);
|
||||
const promise = inlineChatController.reviewEdits(firstEdit.range, textEdits, tokenSource.token);
|
||||
response.setEditApplied(item, 1);
|
||||
promise.finally(() => {
|
||||
tokenSource.dispose();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,3 +163,10 @@ export interface IChatEditingActionContext {
|
||||
export function isChatEditingActionContext(thing: unknown): thing is IChatEditingActionContext {
|
||||
return typeof thing === 'object' && !!thing && 'sessionId' in thing;
|
||||
}
|
||||
|
||||
export function getMultiDiffSourceUri(): URI {
|
||||
return URI.from({
|
||||
scheme: CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME,
|
||||
path: '',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { RawContextKey } from '../../../../../../platform/contextkey/common/contextkey.js';
|
||||
|
||||
export const ctxNotebookHasEditorModification = new RawContextKey<boolean>('chat.hasNotebookEditorModifications', undefined, localize('chat.hasNotebookEditorModifications', "The current Notebook editor contains chat modifications"));
|
||||
@@ -17,14 +17,12 @@ import { INotebookOriginalModelReferenceFactory, NotebookOriginalModelReferenceF
|
||||
import { debouncedObservable2 } from '../../../../../../base/common/observableInternal/utils.js';
|
||||
import { CellDiffInfo } from '../../diff/notebookDiffViewModel.js';
|
||||
import { NotebookChatActionsOverlayController } from './notebookChatActionsOverlay.js';
|
||||
import { IContextKey, IContextKeyService, RawContextKey } from '../../../../../../platform/contextkey/common/contextkey.js';
|
||||
import { localize } from '../../../../../../nls.js';
|
||||
import { IContextKey, IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js';
|
||||
import { registerNotebookContribution } from '../../notebookEditorExtensions.js';
|
||||
import { InstantiationType, registerSingleton } from '../../../../../../platform/instantiation/common/extensions.js';
|
||||
import { INotebookOriginalCellModelFactory, OriginalNotebookCellModelFactory } from './notebookOriginalCellModelFactory.js';
|
||||
import { Event } from '../../../../../../base/common/event.js';
|
||||
|
||||
export const ctxNotebookHasEditorModification = new RawContextKey<boolean>('chat.hasNotebookEditorModifications', undefined, localize('chat.hasNotebookEditorModifications', "The current Notebook editor contains chat modifications"));
|
||||
import { ctxNotebookHasEditorModification } from './notebookChatEditContext.js';
|
||||
|
||||
export class NotebookChatEditorControllerContrib extends Disposable implements INotebookEditorContribution {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user