From 7dec57ae04a0d1d4b11bf7217ce26f7fa782a08d Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Wed, 15 Jan 2025 19:24:08 -0800 Subject: [PATCH 01/29] add high priority to void keybinds --- .../platform/keybinding/common/keybindingsRegistry.ts | 3 ++- .../workbench/contrib/void/browser/quickEditActions.ts | 6 ++++-- .../void/browser/react/src/sidebar-tsx/SidebarChat.tsx | 2 +- .../workbench/contrib/void/browser/sidebarActions.ts | 10 +++++++++- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/keybinding/common/keybindingsRegistry.ts b/src/vs/platform/keybinding/common/keybindingsRegistry.ts index eeabcc55..c8639cbf 100644 --- a/src/vs/platform/keybinding/common/keybindingsRegistry.ts +++ b/src/vs/platform/keybinding/common/keybindingsRegistry.ts @@ -64,7 +64,8 @@ export const enum KeybindingWeight { EditorContrib = 100, WorkbenchContrib = 200, BuiltinExtension = 300, - ExternalExtension = 400 + ExternalExtension = 400, + VoidExtension = 500, // Void - must trump any external extension } export interface ICommandAndKeybindingRule extends IKeybindingRule { diff --git a/src/vs/workbench/contrib/void/browser/quickEditActions.ts b/src/vs/workbench/contrib/void/browser/quickEditActions.ts index 125e983c..1498c8f6 100644 --- a/src/vs/workbench/contrib/void/browser/quickEditActions.ts +++ b/src/vs/workbench/contrib/void/browser/quickEditActions.ts @@ -12,6 +12,7 @@ import { ICodeEditorService } from '../../../../editor/browser/services/codeEdit import { IInlineDiffsService } from './inlineDiffsService.js'; import { roundRangeToLines } from './sidebarActions.js'; import { VOID_CTRL_K_ACTION_ID } from './actionIDs.js'; +import { localize2 } from '../../../../nls.js'; export type QuickEditPropsType = { @@ -37,10 +38,11 @@ registerAction2(class extends Action2 { ) { super({ id: VOID_CTRL_K_ACTION_ID, - title: 'Void: Quick Edit', + f1: true, + title: localize2('voidQuickEditAction', 'Void: Quick Edit'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyK, - weight: KeybindingWeight.BuiltinExtension, + weight: KeybindingWeight.VoidExtension, } }); } diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index c41c6702..06a1c4ae 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -625,7 +625,7 @@ export const SidebarChat = () => { {/* text input */} { setInstructionsAreEmpty(!newStr) }, [setInstructionsAreEmpty])} onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { diff --git a/src/vs/workbench/contrib/void/browser/sidebarActions.ts b/src/vs/workbench/contrib/void/browser/sidebarActions.ts index d33df07d..3b260158 100644 --- a/src/vs/workbench/contrib/void/browser/sidebarActions.ts +++ b/src/vs/workbench/contrib/void/browser/sidebarActions.ts @@ -153,7 +153,15 @@ registerAction2(class extends Action2 { registerAction2(class extends Action2 { constructor() { - super({ id: VOID_CTRL_L_ACTION_ID, title: 'Void: Press Ctrl+L', keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyL, weight: KeybindingWeight.BuiltinExtension } }); + super({ + id: VOID_CTRL_L_ACTION_ID, + f1: true, + title: localize2('voidCtrlL', 'Void: Add Select to Chat'), + keybinding: { + primary: KeyMod.CtrlCmd | KeyCode.KeyL, + weight: KeybindingWeight.VoidExtension + } + }); } async run(accessor: ServicesAccessor): Promise { const commandService = accessor.get(ICommandService) From e3da020c0a737a4b4d5fd1dbec528da616da13e3 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Wed, 15 Jan 2025 19:27:32 -0800 Subject: [PATCH 02/29] bigger weight --- src/vs/platform/keybinding/common/keybindingsRegistry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/keybinding/common/keybindingsRegistry.ts b/src/vs/platform/keybinding/common/keybindingsRegistry.ts index c8639cbf..e83230ae 100644 --- a/src/vs/platform/keybinding/common/keybindingsRegistry.ts +++ b/src/vs/platform/keybinding/common/keybindingsRegistry.ts @@ -65,7 +65,7 @@ export const enum KeybindingWeight { WorkbenchContrib = 200, BuiltinExtension = 300, ExternalExtension = 400, - VoidExtension = 500, // Void - must trump any external extension + VoidExtension = 605, // Void - must trump any external extension } export interface ICommandAndKeybindingRule extends IKeybindingRule { From f8c454544cc4927efa1428e70c5c7d5850008633 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Wed, 15 Jan 2025 20:53:41 -0800 Subject: [PATCH 03/29] add logging of debug metrics --- .../telemetry/common/telemetryService.ts | 2 + src/vs/platform/void/common/metricsService.ts | 28 ++++++++++ .../void/electron-main/metricsMainService.ts | 56 ++++++++++++++++--- 3 files changed, 77 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index c3e05eb2..eb04c7a1 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { DisposableStore } from '../../../base/common/lifecycle.js'; +// import { mixin } from '../../../base/common/objects.js'; import { isWeb } from '../../../base/common/platform.js'; import { escapeRegExpCharacters } from '../../../base/common/strings.js'; import { localize } from '../../../nls.js'; @@ -15,6 +16,7 @@ import { Registry } from '../../registry/common/platform.js'; import { ClassifiedEvent, IGDPRProperty, OmitMetadata, StrictPropertyCheck } from './gdprTypings.js'; import { ITelemetryData, ITelemetryService, TelemetryConfiguration, TelemetryLevel, TELEMETRY_CRASH_REPORTER_SETTING_ID, TELEMETRY_OLD_SETTING_ID, TELEMETRY_SECTION_ID, TELEMETRY_SETTING_ID, ICommonProperties } from './telemetry.js'; import { getTelemetryLevel, ITelemetryAppender } from './telemetryUtils.js'; +// import { cleanData } from './telemetryUtils.js'; export interface ITelemetryServiceConfig { appenders: ITelemetryAppender[]; diff --git a/src/vs/platform/void/common/metricsService.ts b/src/vs/platform/void/common/metricsService.ts index 7002af45..a3aeb6a8 100644 --- a/src/vs/platform/void/common/metricsService.ts +++ b/src/vs/platform/void/common/metricsService.ts @@ -7,10 +7,15 @@ import { createDecorator } from '../../instantiation/common/instantiation.js'; import { ProxyChannel } from '../../../base/parts/ipc/common/ipc.js'; import { IMainProcessService } from '../../ipc/common/mainProcessService.js'; import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js'; +import { Action2, registerAction2 } from '../../actions/common/actions.js'; +import { localize2 } from '../../../nls.js'; +import { ServicesAccessor } from '../../../editor/browser/editorExtensions.js'; +import { INotificationService } from '../../notification/common/notification.js'; export interface IMetricsService { readonly _serviceBrand: undefined; capture(event: string, params: Record): void; + getDebuggingProperties(): Promise; } export const IMetricsService = createDecorator('metricsService'); @@ -34,7 +39,30 @@ export class MetricsService implements IMetricsService { this.metricsService.capture(...params); } + // anything transmitted over a channel must be async even if it looks like it doesn't have to be + async getDebuggingProperties(): Promise { + return this.metricsService.getDebuggingProperties() + } } registerSingleton(IMetricsService, MetricsService, InstantiationType.Eager); + +// debugging action +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'voidDebugInfo', + f1: true, + title: localize2('voidMetricsDebug', 'Void: Log Debug Info'), + }); + } + async run(accessor: ServicesAccessor): Promise { + const metricsService = accessor.get(IMetricsService) + const notifService = accessor.get(INotificationService) + + const debugProperties = await metricsService.getDebuggingProperties() + console.log('Metrics:', debugProperties) + notifService.info(`Void Debug info:\n${JSON.stringify(debugProperties, null, 2)}`) + } +}) diff --git a/src/vs/platform/void/electron-main/metricsMainService.ts b/src/vs/platform/void/electron-main/metricsMainService.ts index e1811e4c..d9b5b852 100644 --- a/src/vs/platform/void/electron-main/metricsMainService.ts +++ b/src/vs/platform/void/electron-main/metricsMainService.ts @@ -5,6 +5,7 @@ import { Disposable } from '../../../base/common/lifecycle.js'; import { isLinux, isMacintosh, isWindows } from '../../../base/common/platform.js'; + import { IProductService } from '../../product/common/productService.js'; import { ITelemetryService } from '../../telemetry/common/telemetry.js'; @@ -19,36 +20,73 @@ import { PostHog } from 'posthog-node' // const buildNumber = '1.0.0'; // const isMac = process.platform === 'darwin'; + +const os = isWindows ? 'windows' : isMacintosh ? 'mac' : isLinux ? 'linux' : null + export class MetricsMainService extends Disposable implements IMetricsService { _serviceBrand: undefined; - readonly _distinctId: string + readonly distinctId: string readonly client: PostHog + readonly _initProperties: object + + constructor( @ITelemetryService private readonly _telemetryService: ITelemetryService, - @IProductService private readonly _productService: IProductService + @IProductService private readonly _productService: IProductService, ) { super() - this.client = new PostHog('phc_UanIdujHiLp55BkUTjB1AuBXcasVkdqRwgnwRlWESH2', { host: 'https://us.i.posthog.com', }) + this.client = new PostHog('phc_UanIdujHiLp55BkUTjB1AuBXcasVkdqRwgnwRlWESH2', { + host: 'https://us.i.posthog.com', + }) const { devDeviceId, firstSessionDate, machineId } = this._telemetryService + this.distinctId = devDeviceId + const { commit, version, quality } = this._productService - this._distinctId = devDeviceId + // custom properties we identify + this._initProperties = { + firstSessionDate, + machineId, + commit, + version, + os, + quality, + distinctId: this.distinctId, + ...this._getOSInfo(), + } - const { commit, version } = this._productService - const os = isWindows ? 'windows' : isMacintosh ? 'mac' : isLinux ? 'linux' : null + const identifyMessage = { + distinctId: this.distinctId, + properties: this._initProperties, + } + this.client.identify(identifyMessage) - this.client.identify({ distinctId: this._distinctId, properties: { firstSessionDate, machineId, commit, version, os } }) + console.log('Void posthog metrics info:', JSON.stringify(identifyMessage, null, 2)) - console.log('Void posthog metrics info:', JSON.stringify({ devDeviceId, firstSessionDate, machineId })) + } + + _getOSInfo() { + try { + const { platform, arch } = process // see platform.ts + return { platform, arch } + } + catch (e) { + return { osInfo: { platform: '??', arch: '??' } } + } } capture: IMetricsService['capture'] = (event, params) => { - const capture = { distinctId: this._distinctId, event, properties: params } as const + const capture = { distinctId: this.distinctId, event, properties: params } as const // console.log('full capture:', capture) this.client.capture(capture) } + + + async getDebuggingProperties() { + return this._initProperties + } } From 2103a959302b680b01beec753941496fff88e1ec Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Wed, 15 Jan 2025 22:42:46 -0800 Subject: [PATCH 04/29] VOID ICON - remove code-icon.svg --- src/vs/workbench/browser/media/code-icon.svg | 1 - src/vs/workbench/browser/media/void-icon-sm.png | Bin 0 -> 87731 bytes .../browser/parts/banner/media/bannerpart.css | 2 +- .../parts/titlebar/media/titlebarpart.css | 4 ++-- .../update/browser/media/releasenoteseditor.css | 2 +- .../browser/media/gettingStarted.css | 2 +- .../browser/media/walkThroughPart.css | 2 +- 7 files changed, 6 insertions(+), 7 deletions(-) delete mode 100644 src/vs/workbench/browser/media/code-icon.svg create mode 100644 src/vs/workbench/browser/media/void-icon-sm.png diff --git a/src/vs/workbench/browser/media/code-icon.svg b/src/vs/workbench/browser/media/code-icon.svg deleted file mode 100644 index cc61f81e..00000000 --- a/src/vs/workbench/browser/media/code-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/browser/media/void-icon-sm.png b/src/vs/workbench/browser/media/void-icon-sm.png new file mode 100644 index 0000000000000000000000000000000000000000..458166704f9d5df60cc3b64b8c6ab799e91989d2 GIT binary patch literal 87731 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 BfI$EN literal 0 HcmV?d00001 diff --git a/src/vs/workbench/browser/parts/banner/media/bannerpart.css b/src/vs/workbench/browser/parts/banner/media/bannerpart.css index a0de81f2..90e26053 100644 --- a/src/vs/workbench/browser/parts/banner/media/bannerpart.css +++ b/src/vs/workbench/browser/parts/banner/media/bannerpart.css @@ -30,7 +30,7 @@ background-repeat: no-repeat; background-position: center center; background-size: 16px; - background-image: url('../../../../browser/media/code-icon.svg'); + background-image: url('../../../../browser/media/void-icon-sm.png'); width: 16px; padding: 0; margin: 0 6px 0 10px; diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index ccc39382..221634bb 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -253,7 +253,7 @@ } .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-left > .window-appicon:not(.codicon) { - background-image: url('../../../media/code-icon.svg'); + background-image: url('../../../media/void-icon-sm.png'); background-repeat: no-repeat; background-position: center center; background-size: 16px; @@ -275,7 +275,7 @@ height: 8px; z-index: 1; /* on top of home indicator */ - background-image: url('../../../media/code-icon.svg'); + background-image: url('../../../media/void-icon-sm.png'); background-repeat: no-repeat; background-position: center center; background-size: 8px; diff --git a/src/vs/workbench/contrib/update/browser/media/releasenoteseditor.css b/src/vs/workbench/contrib/update/browser/media/releasenoteseditor.css index 4210055b..b79e3905 100644 --- a/src/vs/workbench/contrib/update/browser/media/releasenoteseditor.css +++ b/src/vs/workbench/contrib/update/browser/media/releasenoteseditor.css @@ -5,5 +5,5 @@ .file-icons-enabled .show-file-icons .webview-vs_code_release_notes-name-file-icon.file-icon::before { content: ' '; - background-image: url('../../../../browser/media/code-icon.svg'); + background-image: url('../../../../browser/media/void-icon-sm.png'); } diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css b/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css index 485075f3..454875dd 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css @@ -5,7 +5,7 @@ .file-icons-enabled .show-file-icons .vscode_getting_started_page-name-file-icon.file-icon::before { content: ' '; - background-image: url('../../../../browser/media/code-icon.svg'); + background-image: url('../../../../browser/media/void-icon-sm.png'); } .monaco-workbench .part.editor > .content .gettingStartedContainer { diff --git a/src/vs/workbench/contrib/welcomeWalkthrough/browser/media/walkThroughPart.css b/src/vs/workbench/contrib/welcomeWalkthrough/browser/media/walkThroughPart.css index 7ab127ea..8084ecec 100644 --- a/src/vs/workbench/contrib/welcomeWalkthrough/browser/media/walkThroughPart.css +++ b/src/vs/workbench/contrib/welcomeWalkthrough/browser/media/walkThroughPart.css @@ -113,7 +113,7 @@ .file-icons-enabled .show-file-icons .vs_code_editor_walkthrough\.md-name-file-icon.md-ext-file-icon.ext-file-icon.markdown-lang-file-icon.file-icon::before { content: ' '; - background-image: url('../../../../browser/media/code-icon.svg'); + background-image: url('../../../../browser/media/void-icon-sm.png'); } .monaco-workbench .part.editor > .content .walkThroughContent .mac-only, From 10adc13b60a86ce820c25ef9c0e582c14413f5dc Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Wed, 15 Jan 2025 22:57:17 -0800 Subject: [PATCH 05/29] add manual device id --- .../void/electron-main/metricsMainService.ts | 46 ++++++++++++------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/src/vs/platform/void/electron-main/metricsMainService.ts b/src/vs/platform/void/electron-main/metricsMainService.ts index d9b5b852..f9a1683d 100644 --- a/src/vs/platform/void/electron-main/metricsMainService.ts +++ b/src/vs/platform/void/electron-main/metricsMainService.ts @@ -5,55 +5,67 @@ import { Disposable } from '../../../base/common/lifecycle.js'; import { isLinux, isMacintosh, isWindows } from '../../../base/common/platform.js'; +import { generateUuid } from '../../../base/common/uuid.js'; import { IProductService } from '../../product/common/productService.js'; -import { ITelemetryService } from '../../telemetry/common/telemetry.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js'; import { IMetricsService } from '../common/metricsService.js'; import { PostHog } from 'posthog-node' -// posthog-js (old): -// posthog.init('phc_UanIdujHiLp55BkUTjB1AuBXcasVkdqRwgnwRlWESH2', { api_host: 'https://us.i.posthog.com', }) - -// const buildEnv = 'development'; -// const buildNumber = '1.0.0'; -// const isMac = process.platform === 'darwin'; - - const os = isWindows ? 'windows' : isMacintosh ? 'mac' : isLinux ? 'linux' : null +const VOID_DISTINCT_ID_STORAGE_KEY = 'void.distinctId' +const VOID_MACHINE_STORAGE_KEY = 'void.machineId' + export class MetricsMainService extends Disposable implements IMetricsService { _serviceBrand: undefined; - readonly distinctId: string - readonly client: PostHog + private readonly client: PostHog - readonly _initProperties: object + private readonly _initProperties: object + + + // TODO we should eventually identify people based on email + private get distinctId() { + const curr = this._storageService.get(VOID_DISTINCT_ID_STORAGE_KEY, StorageScope.APPLICATION) + if (curr !== undefined) return curr + const newVal = generateUuid() + this._storageService.store(VOID_DISTINCT_ID_STORAGE_KEY, newVal, StorageScope.APPLICATION, StorageTarget.USER) + return newVal + } + + private get machineId() { + const curr = this._storageService.get(VOID_MACHINE_STORAGE_KEY, StorageScope.APPLICATION) + if (curr !== undefined) return curr + const newVal = generateUuid() + this._storageService.store(VOID_MACHINE_STORAGE_KEY, newVal, StorageScope.APPLICATION, StorageTarget.MACHINE) // <-- MACHINE here + return newVal + } constructor( - @ITelemetryService private readonly _telemetryService: ITelemetryService, @IProductService private readonly _productService: IProductService, + @IStorageService private readonly _storageService: IStorageService, ) { super() this.client = new PostHog('phc_UanIdujHiLp55BkUTjB1AuBXcasVkdqRwgnwRlWESH2', { host: 'https://us.i.posthog.com', }) - const { devDeviceId, firstSessionDate, machineId } = this._telemetryService - this.distinctId = devDeviceId + // we'd like to use devDeviceId on telemetryService, but that gets sanitized by the time it gets here as 'someValue.devDeviceId' + const { commit, version, quality } = this._productService // custom properties we identify this._initProperties = { - firstSessionDate, - machineId, commit, version, os, quality, distinctId: this.distinctId, + machineId: this.machineId, ...this._getOSInfo(), } From 0d48db1a0c3fb4456e66173a8b4c79c001234b85 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Wed, 15 Jan 2025 23:17:05 -0800 Subject: [PATCH 06/29] fix metrics --- .../void/electron-main/metricsMainService.ts | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/vs/platform/void/electron-main/metricsMainService.ts b/src/vs/platform/void/electron-main/metricsMainService.ts index f9a1683d..fdfb1d16 100644 --- a/src/vs/platform/void/electron-main/metricsMainService.ts +++ b/src/vs/platform/void/electron-main/metricsMainService.ts @@ -6,9 +6,10 @@ import { Disposable } from '../../../base/common/lifecycle.js'; import { isLinux, isMacintosh, isWindows } from '../../../base/common/platform.js'; import { generateUuid } from '../../../base/common/uuid.js'; +import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js'; import { IProductService } from '../../product/common/productService.js'; -import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js'; +import { IStorageMainService } from '../../storage/electron-main/storageMainService.js'; import { IMetricsService } from '../common/metricsService.js'; import { PostHog } from 'posthog-node' @@ -16,7 +17,6 @@ import { PostHog } from 'posthog-node' const os = isWindows ? 'windows' : isMacintosh ? 'mac' : isLinux ? 'linux' : null -const VOID_DISTINCT_ID_STORAGE_KEY = 'void.distinctId' const VOID_MACHINE_STORAGE_KEY = 'void.machineId' export class MetricsMainService extends Disposable implements IMetricsService { @@ -28,26 +28,19 @@ export class MetricsMainService extends Disposable implements IMetricsService { // TODO we should eventually identify people based on email - private get distinctId() { - const curr = this._storageService.get(VOID_DISTINCT_ID_STORAGE_KEY, StorageScope.APPLICATION) - if (curr !== undefined) return curr - const newVal = generateUuid() - this._storageService.store(VOID_DISTINCT_ID_STORAGE_KEY, newVal, StorageScope.APPLICATION, StorageTarget.USER) - return newVal - } - private get machineId() { - const curr = this._storageService.get(VOID_MACHINE_STORAGE_KEY, StorageScope.APPLICATION) - if (curr !== undefined) return curr + const currVal = this._storageService.applicationStorage.get(VOID_MACHINE_STORAGE_KEY) + if (currVal !== undefined) return currVal const newVal = generateUuid() - this._storageService.store(VOID_MACHINE_STORAGE_KEY, newVal, StorageScope.APPLICATION, StorageTarget.MACHINE) // <-- MACHINE here + this._storageService.applicationStorage.set(VOID_MACHINE_STORAGE_KEY, newVal) return newVal } constructor( @IProductService private readonly _productService: IProductService, - @IStorageService private readonly _storageService: IStorageService, + @IStorageMainService private readonly _storageService: IStorageMainService, + @IEnvironmentMainService private readonly _envMainService: IEnvironmentMainService, ) { super() this.client = new PostHog('phc_UanIdujHiLp55BkUTjB1AuBXcasVkdqRwgnwRlWESH2', { @@ -58,19 +51,22 @@ export class MetricsMainService extends Disposable implements IMetricsService { const { commit, version, quality } = this._productService + const isDevMode = !this._envMainService.isBuilt // found in abstractUpdateService.ts + + // custom properties we identify this._initProperties = { commit, version, os, quality, - distinctId: this.distinctId, - machineId: this.machineId, + distinctId: this.machineId, + isDevMode, ...this._getOSInfo(), } const identifyMessage = { - distinctId: this.distinctId, + distinctId: this.machineId, properties: this._initProperties, } this.client.identify(identifyMessage) @@ -90,7 +86,7 @@ export class MetricsMainService extends Disposable implements IMetricsService { } capture: IMetricsService['capture'] = (event, params) => { - const capture = { distinctId: this.distinctId, event, properties: params } as const + const capture = { distinctId: this.machineId, event, properties: params } as const // console.log('full capture:', capture) this.client.capture(capture) } From 433884b36c3376e2ba197bf73a4bd095e3865103 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Wed, 15 Jan 2025 23:42:25 -0800 Subject: [PATCH 07/29] do not ignore version mismatch --- src/vs/platform/extensions/common/extensionValidator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/extensions/common/extensionValidator.ts b/src/vs/platform/extensions/common/extensionValidator.ts index 94f3bcf8..e9736b22 100644 --- a/src/vs/platform/extensions/common/extensionValidator.ts +++ b/src/vs/platform/extensions/common/extensionValidator.ts @@ -404,8 +404,8 @@ function isVersionValid(currentVersion: string, date: ProductDate, requestedVers if (!isValidVersion(currentVersion, date, desiredVersion)) { // Void - ignore not compatible - // notices.push(nls.localize('versionMismatch', "Extension is not compatible with Code {0}. Extension requires: {1}.", currentVersion, requestedVersion)); - // return false; + notices.push(nls.localize('versionMismatch', "Extension is not compatible with Code {0}. Extension requires: {1}.", currentVersion, requestedVersion)); + return false; } return true; From 9756ab6d161133d7304e141a4f383d7d56ca12d2 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Wed, 15 Jan 2025 23:52:32 -0800 Subject: [PATCH 08/29] filter out empty thread --- .../browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx index b52d298c..a474de35 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx @@ -28,7 +28,9 @@ export const SidebarThreadSelector = () => { const { allThreads } = threadsState // sorted by most recent to least recent - const sortedThreadIds = Object.keys(allThreads ?? {}).sort((threadId1, threadId2) => allThreads![threadId1].lastModified > allThreads![threadId2].lastModified ? -1 : 1) + const sortedThreadIds = Object.keys(allThreads ?? {}) + .sort((threadId1, threadId2) => allThreads![threadId1].lastModified > allThreads![threadId2].lastModified ? -1 : 1) + .filter(threadId => allThreads![threadId].messages.length !== 0) return (
From e4fd6d05a4673eedb4664f5dbbb02382ccca22da Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Thu, 16 Jan 2025 02:20:32 -0800 Subject: [PATCH 09/29] prospective file adding --- .../react/src/sidebar-tsx/SidebarChat.tsx | 208 ++++++++++-------- .../void/browser/react/src/util/services.tsx | 28 ++- .../contrib/void/browser/sidebarActions.ts | 27 ++- .../void/browser/voidUriStateService.ts | 57 +++++ 4 files changed, 213 insertions(+), 107 deletions(-) create mode 100644 src/vs/workbench/contrib/void/browser/voidUriStateService.ts diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 06a1c4ae..e534a324 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -6,7 +6,7 @@ import React, { ButtonHTMLAttributes, FormEvent, FormHTMLAttributes, Fragment, useCallback, useEffect, useRef, useState } from 'react'; -import { useAccessor, useSidebarState, useChatThreadsState, useChatThreadsStreamState } from '../util/services.js'; +import { useAccessor, useSidebarState, useChatThreadsState, useChatThreadsStreamState, useUriState } from '../util/services.js'; import { ChatMessage, CodeSelection, CodeStagingSelection } from '../../../chatThreadService.js'; import { BlockCode } from '../markdown/BlockCode.js'; @@ -259,9 +259,9 @@ const getBasename = (pathStr: string) => { } export const SelectedFiles = ( - { type, selections, setStaging }: - | { type: 'past', selections: CodeSelection[] | null; setStaging?: undefined } - | { type: 'staging', selections: CodeStagingSelection[] | null; setStaging: ((files: CodeStagingSelection[]) => void) } + { type, selections, setSelections }: + | { type: 'past', selections: CodeSelection[]; setSelections?: undefined } + | { type: 'staging', selections: CodeStagingSelection[]; setSelections: ((newSelections: CodeStagingSelection[]) => void) } ) => { // index -> isOpened @@ -273,85 +273,104 @@ export const SelectedFiles = ( const accessor = useAccessor() const commandService = accessor.get('ICommandService') + const { currentUri } = useUriState() + + let prospectiveSelections: CodeStagingSelection[] = [] + if ( // add a prospective file if type === 'staging' and if the user is in a file, and if the file is not selected yet + type === 'staging' + && currentUri + && !selections.find(s => s.range === null && s.fileURI.fsPath === currentUri.fsPath) + ) { + prospectiveSelections = [{ + type: 'File', + fileURI: currentUri, + selectionStr: null, + range: null, + }] + } + + const allSelections = [...selections, ...prospectiveSelections] + return ( - !!selections && selections.length !== 0 && ( -
- {selections.map((selection, i) => { +
- const isThisSelectionOpened = !!(selection.selectionStr && selectionIsOpened[i]) - const isThisSelectionAFile = selection.selectionStr === null + {allSelections.map((selection, i) => { - return
selections.length - 1 + + return
+ {/* selection summary */} +
- {/* selection summary */} -
-
{ - // open the file if it is a file - if (isThisSelectionAFile) { - commandService.executeCommand('vscode.open', selection.fileURI, { - preview: true, - // preserveFocus: false, - }); - } else { - // open the selection if it is a text-selection - setSelectionIsOpened(s => { - const newS = [...s] - newS[i] = !newS[i] - return newS - }); - } - }} - > - - {/* file name */} - {getBasename(selection.fileURI.fsPath)} - {/* selection range */} - {!isThisSelectionAFile ? ` (${selection.range.startLineNumber}-${selection.range.endLineNumber})` : ''} - + onClick={() => { + if (isThisSelectionProspective) { // add prospective selection to selections + if (type !== 'staging') return; // (never) + setSelections([...selections, selection as CodeStagingSelection]) - {/* X button */} - {type === 'staging' && - { - e.stopPropagation(); // don't open/close selection - if (type !== 'staging') return; - setStaging([...selections.slice(0, i), ...selections.slice(i + 1)]) - setSelectionIsOpened(o => [...o.slice(0, i), ...o.slice(i + 1)]) - }} - > - - } + } else if (isThisSelectionAFile) { // open files + commandService.executeCommand('vscode.open', selection.fileURI, { + preview: true, + // preserveFocus: false, + }); + } else { // show text + setSelectionIsOpened(s => { + const newS = [...s] + newS[i] = !newS[i] + return newS + }); + } + }} + > + + {/* file name */} + {getBasename(selection.fileURI.fsPath)} + {/* selection range */} + {!isThisSelectionAFile ? ` (${selection.range.startLineNumber}-${selection.range.endLineNumber})` : ''} + + + {/* X button */} + {type === 'staging' && + { + e.stopPropagation(); // don't open/close selection + if (type !== 'staging') return; + setSelections([...selections.slice(0, i), ...selections.slice(i + 1)]) + setSelectionIsOpened(o => [...o.slice(0, i), ...o.slice(i + 1)]) + }} + > + + } -
+
- {/* clear all selections button */} - {type !== 'staging' || selections.length === 0 || i !== selections.length - 1 - ? null - :
-
setIsClearHovered(true)} - onMouseLeave={() => setIsClearHovered(false)} - > - +
setIsClearHovered(true)} + onMouseLeave={() => setIsClearHovered(false)} + > + { setStaging([]) }} - /> -
+ onClick={() => { setSelections([]) }} + />
- } -
- {/* selection text */} - {isThisSelectionOpened && -
{ - e.stopPropagation(); // don't focus input box - }} - > -
}
+ {/* selection text */} + {isThisSelectionOpened && +
{ + e.stopPropagation(); // don't focus input box + }} + > + +
+ } +
- })} + })} -
- ) +
+ ) } @@ -407,7 +426,7 @@ const ChatBubble = ({ chatMessage, isLoading }: { if (role === 'user') { chatbubbleContents = <> - + {chatMessage.displayContent} {/* {!isEditMode ? chatMessage.displayContent : <>} */} @@ -604,9 +623,8 @@ export const SidebarChat = () => { {/* top row */} <> {/* selections */} - {(selections && selections.length !== 0) && - - } + + {/* error message */} {latestError === undefined ? null : diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx index b5613ab6..b15cd2a6 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx @@ -10,6 +10,7 @@ import { IDisposable } from '../../../../../../../base/common/lifecycle.js' import { VoidSidebarState } from '../../../sidebarStateService.js' import { VoidSettingsState } from '../../../../../../../platform/void/common/voidSettingsService.js' import { ColorScheme } from '../../../../../../../platform/theme/common/theme.js' +import { VoidUriState } from '../../../voidUriStateService.js'; import { VoidQuickEditState } from '../../../quickEditStateService.js' import { RefreshModelStateOfProvider } from '../../../../../../../platform/void/common/refreshModelService.js' @@ -28,6 +29,7 @@ import { ILLMMessageService } from '../../../../../../../platform/void/common/ll import { IRefreshModelService } from '../../../../../../../platform/void/common/refreshModelService.js'; import { IVoidSettingsService } from '../../../../../../../platform/void/common/voidSettingsService.js'; import { IInlineDiffsService } from '../../../inlineDiffsService.js'; +import { IVoidUriStateService } from '../../../voidUriStateService.js'; import { IQuickEditStateService } from '../../../quickEditStateService.js'; import { ISidebarStateService } from '../../../sidebarStateService.js'; import { IChatThreadService } from '../../../chatThreadService.js'; @@ -47,10 +49,14 @@ import { IPathService } from '../../../../../../../workbench/services/path/commo import { IMetricsService } from '../../../../../../../platform/void/common/metricsService.js' + // normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes // even if React hasn't mounted yet, the variables are always updated to the latest state. // React listens by adding a setState function to these listeners. +let uriState: VoidUriState +const uriStateListeners: Set<(s: VoidUriState) => void> = new Set() + let quickEditState: VoidQuickEditState const quickEditStateListeners: Set<(s: VoidQuickEditState) => void> = new Set() @@ -90,6 +96,7 @@ export const _registerServices = (accessor: ServicesAccessor) => { _registerAccessor(accessor) const stateServices = { + uriStateService: accessor.get(IVoidUriStateService), quickEditStateService: accessor.get(IQuickEditStateService), sidebarStateService: accessor.get(ISidebarStateService), chatThreadsStateService: accessor.get(IChatThreadService), @@ -99,7 +106,15 @@ export const _registerServices = (accessor: ServicesAccessor) => { inlineDiffsService: accessor.get(IInlineDiffsService), } - const { sidebarStateService, quickEditStateService, settingsStateService, chatThreadsStateService, refreshModelService, themeService, inlineDiffsService } = stateServices + const { uriStateService, sidebarStateService, quickEditStateService, settingsStateService, chatThreadsStateService, refreshModelService, themeService, inlineDiffsService } = stateServices + + uriState = uriStateService.state + disposables.push( + uriStateService.onDidChangeState(() => { + uriState = uriStateService.state + uriStateListeners.forEach(l => l(uriState)) + }) + ) quickEditState = quickEditStateService.state disposables.push( @@ -178,6 +193,7 @@ const getReactAccessor = (accessor: ServicesAccessor) => { IRefreshModelService: accessor.get(IRefreshModelService), IVoidSettingsService: accessor.get(IVoidSettingsService), IInlineDiffsService: accessor.get(IInlineDiffsService), + IVoidUriStateService: accessor.get(IVoidUriStateService), IQuickEditStateService: accessor.get(IQuickEditStateService), ISidebarStateService: accessor.get(ISidebarStateService), IChatThreadService: accessor.get(IChatThreadService), @@ -224,6 +240,16 @@ export const useAccessor = () => { // -- state of services -- +export const useUriState = () => { + const [s, ss] = useState(uriState) + useEffect(() => { + ss(uriState) + uriStateListeners.add(ss) + return () => { uriStateListeners.delete(ss) } + }, [ss]) + return s +} + export const useQuickEditState = () => { const [s, ss] = useState(quickEditState) useEffect(() => { diff --git a/src/vs/workbench/contrib/void/browser/sidebarActions.ts b/src/vs/workbench/contrib/void/browser/sidebarActions.ts index 3b260158..5787fc4d 100644 --- a/src/vs/workbench/contrib/void/browser/sidebarActions.ts +++ b/src/vs/workbench/contrib/void/browser/sidebarActions.ts @@ -26,10 +26,9 @@ import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; -import { URI } from '../../../../base/common/uri.js'; import { localize2 } from '../../../../nls.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; - +import { IVoidUriStateService } from './voidUriStateService.js'; // ---------- Register commands and keybindings ---------- @@ -241,7 +240,7 @@ registerAction2(class extends Action2 { export class TabSwitchListener extends Disposable { constructor( - onSwitchTab: (uri: URI) => void, + onSwitchTab: () => void, @ICodeEditorService private readonly _editorService: ICodeEditorService, ) { super() @@ -250,7 +249,7 @@ export class TabSwitchListener extends Disposable { const addTabSwitchListeners = (editor: ICodeEditor) => { this._register(editor.onDidChangeModel(e => { if (e.newModelUrl?.scheme !== 'file') return - onSwitchTab(e.newModelUrl) + onSwitchTab() })) } @@ -270,8 +269,10 @@ class TabSwitchContribution extends Disposable implements IWorkbenchContribution constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, - @ICommandService private readonly commandService: ICommandService, @IViewsService private readonly viewsService: IViewsService, + @IVoidUriStateService private readonly uriStateService: IVoidUriStateService, + @ICodeEditorService private readonly codeEditorService: ICodeEditorService, + // @ICommandService private readonly commandService: ICommandService, ) { super() @@ -281,18 +282,22 @@ class TabSwitchContribution extends Disposable implements IWorkbenchContribution sidebarIsVisible = e.visible })) - const addCurrentFileIfVisible = () => { - if (sidebarIsVisible) - this.commandService.executeCommand(VOID_ADD_SELECTION_TO_SIDEBAR_ACTION_ID) + const onSwitchTab = () => { // update state + if (sidebarIsVisible) { + const currentUri = this.codeEditorService.getActiveCodeEditor()?.getModel()?.uri + if (!currentUri) return; + this.uriStateService.setState({ currentUri }) + // this.commandService.executeCommand(VOID_ADD_SELECTION_TO_SIDEBAR_ACTION_ID) + } } // when sidebar becomes visible, add current file this._register(this.viewsService.onDidChangeViewVisibility(e => { sidebarIsVisible = e.visible })) // run on current tab if it exists, and listen for tab switches and visibility changes - addCurrentFileIfVisible() - this._register(this.viewsService.onDidChangeViewVisibility(() => { addCurrentFileIfVisible() })) - this._register(this.instantiationService.createInstance(TabSwitchListener, () => { addCurrentFileIfVisible() })) + onSwitchTab() + this._register(this.viewsService.onDidChangeViewVisibility(() => { onSwitchTab() })) + this._register(this.instantiationService.createInstance(TabSwitchListener, () => { onSwitchTab() })) } } diff --git a/src/vs/workbench/contrib/void/browser/voidUriStateService.ts b/src/vs/workbench/contrib/void/browser/voidUriStateService.ts new file mode 100644 index 00000000..1a89c29b --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/voidUriStateService.ts @@ -0,0 +1,57 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from '../../../../base/common/event.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { URI } from '../../../../base/common/uri.js'; +import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; +import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; + + + +// service that manages state +export type VoidUriState = { + currentUri?: URI +} + +export interface IVoidUriStateService { + readonly _serviceBrand: undefined; + + readonly state: VoidUriState; // readonly to the user + setState(newState: Partial): void; + onDidChangeState: Event; +} + +export const IVoidUriStateService = createDecorator('voidUriStateService'); +class VoidUriStateService extends Disposable implements IVoidUriStateService { + _serviceBrand: undefined; + + static readonly ID = 'voidUriStateService'; + + private readonly _onDidChangeState = new Emitter(); + readonly onDidChangeState: Event = this._onDidChangeState.event; + + + // state + state: VoidUriState + + constructor( + ) { + super() + + // initial state + this.state = { currentUri: undefined } + } + + setState(newState: Partial) { + + this.state = { ...this.state, ...newState } + this._onDidChangeState.fire() + } + + +} + +registerSingleton(IVoidUriStateService, VoidUriStateService, InstantiationType.Eager); From ddd9cf857f2e5b60e910a862824667376762e41a Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Thu, 16 Jan 2025 03:31:17 -0800 Subject: [PATCH 10/29] ux --- .../void/browser/react/src/sidebar-tsx/SidebarChat.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index e534a324..b6ba1130 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -312,9 +312,9 @@ export const SelectedFiles = ( rounded-md px-1 w-fit h-fit select-none - ${isThisSelectionProspective ? 'bg-void-1' : 'bg-void-bg-3 hover:brightness-95'} - text-void-fg-1 text-xs text-nowrap - border rounded-xs ${isClearHovered ? 'border-void-border-1' : 'border-void-border-2'} hover:border-void-border-1 + ${isThisSelectionProspective ? 'bg-void-1 text-void-fg-3' : 'bg-void-bg-3 hover:brightness-95 text-void-fg-1'} + text-xs text-nowrap + border rounded-xs ${isClearHovered && !isThisSelectionProspective ? 'border-void-border-1' : 'border-void-border-2'} hover:border-void-border-1 transition-all duration-150`} onClick={() => { if (isThisSelectionProspective) { // add prospective selection to selections @@ -343,7 +343,7 @@ export const SelectedFiles = ( {/* X button */} - {type === 'staging' && + {type === 'staging' && !isThisSelectionProspective && { @@ -360,7 +360,7 @@ export const SelectedFiles = (
{/* clear all selections button */} - {type !== 'staging' || allSelections.length === 0 || i !== allSelections.length - 1 + {type !== 'staging' || selections.length === 0 || i !== allSelections.length - 1 ? null :
Date: Thu, 16 Jan 2025 17:09:38 -0800 Subject: [PATCH 11/29] fix spacing --- .../void/browser/react/src/markdown/BlockCode.tsx | 10 ++++------ .../void/browser/react/src/sidebar-tsx/Sidebar.tsx | 2 +- .../void/browser/react/src/sidebar-tsx/SidebarChat.tsx | 5 +++++ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/BlockCode.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/BlockCode.tsx index fca06caa..43abd5b1 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/BlockCode.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/BlockCode.tsx @@ -9,23 +9,21 @@ import { VoidCodeEditor, VoidCodeEditorProps } from '../util/inputs.js'; export const BlockCode = ({ buttonsOnHover, ...codeEditorProps }: { buttonsOnHover?: React.ReactNode } & VoidCodeEditorProps) => { - const isSingleLine = !codeEditorProps.initValue.includes('\n') return ( <> -
- +
{buttonsOnHover === null ? null : ( -
-
+
+
{buttonsOnHover}
)} -
) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx index fe757a96..839a6679 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx @@ -28,7 +28,7 @@ export const Sidebar = ({ className }: { className: string }) => {
Date: Thu, 16 Jan 2025 17:10:32 -0800 Subject: [PATCH 12/29] spacing --- .../contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 94909ca5..607bd204 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -594,7 +594,7 @@ export const SidebarChat = () => { overflow-x-hidden overflow-y-auto `} - style={{ maxHeight: sidebarDimensions.height - historyDimensions.height - formDimensions.height - 30 }} // the height of the previousMessages is determined by all other heights + style={{ maxHeight: sidebarDimensions.height - historyDimensions.height - formDimensions.height - 40 }} // the height of the previousMessages is determined by all other heights > {/* previous messages */} {previousMessages.map((message, i) => From 789016709a44e47f9d17b41541ba5049950dae68 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Thu, 16 Jan 2025 20:02:22 -0800 Subject: [PATCH 13/29] multiple prospective --- .../react/src/sidebar-tsx/SidebarChat.tsx | 51 +++++++++++++------ 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 607bd204..c0a3b638 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -273,24 +273,36 @@ export const SelectedFiles = ( const accessor = useAccessor() const commandService = accessor.get('ICommandService') + // state for tracking prospective files const { currentUri } = useUriState() - + const [recentUris, setRecentUris] = useState([]) + const maxProspectiveFiles = 3 + useEffect(() => { + if (!currentUri) return + setRecentUris(prev => { + const withoutCurrent = prev.filter(uri => uri.fsPath !== currentUri.fsPath) // remove if already exists + const withCurrent = [currentUri, ...withoutCurrent] + return withCurrent.slice(0, maxProspectiveFiles) + }) + }, [currentUri]) let prospectiveSelections: CodeStagingSelection[] = [] - if ( // add a prospective file if type === 'staging' and if the user is in a file, and if the file is not selected yet - type === 'staging' - && currentUri - && !selections.find(s => s.range === null && s.fileURI.fsPath === currentUri.fsPath) - ) { - prospectiveSelections = [{ - type: 'File', - fileURI: currentUri, - selectionStr: null, - range: null, - }] + if (type === 'staging') { // add a prospective file if type === 'staging' and if the user is in a file, and if the file is not selected yet + prospectiveSelections = recentUris + .filter(uri => !selections.find(s => s.range === null && s.fileURI.fsPath === uri.fsPath)) + .map(uri => ({ + type: 'File', + fileURI: uri, + selectionStr: null, + range: null, + })) } const allSelections = [...selections, ...prospectiveSelections] + if (allSelections.length === 0) { + return null + } + return (
@@ -300,8 +312,10 @@ export const SelectedFiles = ( const isThisSelectionAFile = selection.selectionStr === null const isThisSelectionProspective = i > selections.length - 1 - return
{/* selection summary */}
{/* clear all selections button */} - {type !== 'staging' || selections.length === 0 || i !== allSelections.length - 1 + {type !== 'staging' || selections.length === 0 || i !== selections.length - 1 ? null :
} -
+
) + + return <> + {i === selections.length &&
} + {selectionHTML} + })} From 2e3f608805ba7304d6d8057639c347b48f148621 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Thu, 16 Jan 2025 20:17:49 -0800 Subject: [PATCH 14/29] better history handling --- .../browser/react/src/sidebar-tsx/SidebarChat.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index c0a3b638..3db1a6c6 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -276,19 +276,22 @@ export const SelectedFiles = ( // state for tracking prospective files const { currentUri } = useUriState() const [recentUris, setRecentUris] = useState([]) + const maxRecentUris = 10 const maxProspectiveFiles = 3 - useEffect(() => { + useEffect(() => { // handle recent files if (!currentUri) return setRecentUris(prev => { - const withoutCurrent = prev.filter(uri => uri.fsPath !== currentUri.fsPath) // remove if already exists + const withoutCurrent = prev.filter(uri => uri.fsPath !== currentUri.fsPath) // remove duplicates const withCurrent = [currentUri, ...withoutCurrent] - return withCurrent.slice(0, maxProspectiveFiles) + return withCurrent.slice(0, maxRecentUris) }) }, [currentUri]) let prospectiveSelections: CodeStagingSelection[] = [] - if (type === 'staging') { // add a prospective file if type === 'staging' and if the user is in a file, and if the file is not selected yet + if (type === 'staging') { // handle prospective files + // add a prospective file if type === 'staging' and if the user is in a file, and if the file is not selected yet prospectiveSelections = recentUris .filter(uri => !selections.find(s => s.range === null && s.fileURI.fsPath === uri.fsPath)) + .slice(0, maxProspectiveFiles) .map(uri => ({ type: 'File', fileURI: uri, @@ -312,7 +315,7 @@ export const SelectedFiles = ( const isThisSelectionAFile = selection.selectionStr === null const isThisSelectionProspective = i > selections.length - 1 - const selectionHTML = (
{ From 4b15885147772f42fd9bef46da14ad282f87989f Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Thu, 16 Jan 2025 21:38:06 -0800 Subject: [PATCH 15/29] summary styles --- .../react/src/sidebar-tsx/SidebarChat.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 3db1a6c6..54bc1d85 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -307,7 +307,7 @@ export const SelectedFiles = ( } return ( -
+
{allSelections.map((selection, i) => { @@ -322,16 +322,16 @@ export const SelectedFiles = ( > {/* selection summary */}
{ if (isThisSelectionProspective) { // add prospective selection to selections @@ -362,7 +362,7 @@ export const SelectedFiles = ( {/* X button */} {type === 'staging' && !isThisSelectionProspective && { e.stopPropagation(); // don't open/close selection if (type !== 'staging') return; @@ -370,7 +370,7 @@ export const SelectedFiles = ( setSelectionIsOpened(o => [...o.slice(0, i), ...o.slice(i + 1)]) }} > - + } @@ -420,7 +420,9 @@ export const SelectedFiles = (
) return <> - {i === selections.length &&
} + {selections.length > 0 && i === selections.length && +
// divider between `selections` and `prospectiveSelections` + } {selectionHTML} From 015943329fa217d22c426a98fdd7a53691e7a44a Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Thu, 16 Jan 2025 23:58:55 -0800 Subject: [PATCH 16/29] misc --- CHANGELOG.md | 2 ++ build/darwin/create-universal-app.js | 1 + build/darwin/sign.js | 8 ++++---- build/darwin/sign.ts | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e6d5909..a4ec6dbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ - Lots of new UI, misc bug fixes, and performance improvements. +- VS Code's default Ctrl+L is now Ctrl+M in Void (on Mac Cmd+L becomes Cmd+M). + - Switched from the MIT License to the Apache 2.0 License. Apache's attribution clause provides a small amount of protection to our source initiative. A huge shoutout to our many contributors. If you'd like to help build Void, diff --git a/build/darwin/create-universal-app.js b/build/darwin/create-universal-app.js index a3daf187..e6a355d5 100644 --- a/build/darwin/create-universal-app.js +++ b/build/darwin/create-universal-app.js @@ -4,6 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); +// Void explanation - product-build-darwin-universal.yml runs this (create-universal-app.ts), then sign.ts const path = require("path"); const fs = require("fs"); const minimatch = require("minimatch"); diff --git a/build/darwin/sign.js b/build/darwin/sign.js index feb5834f..90c2e825 100644 --- a/build/darwin/sign.js +++ b/build/darwin/sign.js @@ -78,24 +78,24 @@ async function main(buildDir) { // universal will get its copy from the x64 build. if (arch !== 'universal') { await (0, cross_spawn_promise_1.spawn)('plutil', [ - '-insert', + '-replace', // Void changed this to replace 'NSAppleEventsUsageDescription', '-string', - 'An application in Visual Studio Code wants to use AppleScript.', + 'An application in Void wants to use AppleScript.', `${infoPlistPath}` ]); await (0, cross_spawn_promise_1.spawn)('plutil', [ '-replace', 'NSMicrophoneUsageDescription', '-string', - 'An application in Visual Studio Code wants to use the Microphone.', + 'An application in Void wants to use the Microphone.', `${infoPlistPath}` ]); await (0, cross_spawn_promise_1.spawn)('plutil', [ '-replace', 'NSCameraUsageDescription', '-string', - 'An application in Visual Studio Code wants to use the Camera.', + 'An application in Void wants to use the Camera.', `${infoPlistPath}` ]); } diff --git a/build/darwin/sign.ts b/build/darwin/sign.ts index 9e605801..a41ca30d 100644 --- a/build/darwin/sign.ts +++ b/build/darwin/sign.ts @@ -89,7 +89,7 @@ async function main(buildDir?: string): Promise { // universal will get its copy from the x64 build. if (arch !== 'universal') { await spawn('plutil', [ - '-insert', + '-replace', // Void changed this to replace 'NSAppleEventsUsageDescription', '-string', 'An application in Void wants to use AppleScript.', From ce7ee62abeb04ed82f2e4a17a7f31a80c33cd723 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Fri, 17 Jan 2025 15:49:34 -0800 Subject: [PATCH 17/29] remove old files --- .../createJsProgramGraph.ts | 333 ------------------ .../void/LangaugeServerTest/findFunctions.ts | 62 ---- 2 files changed, 395 deletions(-) delete mode 100644 extensions/void/LangaugeServerTest/createJsProgramGraph.ts delete mode 100644 extensions/void/LangaugeServerTest/findFunctions.ts diff --git a/extensions/void/LangaugeServerTest/createJsProgramGraph.ts b/extensions/void/LangaugeServerTest/createJsProgramGraph.ts deleted file mode 100644 index 4fb9bcaf..00000000 --- a/extensions/void/LangaugeServerTest/createJsProgramGraph.ts +++ /dev/null @@ -1,333 +0,0 @@ -import * as vscode from 'vscode'; -import Parser from 'tree-sitter'; -import JavaScript from 'tree-sitter-javascript'; - -interface Definition { - file: string; - node: Parser.SyntaxNode; -} - -interface DefnUse { - parent: Parser.SyntaxNode; - file: string; -} - -interface ImportInfo { - source: string; - imported: string; -} - -class ProjectAnalyzer { - private parser: Parser; - private graph: Map>; - private visited: Set; - private parsedFiles: Map; - private imports: Map>; - private definitions: Map; - private fileStack: Set; - - constructor() { - this.parser = new Parser(); - this.parser.setLanguage(JavaScript); - this.graph = new Map(); - this.visited = new Set(); - this.parsedFiles = new Map(); - this.imports = new Map(); - this.definitions = new Map(); - this.fileStack = new Set(); - } - - async parseFile(filePath: string): Promise { - if (this.parsedFiles.has(filePath)) { - return this.parsedFiles.get(filePath)!; - } - - if (this.fileStack.has(filePath)) { - return null; // Circular import - } - - this.fileStack.add(filePath); - - try { - const uri = vscode.Uri.file(filePath); - const document = await vscode.workspace.openTextDocument(uri); - const code = document.getText(); - const tree = this.parser.parse(code); - - this.parsedFiles.set(filePath, tree); - this.collectImports(filePath, tree); - this.collectDefinitions(filePath, tree); - - return tree; - } catch (error) { - console.error(`Error parsing ${filePath}:`, error); - return null; - } finally { - this.fileStack.delete(filePath); - } - } - - private collectImports(filePath: string, tree: Parser.Tree): void { - const fileImports = new Map(); - - const visit = (node: Parser.SyntaxNode): void => { - if (node.type === 'import_declaration') { - const source = node.childForFieldName('source')?.text.slice(1, -1) ?? ''; - const specifiers = node.childForFieldName('specifiers'); - - specifiers?.children.forEach(spec => { - if (spec.type === 'import_specifier') { - const local = spec.childForFieldName('local')?.text ?? ''; - const imported = spec.childForFieldName('imported')?.text ?? ''; - fileImports.set(local, { source, imported }); - } - }); - } - node.children.forEach(visit); - }; - - visit(tree.rootNode); - this.imports.set(filePath, fileImports); - } - - private collectDefinitions(filePath: string, tree: Parser.Tree): void { - const visit = (node: Parser.SyntaxNode): void => { - if (node.type === 'function_declaration') { - const name = node.childForFieldName('name')?.text ?? ''; - this.definitions.set(name, { file: filePath, node }); - } - else if (node.type === 'variable_declarator') { - const name = node.childForFieldName('name')?.text; - const value = node.childForFieldName('value'); - if (name && (value?.type === 'arrow_function' || value?.type === 'function')) { - this.definitions.set(name, { file: filePath, node: value }); - } - } - node.children.forEach(visit); - }; - - visit(tree.rootNode); - } - - private async getTypeFromPosition(uri: vscode.Uri, position: vscode.Position): Promise { - const hover = await vscode.commands.executeCommand( - 'vscode.executeHoverProvider', - uri, - position - ); - - if (hover?.[0]?.contents.length) { - for (const content of hover[0].contents) { - let hoverText = typeof content === 'string' ? - content : - ('value' in content ? content.value : ''); - - // Remove typescript backticks if present - hoverText = hoverText.replace(/```typescript\s*/, '').replace(/```\s*$/, ''); - console.log('Processing hover text:', hoverText); - - // Extract the type information - look for the type after the colon - const typeMatches = [ - /:\s*([\w<>]+)(?:\[\])?/, // matches "foo: Type" or "foo: Type[]" - /var\s+\w+:\s*([\w<>]+)/, // matches "var foo: Type" - /\(type\)\s+[\w<>]+:\s*([\w<>]+)/, // matches "(type) foo: Type" - /\(method\)\s*([\w<>]+)\./ // matches "(method) Type.method" - ]; - - for (const pattern of typeMatches) { - const match = pattern.exec(hoverText); - if (match) { - let type = match[1]; - // Handle array types - if (hoverText.includes('[]')) { - return 'Array'; - } - // Extract base type from generics - if (type.includes('<')) { - type = type.split('<')[0]; - } - return type; - } - } - } - } - return null; - } - - private async getCallsInDefn(defnNode: Parser.SyntaxNode, currentFile: string): Promise> { - const calls = new Set(); - const fileImports = this.imports.get(currentFile) ?? new Map(); - const uri = vscode.Uri.file(currentFile); - - const visit = async (node: Parser.SyntaxNode): Promise => { - if (node.type === 'call_expression') { - const callee = node.childForFieldName('function'); - if (callee?.type === 'identifier') { - const name = callee.text; - const importInfo = fileImports.get(name); - if (importInfo) { - calls.add(`${importInfo.source}:${importInfo.imported}`); - } else { - calls.add(name); - } - } - else if (callee?.type === 'member_expression') { - const method = callee.childForFieldName('property')?.text; - const object = callee.childForFieldName('object'); - - if (method && object) { - const position = new vscode.Position( - object.startPosition.row, - object.startPosition.column - ); - - const type = await this.getTypeFromPosition(uri, position); - if (type) { - calls.add(`${type}.${method}`); - } else { - calls.add(`method:${method}`); - } - } - } - } - - for (const child of node.children) { - await visit(child); - } - }; - - await visit(defnNode); - return calls; - } - - private gotoDefn(name: string): Definition | null { - if (name.includes(':')) { - const [file, funcName] = name.split(':'); - const def = this.definitions.get(funcName); - return def ?? null; - } - - return this.definitions.get(name) ?? null; - } - - private getUses(defnNode: Parser.SyntaxNode, currentFile: string): DefnUse[] { - const uses: DefnUse[] = []; - - let fnName: string | undefined; - if (defnNode.type === 'function_declaration') { - fnName = defnNode.childForFieldName('name')?.text; - } else if (defnNode.type === 'arrow_function' || defnNode.type === 'function') { - const parent = defnNode.parent; - if (parent?.type === 'variable_declarator') { - fnName = parent.childForFieldName('name')?.text; - } - } - - if (!fnName) return uses; - - for (const [file, tree] of this.parsedFiles) { - const visit = (node: Parser.SyntaxNode): void => { - if (node.type === 'call_expression') { - const callee = node.childForFieldName('function'); - if (callee?.type === 'identifier' && callee.text === fnName) { - let current: Parser.SyntaxNode | null = node; - while (current) { - if (current.type === 'function_declaration' || - current.type === 'arrow_function' || - current.type === 'function') { - uses.push({ parent: current, file }); - break; - } - current = current.parent; - } - } - } - node.children.forEach(visit); - }; - - visit(tree.rootNode); - } - - return uses; - } - - private async visitAllNodesInGraphFromDefinition(defn: Parser.SyntaxNode, currentFile: string): Promise { - let defnName: string | undefined; - if (defn.type === 'function_declaration') { - defnName = defn.childForFieldName('name')?.text; - } else if (defn.type === 'arrow_function' || defn.type === 'function') { - const parent = defn.parent; - if (parent?.type === 'variable_declarator') { - defnName = parent.childForFieldName('name')?.text; - } - } - - if (!defnName) return; - - const fullName = `${currentFile}:${defnName}`; - if (this.visited.has(fullName)) return; - - const calls = await this.getCallsInDefn(defn, currentFile); - this.graph.set(fullName, calls); - this.visited.add(fullName); - - const callDefns = Array.from(calls).map(call => this.gotoDefn(call)); - for (const callDefn of callDefns) { - if (callDefn) { - await this.visitAllNodesInGraphFromDefinition(callDefn.node, callDefn.file); - } - } - - const defnUses = this.getUses(defn, currentFile); - for (const defnUse of defnUses) { - await this.visitAllNodesInGraphFromDefinition(defnUse.parent, defnUse.file); - } - } - - async analyze(entryFile: string): Promise>> { - const tree = await this.parseFile(entryFile); - if (!tree) return new Map(); - - const visit = async (node: Parser.SyntaxNode): Promise => { - if (node.type === 'function_declaration') { - await this.visitAllNodesInGraphFromDefinition(node, entryFile); - } - else if (node.type === 'variable_declarator') { - const value = node.childForFieldName('value'); - if (value?.type === 'arrow_function' || value?.type === 'function') { - await this.visitAllNodesInGraphFromDefinition(value, entryFile); - } - } - for (const child of node.children) { - await visit(child); - } - }; - - await visit(tree.rootNode); - return this.graph; - } -} - -export async function runTreeSitter(filePath?: string): Promise> | null> { - const editor = vscode.window.activeTextEditor; - if (!editor && !filePath) { - vscode.window.showWarningMessage('No active editor found'); - return null; - } - - try { - const targetPath = filePath ?? editor!.document.uri.fsPath; - const analyzer = new ProjectAnalyzer(); - const graph = await analyzer.analyze(targetPath); - - for (const [defn, calls] of graph) { - console.log(`${defn} calls: ${[...calls].join(', ')}`); - } - - return graph; - } catch (error) { - console.error('Error analyzing file:', error); - vscode.window.showErrorMessage('Error analyzing file'); - return null; - } -} \ No newline at end of file diff --git a/extensions/void/LangaugeServerTest/findFunctions.ts b/extensions/void/LangaugeServerTest/findFunctions.ts deleted file mode 100644 index 570b4369..00000000 --- a/extensions/void/LangaugeServerTest/findFunctions.ts +++ /dev/null @@ -1,62 +0,0 @@ -import * as vscode from 'vscode'; - -const legend = new vscode.SemanticTokensLegend([], []); - -export async function findFunctions() { - - const editor = vscode.window.activeTextEditor; - if (!editor) return; - const document = editor.document; - - const tokens = await vscode.commands.executeCommand( - 'vscode.provideDocumentSemanticTokens', - document.uri - ); - - if (!tokens) { - console.error('No tokens found'); - return []; - } - - const allTokens = decodeTokens(tokens, document); - - - return allTokens; -} - -function decodeTokens(tokens: vscode.SemanticTokens, document: vscode.TextDocument) { - const data = tokens.data; - const decodedTokens = []; - let line = 0; - let character = 0; - - for (let i = 0; i < data.length; i += 5) { - const deltaLine = data[i]; - const deltaStartChar = data[i + 1]; - const length = data[i + 2]; - const tokenTypeIdx = data[i + 3]; - const tokenModifierIdx = data[i + 4]; - - line += deltaLine; - character = deltaLine === 0 ? character + deltaStartChar : deltaStartChar; - - const type = legend.tokenTypes[tokenTypeIdx] || `(${tokenTypeIdx})`; - const modifier = legend.tokenModifiers[tokenModifierIdx] || `(${tokenModifierIdx})`; - - const tokenRange = new vscode.Range(line, character, line, character + length); - const tokenText = document.getText(tokenRange); - - decodedTokens.push({ - line, - startCharacter: character, - length, - type, - modifier, - text: tokenText, - }); - - console.log(`Token: '${tokenText}' | Type: ${type} | Modifier: ${modifier} | Line: ${line}, Character: ${character}`); - } - - return decodedTokens; -} From dce0378eb201e0964eb31aa6d890c4cdbb94a1fa Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Fri, 17 Jan 2025 16:39:24 -0800 Subject: [PATCH 18/29] fix cancel streaming --- .../browser/react/src/sidebar-tsx/SidebarChat.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 54bc1d85..69b015b2 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -548,7 +548,8 @@ export const SidebarChat = () => { // stream state const chatThreadsStreamState = useChatThreadsStreamState(chatThreadsState.currentThreadId) - const isCurrThreadStreaming = !!chatThreadsStreamState?.streamingToken + const streamingToken = chatThreadsStreamState?.streamingToken + const isStreaming = !!streamingToken const latestError = chatThreadsStreamState?.error const messageSoFar = chatThreadsStreamState?.messageSoFar @@ -569,7 +570,7 @@ export const SidebarChat = () => { const onSubmit = async () => { if (isDisabled) return - if (isCurrThreadStreaming) return + if (isStreaming) return // send message to LLM const userMessage = textAreaRef.current?.value ?? '' @@ -582,9 +583,9 @@ export const SidebarChat = () => { } const onAbort = () => { - const token = chatThreadsStreamState?.streamingToken - if (!token) return - chatThreadsService.cancelStreaming(token) + // this assumes an instant cancellation doesn't happen, since streamingToken state must have updated by this time + if (!streamingToken) return + chatThreadsService.cancelStreaming(streamingToken) } // const [_test_messages, _set_test_messages] = useState([]) @@ -626,7 +627,7 @@ export const SidebarChat = () => { )} {/* message stream */} - + @@ -700,7 +701,7 @@ export const SidebarChat = () => {
{/* submit / stop button */} - {isCurrThreadStreaming ? + {isStreaming ? // stop button Date: Fri, 17 Jan 2025 16:56:46 -0800 Subject: [PATCH 19/29] fixes --- .../workbench/contrib/void/browser/inlineDiffsService.ts | 2 +- .../void/browser/react/src/sidebar-tsx/SidebarChat.tsx | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index d6d04ade..d362fe29 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -1806,7 +1806,7 @@ class AcceptAllRejectAllWidget extends Widget implements IOverlayWidget { ]); // Style the container - buttons.style.zIndex = '1'; + buttons.style.zIndex = '2'; buttons.style.padding = '4px'; buttons.style.display = 'flex'; buttons.style.gap = '4px'; diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 69b015b2..5799a72e 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -315,7 +315,9 @@ export const SelectedFiles = ( const isThisSelectionAFile = selection.selectionStr === null const isThisSelectionProspective = i > selections.length - 1 - const selectionHTML = (
) - return <> + return {selections.length > 0 && i === selections.length &&
// divider between `selections` and `prospectiveSelections` } {selectionHTML} - +
})} From 268d2ae99ad8c4f14f9487cef85abc8e500c2137 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Fri, 17 Jan 2025 17:21:04 -0800 Subject: [PATCH 20/29] ux --- .../browser/react/src/sidebar-tsx/SidebarChat.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 5799a72e..d17ac19a 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -259,9 +259,9 @@ const getBasename = (pathStr: string) => { } export const SelectedFiles = ( - { type, selections, setSelections }: - | { type: 'past', selections: CodeSelection[]; setSelections?: undefined } - | { type: 'staging', selections: CodeStagingSelection[]; setSelections: ((newSelections: CodeStagingSelection[]) => void) } + { type, selections, setSelections, showProspectiveSelections }: + | { type: 'past', selections: CodeSelection[]; setSelections?: undefined, showProspectiveSelections?: undefined } + | { type: 'staging', selections: CodeStagingSelection[]; setSelections: ((newSelections: CodeStagingSelection[]) => void), showProspectiveSelections?: boolean } ) => { // index -> isOpened @@ -287,7 +287,7 @@ export const SelectedFiles = ( }) }, [currentUri]) let prospectiveSelections: CodeStagingSelection[] = [] - if (type === 'staging') { // handle prospective files + if (type === 'staging' && showProspectiveSelections) { // handle prospective files // add a prospective file if type === 'staging' and if the user is in a file, and if the file is not selected yet prospectiveSelections = recentUris .filter(uri => !selections.find(s => s.range === null && s.fileURI.fsPath === uri.fsPath)) @@ -621,7 +621,7 @@ export const SidebarChat = () => { overflow-x-hidden overflow-y-auto `} - style={{ maxHeight: sidebarDimensions.height - historyDimensions.height - formDimensions.height - 40 }} // the height of the previousMessages is determined by all other heights + style={{ maxHeight: sidebarDimensions.height - historyDimensions.height - formDimensions.height - 30 }} // the height of the previousMessages is determined by all other heights > {/* previous messages */} {previousMessages.map((message, i) => @@ -641,7 +641,7 @@ export const SidebarChat = () => {
{ {/* top row */} <> {/* selections */} - + {/* error message */} From 6f1905a962854530deb69b67e2a65b53f539965a Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Fri, 17 Jan 2025 17:30:52 -0800 Subject: [PATCH 21/29] fix streaming issue, cleanup chat error styles --- .../platform/void/common/llmMessageService.ts | 2 +- .../contrib/void/browser/chatThreadService.ts | 24 ++-- .../react/src/sidebar-tsx/SidebarChat.tsx | 133 ++++++++++-------- 3 files changed, 84 insertions(+), 75 deletions(-) diff --git a/src/vs/platform/void/common/llmMessageService.ts b/src/vs/platform/void/common/llmMessageService.ts index 0bd8bf13..caaeb0c8 100644 --- a/src/vs/platform/void/common/llmMessageService.ts +++ b/src/vs/platform/void/common/llmMessageService.ts @@ -65,7 +65,7 @@ export class LLMMessageService extends Disposable implements ILLMMessageService this._onRequestIdDone(e.requestId) })) this._register((this.channel.listen('onError_llm') satisfies Event)(e => { - console.log('Error in LLMMessageService:', JSON.stringify(e)) + console.error('Error in LLMMessageService:', JSON.stringify(e)) this.onErrorHooks_llm[e.requestId]?.(e) this._onRequestIdDone(e.requestId) })) diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index 0faf4725..7fede377 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -74,9 +74,9 @@ export type ThreadsState = { export type ThreadStreamState = { [threadId: string]: undefined | { - streamingToken?: string; error?: { message: string, fullError: Error | null }; messageSoFar?: string; + streamingToken?: string; } } @@ -177,6 +177,13 @@ class ChatThreadService extends Disposable implements IChatThreadService { // ---------- streaming ---------- + finishStreaming = (threadId: string, content: string, error?: { message: string, fullError: Error | null }) => { + // add assistant's message to chat history, and clear selection + const assistantHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content || null } + this._addMessageToThread(threadId, assistantHistoryElt) + this._setStreamState(threadId, { messageSoFar: undefined, streamingToken: undefined, error }) + } + async addUserMessageAndStreamResponse(userMessage: string) { const threadId = this.getCurrentThread().id @@ -192,12 +199,6 @@ class ChatThreadService extends Disposable implements IChatThreadService { const userHistoryElt: ChatMessage = { role: 'user', content: chat_prompt(instructions, selections), displayContent: instructions, selections: selections } this._addMessageToThread(threadId, userHistoryElt) - const onDone = (content: string, error?: { message: string, fullError: Error | null }) => { - // add assistant's message to chat history, and clear selection - const assistantHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content || null } - this._addMessageToThread(threadId, assistantHistoryElt) - this._setStreamState(threadId, { messageSoFar: undefined, streamingToken: undefined, error }) - } this._setStreamState(threadId, { error: undefined }) @@ -211,11 +212,10 @@ class ChatThreadService extends Disposable implements IChatThreadService { this._setStreamState(threadId, { messageSoFar: fullText }) }, onFinalMessage: ({ fullText: content }) => { - onDone(content) + this.finishStreaming(threadId, content) }, onError: (error) => { - console.log('Void Chat Error:', error) - onDone(this.streamState[threadId]?.messageSoFar ?? '', error) + this.finishStreaming(threadId, this.streamState[threadId]?.messageSoFar ?? '', error) }, useProviderFor: 'Ctrl+L', @@ -227,8 +227,8 @@ class ChatThreadService extends Disposable implements IChatThreadService { cancelStreaming(threadId: string) { const llmCancelToken = this.streamState[threadId]?.streamingToken - if (llmCancelToken) this._llmMessageService.abort(llmCancelToken) - this._setStreamState(threadId, { streamingToken: undefined }) + if (llmCancelToken !== undefined) this._llmMessageService.abort(llmCancelToken) + this.finishStreaming(threadId, this.streamState[threadId]?.messageSoFar ?? '') } dismissStreamError(threadId: string): void { diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index d17ac19a..17245a51 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -18,7 +18,7 @@ import { ErrorDisplay } from './ErrorDisplay.js'; import { OnError, ServiceSendLLMMessageParams } from '../../../../../../../platform/void/common/llmMessageTypes.js'; import { HistoryInputBox, InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'; import { TextAreaFns, VoidCodeEditorProps, VoidInputBox2 } from '../util/inputs.js'; -import { ModelDropdown } from '../void-settings-tsx/ModelDropdown.js'; +import { ModelDropdown, WarningBox } from '../void-settings-tsx/ModelDropdown.js'; import { chat_systemMessage, chat_prompt } from '../../../prompt/prompts.js'; import { ISidebarStateService } from '../../../sidebarStateService.js'; import { ILLMMessageService } from '../../../../../../../platform/void/common/llmMessageService.js'; @@ -29,6 +29,7 @@ import { VOID_CTRL_L_ACTION_ID } from '../../../actionIDs.js'; import { ArrowBigLeftDash, CopyX, Delete, FileX2, SquareX, X } from 'lucide-react'; import { filenameToVscodeLanguage } from '../../../helpers/detectLanguage.js'; import { Pencil } from 'lucide-react' +import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js'; export const IconX = ({ size, className = '', ...props }: { size: number, className?: string } & React.SVGProps) => { @@ -438,10 +439,47 @@ export const SelectedFiles = ( -const ChatBubble = ({ chatMessage, isLoading }: { - chatMessage: ChatMessage, - isLoading?: boolean, -}) => { +const ChatBubble_ = ({ isEditMode, isLoading, children, role }: { role: ChatMessage['role'], children: React.ReactNode, isLoading: boolean, isEditMode: boolean }) => { + + return
+
+ {children} + {isLoading && } +
+ + {/* edit button */} + {/* {role === 'user' && + { setIsEditMode(v => !v); }} + /> + } */} +
+} + + +const ChatBubble = ({ chatMessage, isLoading }: { chatMessage: ChatMessage, isLoading?: boolean, }) => { const role = chatMessage.role @@ -480,41 +518,9 @@ const ChatBubble = ({ chatMessage, isLoading }: { chatbubbleContents = } - return
-
- {chatbubbleContents} - {isLoading && } -
- - {/* edit button */} - {/* {role === 'user' && - { setIsEditMode(v => !v); }} - /> - } */} -
+ return + {chatbubbleContents} + } @@ -524,7 +530,8 @@ export const SidebarChat = () => { const textAreaFnsRef = useRef(null) const accessor = useAccessor() - const modelService = accessor.get('IModelService') + // const modelService = accessor.get('IModelService') + const commandService = accessor.get('ICommandService') // ----- HIGHER STATE ----- // sidebar state @@ -549,11 +556,10 @@ export const SidebarChat = () => { const selections = chatThreadsState.currentStagingSelections // stream state - const chatThreadsStreamState = useChatThreadsStreamState(chatThreadsState.currentThreadId) - const streamingToken = chatThreadsStreamState?.streamingToken - const isStreaming = !!streamingToken - const latestError = chatThreadsStreamState?.error - const messageSoFar = chatThreadsStreamState?.messageSoFar + const currThreadStreamState = useChatThreadsStreamState(chatThreadsState.currentThreadId) + const isStreaming = !!currThreadStreamState?.streamingToken + const latestError = currThreadStreamState?.error + const messageSoFar = currThreadStreamState?.messageSoFar // ----- SIDEBAR CHAT state (local) ----- @@ -585,9 +591,8 @@ export const SidebarChat = () => { } const onAbort = () => { - // this assumes an instant cancellation doesn't happen, since streamingToken state must have updated by this time - if (!streamingToken) return - chatThreadsService.cancelStreaming(streamingToken) + const threadId = currentThread.id + chatThreadsService.cancelStreaming(threadId) } // const [_test_messages, _set_test_messages] = useState([]) @@ -617,7 +622,7 @@ export const SidebarChat = () => { scrollContainerRef={scrollContainerRef} className={` w-full h-auto - flex flex-col gap-0 + flex flex-col gap-1 overflow-x-hidden overflow-y-auto `} @@ -631,6 +636,21 @@ export const SidebarChat = () => { {/* message stream */} + + {/* error message */} + {latestError === undefined ? null : +
+ { chatThreadsService.dismissStreamError(currentThread.id) }} + showDismiss={true} + /> + + { commandService.executeCommand(VOID_OPEN_SETTINGS_ACTION_ID) }} text='Open settings' /> +
+ } + @@ -655,18 +675,7 @@ export const SidebarChat = () => { {/* top row */} <> {/* selections */} - - - - {/* error message */} - {latestError === undefined ? null : - { chatThreadsService.dismissStreamError(currentThread.id) }} - showDismiss={true} - /> - } + {/* middle row */} From 66c880945ca00516c08a1deecf3fe3e0d17a4691 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Fri, 17 Jan 2025 17:43:21 -0800 Subject: [PATCH 22/29] ctrlk settings handling --- .../contrib/void/browser/inlineDiffsService.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index d362fe29..0b84908e 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -39,6 +39,8 @@ import { INotificationService, Severity } from '../../../../platform/notificatio import { isMacintosh } from '../../../../base/common/platform.js'; import { EditorOption } from '../../../../editor/common/config/editorOptions.js'; import { Emitter } from '../../../../base/common/event.js'; +import { VOID_OPEN_SETTINGS_ACTION_ID } from './voidSettingsPane.js'; +import { ICommandService } from '../../../../platform/commands/common/commands.js'; const configOfBG = (color: Color) => { return { dark: color, light: color, hcDark: color, hcLight: color, } @@ -249,6 +251,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { @IConsistentEditorItemService private readonly _consistentEditorItemService: IConsistentEditorItemService, @IMetricsService private readonly _metricsService: IMetricsService, @INotificationService private readonly _notificationService: INotificationService, + @ICommandService private readonly _commandService: ICommandService, ) { super(); @@ -1362,6 +1365,16 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { this._notificationService.notify({ severity: Severity.Warning, message: `Void Error: ${e.message}`, + actions: { + secondary: [{ + id: 'void.onerror.opensettings', + enabled: true, + label: 'Open Void settings', + tooltip: '', + class: undefined, + run: () => { this._commandService.executeCommand(VOID_OPEN_SETTINGS_ACTION_ID) } + }] + }, source: details ? `(Hold ${isMacintosh ? 'Option' : 'Alt'} to hover) - ${details}` : undefined }) onDone(true) From 7ec574eeeef420520dd2274e1bb0336d2d439ac9 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Fri, 17 Jan 2025 17:45:44 -0800 Subject: [PATCH 23/29] rm log --- src/vs/workbench/contrib/void/browser/inlineDiffsService.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index 0b84908e..143e60a1 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -1360,7 +1360,6 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { onDone(false) }, onError: (e) => { - console.error('Error rewriting file with diff', e); const details = errorDetails(e.fullError) this._notificationService.notify({ severity: Severity.Warning, From c6ff563e1b0be254c74353aef7af534ee28b3afc Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Fri, 17 Jan 2025 17:54:09 -0800 Subject: [PATCH 24/29] style --- .../void/browser/react/src/sidebar-tsx/SidebarChat.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 17245a51..b50914f8 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -324,8 +324,8 @@ export const SelectedFiles = ( `} > {/* selection summary */} -
+ :
setIsClearHovered(true)} From 85426e28bd803188ec3c402afa819cbc2dc1b10e Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Fri, 17 Jan 2025 17:59:11 -0800 Subject: [PATCH 25/29] + --- .../contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index b50914f8..10fe9f12 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -675,7 +675,7 @@ export const SidebarChat = () => { {/* top row */} <> {/* selections */} - + {/* middle row */} From 29c4603909a42712ee90119884d1be9662008419 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Fri, 17 Jan 2025 18:04:47 -0800 Subject: [PATCH 26/29] fix spacing --- .../contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx | 2 +- .../browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 10fe9f12..75e4579e 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -626,7 +626,7 @@ export const SidebarChat = () => { overflow-x-hidden overflow-y-auto `} - style={{ maxHeight: sidebarDimensions.height - historyDimensions.height - formDimensions.height - 30 }} // the height of the previousMessages is determined by all other heights + style={{ maxHeight: sidebarDimensions.height - historyDimensions.height - formDimensions.height - 36 }} // the height of the previousMessages is determined by all other heights > {/* previous messages */} {previousMessages.map((message, i) => diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx index a474de35..cca66c58 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx @@ -33,7 +33,7 @@ export const SidebarThreadSelector = () => { .filter(threadId => allThreads![threadId].messages.length !== 0) return ( -
+
{/* title */} From d3647bf93c476807a193a1261709cf8d9a8976c1 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Fri, 17 Jan 2025 19:02:46 -0800 Subject: [PATCH 27/29] red lines above green --- .../void/browser/inlineDiffsService.ts | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index 143e60a1..a81eefe1 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -599,7 +599,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { )); const viewZone: IViewZone = { - afterLineNumber: type === 'edit' ? diff.endLine : diff.startLine - 1, + afterLineNumber: diff.startLine - 1, heightInLines, minWidthInPx, domNode, @@ -626,6 +626,21 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { const consistentWidgetId = this._consistentItemService.addConsistentItemToURI({ uri, fn: (editor) => { + console.log('diffOriignal', diff.startLine, diff.originalStartLine, diff.type === 'deletion' && diff.originalEndLine) + + let startLine: number + let offsetLines: number + + if (diff.type === 'insertion' || diff.type === 'edit') { + startLine = diff.startLine // green start + offsetLines = 0 + } + else if (diff.type === 'deletion') { + startLine = diff.startLine - 1 + offsetLines = 1 + } + else { throw 1 } + const buttonsWidget = new AcceptRejectWidget({ editor, onAccept: () => { @@ -637,13 +652,8 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { this._metricsService.capture('Reject Diff', {}) }, diffid: diffid.toString(), - startLine: diff.startLine, - offsetLines: ( - diff.type === 'insertion' ? 0 - : diff.type === 'deletion' ? -(diff.originalEndLine - diff.originalStartLine + 1) - : diff.type === 'edit' ? (diff.endLine - diff.startLine + 1) - : 0 // not allowed - ) + startLine, + offsetLines }) return () => { buttonsWidget.dispose() } } From dc33d4d0c045bd6631586c909508be1196801372 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Fri, 17 Jan 2025 19:09:00 -0800 Subject: [PATCH 28/29] fix offset collision with deletion --- .../contrib/void/browser/inlineDiffsService.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index a81eefe1..093969af 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -636,8 +636,17 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { offsetLines = 0 } else if (diff.type === 'deletion') { - startLine = diff.startLine - 1 - offsetLines = 1 + // if diff.startLine is out of bounds + if (diff.startLine === 1) { + const numRedLines = diff.originalEndLine - diff.originalStartLine + 1 + startLine = diff.startLine + offsetLines = -numRedLines + } + else { + startLine = diff.startLine - 1 + offsetLines = 1 + } + } else { throw 1 } From f517dc79a90fe06f55abdd5b260dacf092ad729a Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Fri, 17 Jan 2025 19:10:13 -0800 Subject: [PATCH 29/29] rm console --- src/vs/workbench/contrib/void/browser/inlineDiffsService.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index 093969af..76bcbd8c 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -626,11 +626,8 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { const consistentWidgetId = this._consistentItemService.addConsistentItemToURI({ uri, fn: (editor) => { - console.log('diffOriignal', diff.startLine, diff.originalStartLine, diff.type === 'deletion' && diff.originalEndLine) - let startLine: number let offsetLines: number - if (diff.type === 'insertion' || diff.type === 'edit') { startLine = diff.startLine // green start offsetLines = 0 @@ -646,7 +643,6 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { startLine = diff.startLine - 1 offsetLines = 1 } - } else { throw 1 }