From 7dcb08b09ab03c06c69429643c303cd72c36316d Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Wed, 12 Feb 2025 20:50:04 -0800 Subject: [PATCH 01/10] comment out unwanted code --- .../void/browser/inlineDiffsService.ts | 322 +++++++++--------- .../contrib/void/browser/void.contribution.ts | 2 +- 2 files changed, 162 insertions(+), 162 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index 998a286f..2be7ae7b 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -25,7 +25,7 @@ import * as dom from '../../../../base/browser/dom.js'; import { Widget } from '../../../../base/browser/ui/widget.js'; import { URI } from '../../../../base/common/uri.js'; import { IConsistentEditorItemService, IConsistentItemService } from './helperServices/consistentItemService.js'; -import { voidPrefixAndSuffix, ctrlKStream_userMessage, ctrlKStream_systemMessage, fastApply_rewritewholething_userMessage, fastApply_rewritewholething_systemMessage, defaultFimTags, fastApply_searchreplace_systemMessage, fastApply_searchreplace_userMessage } from './prompt/prompts.js'; +import { voidPrefixAndSuffix, ctrlKStream_userMessage, ctrlKStream_systemMessage, fastApply_rewritewholething_userMessage, fastApply_rewritewholething_systemMessage, defaultFimTags } from './prompt/prompts.js'; import { mountCtrlK } from '../browser/react/out/quick-edit-tsx/index.js' import { QuickEditPropsType } from './quickEditActions.js'; @@ -1207,39 +1207,39 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { return null } - private _generateSearchAndReplaceBlocks({ filename, applyStr }: { filename: URI, applyStr: string }): DiffZone | undefined { + // private _generateSearchAndReplaceBlocks({ filename, applyStr }: { filename: URI, applyStr: string }): DiffZone | undefined { - // call LLM to generate search and replace blocks (outputs something like [{search: 'this is my code', replace: 'this is m'}, ... ]) + // // call LLM to generate search and replace blocks (outputs something like [{search: 'this is my code', replace: 'this is m'}, ... ]) - // 1a output search block + // // 1a output search block - let uri: URI + // let uri: URI - const uri_ = this._getActiveEditorURI() - if (!uri_) return - uri = uri_ + // const uri_ = this._getActiveEditorURI() + // if (!uri_) return + // uri = uri_ - // reject all diffZones on this URI, adding to history (there can't possibly be overlap after this) - this.removeDiffAreas({ uri, behavior: 'reject', removeCtrlKs: true }) + // // reject all diffZones on this URI, adding to history (there can't possibly be overlap after this) + // this.removeDiffAreas({ uri, behavior: 'reject', removeCtrlKs: true }) - // in ctrl+L the start and end lines are the full document + // // in ctrl+L the start and end lines are the full document - const numLines = this._getNumLines(uri) - if (numLines === null) return + // const numLines = this._getNumLines(uri) + // if (numLines === null) return - let startLine: number - let endLine: number + // let startLine: number + // let endLine: number - startLine = 1 - endLine = numLines + // startLine = 1 + // endLine = numLines - const currentFileStr = this._readURI(uri) - if (currentFileStr === null) return - const originalCode = currentFileStr.split('\n').slice((startLine - 1), (endLine - 1) + 1).join('\n') + // const currentFileStr = this._readURI(uri) + // if (currentFileStr === null) return + // const originalCode = currentFileStr.split('\n').slice((startLine - 1), (endLine - 1) + 1).join('\n') - // 1b find the start and end line that the search block lives on (if can't find it, retry 1a) + // // 1b find the start and end line that the search block lives on (if can't find it, retry 1a) @@ -1247,174 +1247,174 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { - let streamRequestIdRef: { current: string | null } = { current: null } + // let streamRequestIdRef: { current: string | null } = { current: null } - // add to history - const { onFinishEdit } = this._addToHistory(uri) + // // add to history + // const { onFinishEdit } = this._addToHistory(uri) - // __TODO__ let users customize modelFimTags - const isOllamaFIM = false // this._voidSettingsService.state.modelSelectionOfFeature['Ctrl+K']?.providerName === 'ollama' - const modelFimTags = defaultFimTags + // // __TODO__ let users customize modelFimTags + // const isOllamaFIM = false // this._voidSettingsService.state.modelSelectionOfFeature['Ctrl+K']?.providerName === 'ollama' + // const modelFimTags = defaultFimTags - const adding: Omit = { - type: 'DiffZone', - originalCode, - startLine, - endLine, - _URI: uri, - _streamState: { - isStreaming: true, - streamRequestIdRef, - line: startLine, - }, - _diffOfId: {}, // added later - _removeStylesFns: new Set(), - } - const diffZone = this._addDiffArea(adding) - this._onDidChangeStreaming.fire({ uri, diffareaid: diffZone.diffareaid }) - this._onDidAddOrDeleteDiffZones.fire({ uri }) + // const adding: Omit = { + // type: 'DiffZone', + // originalCode, + // startLine, + // endLine, + // _URI: uri, + // _streamState: { + // isStreaming: true, + // streamRequestIdRef, + // line: startLine, + // }, + // _diffOfId: {}, // added later + // _removeStylesFns: new Set(), + // } + // const diffZone = this._addDiffArea(adding) + // this._onDidChangeStreaming.fire({ uri, diffareaid: diffZone.diffareaid }) + // this._onDidAddOrDeleteDiffZones.fire({ uri }) - if (from === 'QuickEdit') { - const { diffareaid } = opts - const ctrlKZone = this.diffAreaOfId[diffareaid] - if (ctrlKZone.type !== 'CtrlKZone') return + // if (from === 'QuickEdit') { + // const { diffareaid } = opts + // const ctrlKZone = this.diffAreaOfId[diffareaid] + // if (ctrlKZone.type !== 'CtrlKZone') return - ctrlKZone._linkedStreamingDiffZone = diffZone.diffareaid - } + // ctrlKZone._linkedStreamingDiffZone = diffZone.diffareaid + // } - // now handle messages - let messages: LLMChatMessage[] + // // now handle messages + // let messages: LLMChatMessage[] - if (from === 'Chat') { - const userContent = fastApply_searchreplace_userMessage({ originalCode, applyStr: opts.applyStr, uri }) - messages = [ - { role: 'system', content: fastApply_rewritewholething_systemMessage, }, - { role: 'user', content: userContent, } - ] - } - else if (from === 'QuickEdit') { - const { diffareaid } = opts - const ctrlKZone = this.diffAreaOfId[diffareaid] - if (ctrlKZone.type !== 'CtrlKZone') return - const { _mountInfo } = ctrlKZone - const instructions = _mountInfo?.textAreaRef.current?.value ?? '' + // if (from === 'Chat') { + // const userContent = fastApply_searchreplace_userMessage({ originalCode, applyStr: opts.applyStr, uri }) + // messages = [ + // { role: 'system', content: fastApply_rewritewholething_systemMessage, }, + // { role: 'user', content: userContent, } + // ] + // } + // else if (from === 'QuickEdit') { + // const { diffareaid } = opts + // const ctrlKZone = this.diffAreaOfId[diffareaid] + // if (ctrlKZone.type !== 'CtrlKZone') return + // const { _mountInfo } = ctrlKZone + // const instructions = _mountInfo?.textAreaRef.current?.value ?? '' - // __TODO__ use Ollama's FIM api, if (isOllamaFIM) {...} else: - const { prefix, suffix } = voidPrefixAndSuffix({ fullFileStr: currentFileStr, startLine, endLine }) - // if (isOllamaFIM) { - // messages = { - // type: 'ollamaFIM', - // prefix, - // suffix, - // } + // // __TODO__ use Ollama's FIM api, if (isOllamaFIM) {...} else: + // const { prefix, suffix } = voidPrefixAndSuffix({ fullFileStr: currentFileStr, startLine, endLine }) + // // if (isOllamaFIM) { + // // messages = { + // // type: 'ollamaFIM', + // // prefix, + // // suffix, + // // } - // } - // else { - const language = filenameToVscodeLanguage(uri.fsPath) ?? '' - const userContent = ctrlKStream_userMessage({ selection: originalCode, instructions: instructions, prefix, suffix, isOllamaFIM: false, fimTags: modelFimTags, language }) - // type: 'messages', - messages = [ - { role: 'system', content: ctrlKStream_systemMessage({ fimTags: modelFimTags }), }, - { role: 'user', content: userContent, } - ] - // } - } - else { throw new Error(`featureName ${from} is invalid`) } + // // } + // // else { + // const language = filenameToVscodeLanguage(uri.fsPath) ?? '' + // const userContent = ctrlKStream_userMessage({ selection: originalCode, instructions: instructions, prefix, suffix, isOllamaFIM: false, fimTags: modelFimTags, language }) + // // type: 'messages', + // messages = [ + // { role: 'system', content: ctrlKStream_systemMessage({ fimTags: modelFimTags }), }, + // { role: 'user', content: userContent, } + // ] + // // } + // } + // else { throw new Error(`featureName ${from} is invalid`) } - const onDone = (hadError: boolean) => { - diffZone._streamState = { isStreaming: false, } - this._onDidChangeStreaming.fire({ uri, diffareaid: diffZone.diffareaid }) + // const onDone = (hadError: boolean) => { + // diffZone._streamState = { isStreaming: false, } + // this._onDidChangeStreaming.fire({ uri, diffareaid: diffZone.diffareaid }) - if (from === 'QuickEdit') { - const ctrlKZone = this.diffAreaOfId[opts.diffareaid] as CtrlKZone + // if (from === 'QuickEdit') { + // const ctrlKZone = this.diffAreaOfId[opts.diffareaid] as CtrlKZone - ctrlKZone._linkedStreamingDiffZone = null - this._deleteCtrlKZone(ctrlKZone) - } - this._refreshStylesAndDiffsInURI(uri) - onFinishEdit() + // ctrlKZone._linkedStreamingDiffZone = null + // this._deleteCtrlKZone(ctrlKZone) + // } + // this._refreshStylesAndDiffsInURI(uri) + // onFinishEdit() - // if had error, revert! - if (hadError) { - this._undoHistory(diffZone._URI) - } - } + // // if had error, revert! + // if (hadError) { + // this._undoHistory(diffZone._URI) + // } + // } - // refresh now in case onText takes a while to get 1st message - this._refreshStylesAndDiffsInURI(uri) + // // refresh now in case onText takes a while to get 1st message + // this._refreshStylesAndDiffsInURI(uri) - const extractText = (fullText: string, recentlyAddedTextLen: number) => { - if (from === 'QuickEdit') { - if (isOllamaFIM) return fullText - return extractCodeFromFIM({ text: fullText, recentlyAddedTextLen, midTag: modelFimTags.midTag }) - } - else if (from === 'Chat') { - return extractCodeFromRegular({ text: fullText, recentlyAddedTextLen }) - } - throw 1 - } + // const extractText = (fullText: string, recentlyAddedTextLen: number) => { + // if (from === 'QuickEdit') { + // if (isOllamaFIM) return fullText + // return extractCodeFromFIM({ text: fullText, recentlyAddedTextLen, midTag: modelFimTags.midTag }) + // } + // else if (from === 'Chat') { + // return extractCodeFromRegular({ text: fullText, recentlyAddedTextLen }) + // } + // throw 1 + // } - const latestStreamInfo = { line: diffZone.startLine, addedSplitYet: false, col: 1, originalCodeStartLine: 1 } + // const latestStreamInfo = { line: diffZone.startLine, addedSplitYet: false, col: 1, originalCodeStartLine: 1 } - // state used in onText: - let fullText = '' - let prevIgnoredSuffix = '' + // // state used in onText: + // let fullText = '' + // let prevIgnoredSuffix = '' - streamRequestIdRef.current = this._llmMessageService.sendLLMMessage({ - messagesType: 'chatMessages', - useProviderFor: opts.from === 'Chat' ? 'FastApply' : 'Ctrl+K', - logging: { loggingName: `startApplying - ${from}` }, - messages, - onText: ({ newText: newText_ }) => { + // streamRequestIdRef.current = this._llmMessageService.sendLLMMessage({ + // messagesType: 'chatMessages', + // useProviderFor: opts.from === 'Chat' ? 'FastApply' : 'Ctrl+K', + // logging: { loggingName: `startApplying - ${from}` }, + // messages, + // onText: ({ newText: newText_ }) => { - const newText = prevIgnoredSuffix + newText_ // add the previously ignored suffix because it's no longer the suffix! - fullText += prevIgnoredSuffix + newText + // const newText = prevIgnoredSuffix + newText_ // add the previously ignored suffix because it's no longer the suffix! + // fullText += prevIgnoredSuffix + newText - const [text, deltaText, ignoredSuffix] = extractText(fullText, newText.length) - this._writeStreamedDiffZoneLLMText(diffZone, text, deltaText, latestStreamInfo) - this._refreshStylesAndDiffsInURI(uri) + // const [text, deltaText, ignoredSuffix] = extractText(fullText, newText.length) + // this._writeStreamedDiffZoneLLMText(diffZone, text, deltaText, latestStreamInfo) + // this._refreshStylesAndDiffsInURI(uri) - prevIgnoredSuffix = ignoredSuffix - }, - onFinalMessage: ({ fullText }) => { - // console.log('DONE! FULL TEXT\n', extractText(fullText), diffZone.startLine, diffZone.endLine) - // at the end, re-write whole thing to make sure no sync errors - const [text, _] = extractText(fullText, 0) - this._writeText(uri, text, - { startLineNumber: diffZone.startLine, startColumn: 1, endLineNumber: diffZone.endLine, endColumn: Number.MAX_SAFE_INTEGER }, // 1-indexed - { shouldRealignDiffAreas: true } - ) - onDone(false) - }, - onError: (e) => { - const details = errorDetails(e.fullError) - 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) - }, + // prevIgnoredSuffix = ignoredSuffix + // }, + // onFinalMessage: ({ fullText }) => { + // // console.log('DONE! FULL TEXT\n', extractText(fullText), diffZone.startLine, diffZone.endLine) + // // at the end, re-write whole thing to make sure no sync errors + // const [text, _] = extractText(fullText, 0) + // this._writeText(uri, text, + // { startLineNumber: diffZone.startLine, startColumn: 1, endLineNumber: diffZone.endLine, endColumn: Number.MAX_SAFE_INTEGER }, // 1-indexed + // { shouldRealignDiffAreas: true } + // ) + // onDone(false) + // }, + // onError: (e) => { + // const details = errorDetails(e.fullError) + // 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) + // }, - }) + // }) - return diffZone + // return diffZone - } + // } diff --git a/src/vs/workbench/contrib/void/browser/void.contribution.ts b/src/vs/workbench/contrib/void/browser/void.contribution.ts index 19d20201..c10ca414 100644 --- a/src/vs/workbench/contrib/void/browser/void.contribution.ts +++ b/src/vs/workbench/contrib/void/browser/void.contribution.ts @@ -22,7 +22,7 @@ import './chatThreadService.js' import './autocompleteService.js' // register Context services -import './contextGatheringService.js' +// import './contextGatheringService.js' // import './contextUserChangesService.js' // settings pane From 198a948f6c060c52399cb4d832a6de9d59aedad0 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Fri, 14 Feb 2025 21:30:20 -0800 Subject: [PATCH 02/10] star button commit --- .../browser/parts/editor/editorActions.ts | 20 +++++++++++++++- .../editor/media/multieditortabscontrol.css | 18 ++++----------- .../parts/editor/multiEditorTabsControl.ts | 23 ++++++++++--------- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 1b1a39b3..39c4275b 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -432,7 +432,7 @@ export class UnpinEditorAction extends Action { label: string, @ICommandService private readonly commandService: ICommandService ) { - super(id, label, ThemeIcon.asClassName(Codicon.pinned)); + super(id, label, ThemeIcon.asClassName(Codicon.starFull)); } override run(context?: IEditorCommandsContext): Promise { @@ -440,6 +440,24 @@ export class UnpinEditorAction extends Action { } } +export class PinEditorAction extends Action { + + static readonly ID = 'workbench.action.pinEditor'; + static readonly LABEL = localize('pinEditor', "Pin Editor"); + + constructor( + id: string, + label: string, + @ICommandService private readonly commandService: ICommandService + ) { + super(id, label, ThemeIcon.asClassName(Codicon.star)); + } + + override async run(context?: IEditorCommandsContext): Promise { + return this.commandService.executeCommand('workbench.action.pinEditor', undefined, context); + } +} + export class CloseEditorTabAction extends Action { static readonly ID = 'workbench.action.closeActiveEditor'; diff --git a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css index 93559402..dd6c233f 100644 --- a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css @@ -385,7 +385,7 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-right.sizing-shrink > .tab-actions, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-right.sizing-fixed > .tab-actions { flex: 0; - overflow: hidden; /* let the tab actions be pushed out of view when sizing is set to shrink/fixed to make more room */ + overflow: visible; /* ensure tab actions are always visible */ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty.tab-actions-right.sizing-shrink > .tab-actions, @@ -399,18 +399,8 @@ overflow: visible; /* ...but still show the tab actions on hover, focus and when dirty or sticky */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-action-off:not(.dirty) > .tab-actions, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sticky-compact > .tab-actions { - display: none; /* hide the tab actions when we are configured to hide it (unless dirty, but always when sticky-compact) */ -} - -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active > .tab-actions .action-label, /* always show tab actions for active tab */ -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab > .tab-actions .action-label:focus, /* always show tab actions on focus */ -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab:hover > .tab-actions .action-label, /* always show tab actions on hover */ -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active:hover > .tab-actions .action-label, /* always show tab actions on hover */ -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.sticky:not(.pinned-action-off) > .tab-actions .action-label, /* always show tab actions for sticky tabs */ -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-actions .action-label { /* always show tab actions for dirty tabs */ - opacity: 1; + display: none; /* only hide tab actions when sticky-compact */ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-actions .actions-container { @@ -444,11 +434,11 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-actions .action-label, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sticky:not(.pinned-action-off) > .tab-actions .action-label, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover > .tab-actions .action-label { - opacity: 0.5; /* show tab actions dimmed for inactive group */ + opacity: 1; } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-actions .action-label { - opacity: 0; + opacity: 1; } /* Tab Actions: Off */ diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index b23be82e..bbda32c4 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -35,7 +35,7 @@ import { MergeGroupMode, IMergeGroupOptions } from '../../../services/editor/com import { addDisposableListener, EventType, EventHelper, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode, DragAndDropObserver, isMouseEvent, getWindow } from '../../../../base/browser/dom.js'; import { localize } from '../../../../nls.js'; import { IEditorGroupsView, EditorServiceImpl, IEditorGroupView, IInternalEditorOpenOptions, IEditorPartsView } from './editor.js'; -import { CloseEditorTabAction, UnpinEditorAction } from './editorActions.js'; +import { CloseEditorTabAction, PinEditorAction, UnpinEditorAction } from './editorActions.js'; import { assertAllDefined, assertIsDefined } from '../../../../base/common/types.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { basenameOrAuthority } from '../../../../base/common/resources.js'; @@ -113,6 +113,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { private readonly closeEditorAction = this._register(this.instantiationService.createInstance(CloseEditorTabAction, CloseEditorTabAction.ID, CloseEditorTabAction.LABEL)); private readonly unpinEditorAction = this._register(this.instantiationService.createInstance(UnpinEditorAction, UnpinEditorAction.ID, UnpinEditorAction.LABEL)); + private readonly pinEditorAction = this._register(this.instantiationService.createInstance(PinEditorAction, PinEditorAction.ID, PinEditorAction.LABEL)); // Add this line private readonly tabResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER)); private tabLabels: IEditorInputLabel[] = []; @@ -1518,28 +1519,28 @@ export class MultiEditorTabsControl extends EditorTabsControl { this.redrawTabLabel(editor, tabIndex, tabContainer, tabLabelWidget, tabLabel); // Action - const hasUnpinAction = isTabSticky && options.tabActionUnpinVisibility; - const hasCloseAction = !hasUnpinAction && options.tabActionCloseVisibility; - const hasAction = hasUnpinAction || hasCloseAction; + const hasCloseAction = options.tabActionCloseVisibility; + const hasAction = true; // Always show actions + // Determine which action to show let tabAction; - if (hasAction) { - tabAction = hasUnpinAction ? this.unpinEditorAction : this.closeEditorAction; + if (isTabSticky) { + tabAction = this.unpinEditorAction; } else { - // Even if the action is not visible, add it as it contains the dirty indicator - tabAction = isTabSticky ? this.unpinEditorAction : this.closeEditorAction; + tabAction = this.pinEditorAction; // Use pin action instead of close action } + // Update action bar if (!tabActionBar.hasAction(tabAction)) { if (!tabActionBar.isEmpty()) { tabActionBar.clear(); } - tabActionBar.push(tabAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(tabAction) }); } - tabContainer.classList.toggle(`pinned-action-off`, isTabSticky && !hasUnpinAction); - tabContainer.classList.toggle(`close-action-off`, !hasUnpinAction && !hasCloseAction); + tabContainer.classList.toggle('sticky', isTabSticky); + tabContainer.classList.toggle(`pinned-action-off`, false); + tabContainer.classList.toggle(`close-action-off`, !hasCloseAction); for (const option of ['left', 'right']) { tabContainer.classList.toggle(`tab-actions-${option}`, hasAction && options.tabActionLocation === option); From 249eee341d8b745bae6946783326e9aa24bc7f8e Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Sun, 16 Feb 2025 00:13:26 -0800 Subject: [PATCH 03/10] better context and file reading --- .../contrib/void/browser/chatThreadService.ts | 206 +++++++++++------- .../contrib/void/browser/helpers/readFile.ts | 39 +++- .../contrib/void/browser/prompt/prompts.ts | 64 ++++-- .../react/src/markdown/ChatMarkdownRender.tsx | 1 - .../react/src/sidebar-tsx/SidebarChat.tsx | 65 +++--- .../browser/react/src/sidebar-tsx/delete.tsx | 1 + .../contrib/void/browser/sidebarActions.ts | 17 +- .../contrib/void/common/toolsService.ts | 4 +- 8 files changed, 265 insertions(+), 132 deletions(-) create mode 100644 src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/delete.tsx diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index a3452eb2..248ab441 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -13,7 +13,9 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { IRange } from '../../../../editor/common/core/range.js'; import { ILLMMessageService } from '../common/llmMessageService.js'; import { IModelService } from '../../../../editor/common/services/model.js'; -import { chat_userMessage, chat_systemMessage } from './prompt/prompts.js'; +import { chat_userMessageContent, chat_systemMessage, chat_userMessageContentWithAllFilesToo as chat_userMessageContentWithAllFiles } from './prompt/prompts.js'; +import { LLMChatMessage } from '../common/llmMessageTypes.js'; +import { IFileService } from '../../../../platform/files/common/files.js'; // one of the square items that indicates a selection in a chat bubble (NOT a file, a Selection of text) export type CodeSelection = { @@ -32,23 +34,17 @@ export type FileSelection = { export type StagingSelectionItem = CodeSelection | FileSelection - -export type StagingInfo = { - isBeingEdited: boolean; - selections: StagingSelectionItem[] | null; // staging selections in edit mode -} - -const defaultStaging: StagingInfo = { isBeingEdited: false, selections: [] } - - // WARNING: changing this format is a big deal!!!!!! need to migrate old format to new format on users' computers so people don't get errors. export type ChatMessage = | { role: 'user'; - content: string | null; // content sent to the llm - allowed to be '', will be replaced with (empty) + content: string | null; // content displayed to the LLM on future calls - allowed to be '', will be replaced with (empty) displayContent: string | null; // content displayed to user - allowed to be '', will be ignored selections: StagingSelectionItem[] | null; // the user's selection - staging: StagingInfo | null + state: { + stagingSelections: StagingSelectionItem[]; + isBeingEdited: boolean; + } } | { role: 'assistant'; @@ -61,6 +57,11 @@ export type ChatMessage = displayContent?: undefined; } +type UserMessageType = ChatMessage & { role: 'user' } +type UserMessageState = UserMessageType['state'] + +export const defaultMessageState: UserMessageState = { stagingSelections: [], isBeingEdited: false } + // a 'thread' means a chat message history export type ChatThreads = { [id: string]: { @@ -68,11 +69,18 @@ export type ChatThreads = { createdAt: string; // ISO string lastModified: string; // ISO string messages: ChatMessage[]; - staging: StagingInfo | null; - focusedMessageIdx?: number | undefined; // index of the message that is being edited (undefined if none) + state: { + stagingSelections: StagingSelectionItem[]; + focusedMessageIdx: number | undefined; // index of the message that is being edited (undefined if none) + isCheckedOfSelectionId: { [selectionId: string]: boolean }; + } }; } +type ThreadType = ChatThreads[string] + +const defaultThreadState: ThreadType['state'] = { stagingSelections: [], focusedMessageIdx: undefined, isCheckedOfSelectionId: {} } + export type ThreadsState = { allThreads: ChatThreads; currentThreadId: string; // intended for internal use only @@ -94,11 +102,12 @@ const newThreadObject = () => { createdAt: now, lastModified: now, messages: [], - focusedMessageIdx: undefined, - staging: { - isBeingEdited: true, - selections: [], - } + state: { + stagingSelections: [], + focusedMessageIdx: undefined, + isCheckedOfSelectionId: {} + }, + } satisfies ChatThreads[string] } @@ -124,7 +133,9 @@ export interface IChatThreadService { isFocusingMessage(): boolean; setFocusedMessageIdx(messageIdx: number | undefined): void; - _useFocusedStagingState(messageIdx?: number | undefined): readonly [StagingInfo, (stagingInfo: StagingInfo) => void]; + // _useFocusedStagingState(messageIdx?: number | undefined): readonly [StagingInfo, (stagingInfo: StagingInfo) => void]; + _useCurrentThreadState(): readonly [ThreadType['state'], (newState: Partial) => void]; + _useCurrentMessageState(messageIdx: number): readonly [UserMessageState, (newState: Partial) => void]; editUserMessageAndStreamResponse(userMessage: string, messageIdx: number): Promise; addUserMessageAndStreamResponse(userMessage: string): Promise; @@ -150,6 +161,7 @@ class ChatThreadService extends Disposable implements IChatThreadService { constructor( @IStorageService private readonly _storageService: IStorageService, @IModelService private readonly _modelService: IModelService, + @IFileService private readonly _fileService: IFileService, @ILLMMessageService private readonly _llmMessageService: ILLMMessageService, ) { super() @@ -190,21 +202,19 @@ class ChatThreadService extends Disposable implements IChatThreadService { const threads: ChatThreads = oldThreadsObject /** v1 -> v2 - - threadsState.currentStagingSelections: CodeStagingSelection[] | null; - + thread.staging: StagingInfo - + thread.focusedMessageIdx?: number | undefined; - - + chatMessage.staging: StagingInfo | null - */ + - threads.state.currentStagingSelections: CodeStagingSelection[] | null; + + thread[threadIdx].state + + message.state +*/ // check if we need to update let shouldUpdate = false for (const thread of Object.values(threads)) { - if (!thread.staging) { + if (!thread.state) { shouldUpdate = true } for (const chatMessage of Object.values(thread.messages)) { - if (chatMessage.role === 'user' && !chatMessage.staging) { + if (chatMessage.role === 'user' && !chatMessage.state) { shouldUpdate = true } } @@ -214,13 +224,12 @@ class ChatThreadService extends Disposable implements IChatThreadService { // update the threads for (const thread of Object.values(threads)) { - if (!thread.staging) { - thread.staging = defaultStaging - thread.focusedMessageIdx = undefined + if (!thread.state) { + thread.state = defaultThreadState } for (const chatMessage of Object.values(thread.messages)) { - if (chatMessage.role === 'user' && !chatMessage.staging) { - chatMessage.staging = defaultStaging + if (chatMessage.role === 'user' && !chatMessage.state) { + chatMessage.state = defaultMessageState } } } @@ -245,6 +254,17 @@ class ChatThreadService extends Disposable implements IChatThreadService { this._onDidChangeCurrentThread.fire() } + private _getAllSelections() { + const thread = this.getCurrentThread() + return thread.messages.flatMap(m => m.role === 'user' && m.selections || []) + } + + private _getSelectionsUpToMessageIdx(messageIdx: number) { + const thread = this.getCurrentThread() + const prevMessages = thread.messages.slice(0, messageIdx) + return prevMessages.flatMap(m => m.role === 'user' && m.selections || []) + } + private _setStreamState(threadId: string, state: Partial>) { this.streamState[threadId] = { ...this.streamState[threadId], @@ -268,12 +288,14 @@ class ChatThreadService extends Disposable implements IChatThreadService { const thread = this.getCurrentThread() - const messageToReplace = thread.messages[messageIdx] - if (messageToReplace?.role !== 'user') { - console.log(`Error: tried to edit non-user message. messageIdx=${messageIdx}, numMessages=${thread.messages.length}`) - return + if (thread.messages?.[messageIdx]?.role !== 'user') { + throw new Error("Error: editing a message with role !=='user'") } + // get prev and curr selections before clearing the message + const prevSelns = this._getSelectionsUpToMessageIdx(messageIdx) + const currSelns = thread.messages[messageIdx].selections || [] + // clear messages up to the index const slicedMessages = thread.messages.slice(0, messageIdx) this._setState({ @@ -287,36 +309,45 @@ class ChatThreadService extends Disposable implements IChatThreadService { }, true) // stream the edit - this.addUserMessageAndStreamResponse(userMessage, messageToReplace.staging) + this.addUserMessageAndStreamResponse(userMessage, { prevSelns, currSelns }) } - async addUserMessageAndStreamResponse(userMessage: string, stagingOverride?: StagingInfo | null) { - + async addUserMessageAndStreamResponse(userMessage: string, options?: { prevSelns?: StagingSelectionItem[], currSelns?: StagingSelectionItem[] }) { const thread = this.getCurrentThread() const threadId = thread.id - let threadStaging = thread.staging - - const currStaging = stagingOverride ?? threadStaging ?? defaultStaging // don't use _useFocusedStagingState to avoid race conditions with focusing - const { selections: currSelns, } = currStaging - // add user's message to chat history const instructions = userMessage - const content = await chat_userMessage(instructions, currSelns, this._modelService) - const userHistoryElt: ChatMessage = { role: 'user', content: content, displayContent: instructions, selections: currSelns, staging: null, } + + const prevSelns: StagingSelectionItem[] = options?.prevSelns ?? this._getAllSelections() + const currSelns: StagingSelectionItem[] = options?.currSelns ?? thread.state.stagingSelections + + // read all curr+previous files on demand instead of adding them to the history + const messageContent = await chat_userMessageContent(instructions, prevSelns, currSelns) + const messageContentWithAllFiles = await chat_userMessageContentWithAllFiles(instructions, prevSelns, currSelns, this._modelService, this._fileService) + const prevLLMMessages = this.getCurrentThread().messages.map(m => ({ role: m.role, content: m.content || '(empty model output)' })) + const currLLMMessage: LLMChatMessage = { role: 'user', content: messageContentWithAllFiles } + + const userHistoryElt: ChatMessage = { role: 'user', content: messageContent, displayContent: instructions, selections: currSelns, state: defaultMessageState } this._addMessageToThread(threadId, userHistoryElt) this._setStreamState(threadId, { error: undefined }) + console.log(`messageContent`) + console.log([{ role: 'system', content: chat_systemMessage }, + ...prevLLMMessages, + currLLMMessage,]) + const llmCancelToken = this._llmMessageService.sendLLMMessage({ messagesType: 'chatMessages', logging: { loggingName: 'Chat' }, useProviderFor: 'Ctrl+L', messages: [ { role: 'system', content: chat_systemMessage }, - ...this.getCurrentThread().messages.map(m => ({ role: m.role, content: m.content || '(empty model output)' })), + ...prevLLMMessages, + currLLMMessage, ], onText: ({ newText, fullText }) => { this._setStreamState(threadId, { messageSoFar: fullText }) @@ -357,13 +388,13 @@ class ChatThreadService extends Disposable implements IChatThreadService { const thread = this.getCurrentThread() // get the focusedMessageIdx - const focusedMessageIdx = thread.focusedMessageIdx + const focusedMessageIdx = thread.state.focusedMessageIdx if (focusedMessageIdx === undefined) return; // check that the message is actually being edited const focusedMessage = thread.messages[focusedMessageIdx] if (focusedMessage.role !== 'user') return; - if (!focusedMessage.staging?.isBeingEdited) return; + if (!focusedMessage.state) return; return focusedMessageIdx } @@ -429,28 +460,34 @@ class ChatThreadService extends Disposable implements IChatThreadService { ...this.state.allThreads, [threadId]: { ...thread, - focusedMessageIdx: messageIdx + state: { + ...thread.state, + focusedMessageIdx: messageIdx, + } } } }, true) } - // set thread.messages[messageIdx].stagingSelections - private setEditMessageStaging(staging: StagingInfo, messageIdx: number): void { + // set message.state + private _setCurrentMessageState(state: Partial, messageIdx: number): void { - const thread = this.getCurrentThread() - const message = thread.messages[messageIdx] - if (message.role !== 'user') return; + const threadId = this.state.currentThreadId + const thread = this.state.allThreads[threadId] + if (!thread) return this._setState({ allThreads: { ...this.state.allThreads, - [thread.id]: { + [threadId]: { ...thread, messages: thread.messages.map((m, i) => - i === messageIdx ? { + i === messageIdx && m.role === 'user' ? { ...m, - staging, + state: { + ...m.state, + ...state + }, } : m ) } @@ -459,48 +496,53 @@ class ChatThreadService extends Disposable implements IChatThreadService { } - // set thread.stagingSelections - private setDefaultStaging(staging: StagingInfo): void { + // set thread.state + private _setCurrentThreadState(state: Partial): void { - const thread = this.getCurrentThread() + const threadId = this.state.currentThreadId + const thread = this.state.allThreads[threadId] + if (!thread) return this._setState({ allThreads: { ...this.state.allThreads, [thread.id]: { ...thread, - staging, + state: { + ...thread.state, + ...state + } } } }, true) } - // gets `staging` and `setStaging` of the currently focused element, given the index of the currently selected message (or undefined if no message is selected) - _useFocusedStagingState(messageIdx?: number | undefined) { - const defaultStaging = { isBeingEdited: false, selections: [], text: '' } - - let staging: StagingInfo = defaultStaging - let setStaging: (selections: StagingInfo) => void = () => { } + _useCurrentMessageState(messageIdx: number) { const thread = this.getCurrentThread() - const isFocusingMessage = messageIdx !== undefined - if (isFocusingMessage) { // is editing message + const messages = thread.messages + const currMessage = messages[messageIdx] - const message = thread.messages[messageIdx!] - if (message.role === 'user') { - staging = message.staging || defaultStaging - setStaging = (s) => this.setEditMessageStaging(s, messageIdx) - } - - } - else { // is editing the default input box - staging = thread.staging || defaultStaging - setStaging = this.setDefaultStaging.bind(this) + if (currMessage.role !== 'user') { + return [defaultMessageState, (s: any) => { }] as const } - return [staging, setStaging] as const + const state = currMessage.state + const setState = (newState: Partial) => this._setCurrentMessageState(newState, messageIdx) + + return [state, setState] as const + + } + + _useCurrentThreadState() { + const thread = this.getCurrentThread() + + const state = thread.state + const setState = this._setCurrentThreadState.bind(this) + + return [state, setState] as const } diff --git a/src/vs/workbench/contrib/void/browser/helpers/readFile.ts b/src/vs/workbench/contrib/void/browser/helpers/readFile.ts index b0f154d1..f7752b84 100644 --- a/src/vs/workbench/contrib/void/browser/helpers/readFile.ts +++ b/src/vs/workbench/contrib/void/browser/helpers/readFile.ts @@ -3,14 +3,41 @@ import { EndOfLinePreference } from '../../../../../editor/common/model' import { IModelService } from '../../../../../editor/common/services/model.js' import { IFileService } from '../../../../../platform/files/common/files' -// read files from VSCode. preferred (but appears to only work if the model of this URI already exists. If it doesn't use the other function.) -export const VSReadFile = async (modelService: IModelService, uri: URI): Promise => { - const model = modelService.getModel(uri) - if (!model) return null - return model.getValue(EndOfLinePreference.LF) + +// attempts to read URI of currently opened model, then of raw file +export const VSReadFile = async (modelService: IModelService, fileService: IFileService, uri: URI) => { + + const modelResult = await _VSReadModel(modelService, uri) + if (modelResult) return modelResult + + const fileResult = await _VSReadFileRaw(fileService, uri) + if (fileResult) return fileResult + + return '' + } -export const VSReadFileRaw = async (fileService: IFileService, uri: URI) => { +// read files from VSCode. preferred (but appears to only work if the model of this URI already exists. If it doesn't use the other function.) +export const _VSReadModel = async (modelService: IModelService, uri: URI): Promise => { + + // attempt to read saved model (sometimes doesn't work if page is reloaded) + const model = modelService.getModel(uri) + if (model) { + return model.getValue(EndOfLinePreference.LF) + } + + // look at all opened models and check if they have the same `fsPath` + const models = modelService.getModels(); + for (const model of models) { + if (model.uri.fsPath.toString() === uri.fsPath.toString()) { + return model.getValue(EndOfLinePreference.LF); + } + } + + return null +} + +export const _VSReadFileRaw = async (fileService: IFileService, uri: URI) => { const res = await fileService.readFile(uri) const str = res.value.toString() return str diff --git a/src/vs/workbench/contrib/void/browser/prompt/prompts.ts b/src/vs/workbench/contrib/void/browser/prompt/prompts.ts index b3fb4482..5ecb924a 100644 --- a/src/vs/workbench/contrib/void/browser/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/browser/prompt/prompts.ts @@ -7,8 +7,9 @@ import { URI } from '../../../../../base/common/uri.js'; import { filenameToVscodeLanguage } from '../helpers/detectLanguage.js'; import { CodeSelection, StagingSelectionItem, FileSelection } from '../chatThreadService.js'; -import { VSReadFile } from '../helpers/readFile.js'; +import { _VSReadModel, VSReadFile } from '../helpers/readFile.js'; import { IModelService } from '../../../../../editor/common/services/model.js'; +import { IFileService } from '../../../../../platform/files/common/files.js'; // this is just for ease of readability @@ -156,10 +157,10 @@ ${tripleTick[1]} } const failToReadStr = 'Could not read content. This file may have been deleted. If you expected content here, you can tell the user about this as they might not know.' -const stringifyFileSelections = async (fileSelections: FileSelection[], modelService: IModelService) => { +const stringifyFileSelections = async (fileSelections: FileSelection[], modelService: IModelService, fileService: IFileService) => { if (fileSelections.length === 0) return null const fileSlns: FileSelnLocal[] = await Promise.all(fileSelections.map(async (sel) => { - const content = await VSReadFile(modelService, sel.fileURI) ?? failToReadStr + const content = await VSReadFile(modelService, fileService, sel.fileURI) ?? failToReadStr return { ...sel, content } })) return fileSlns.map(sel => stringifyFileSelection(sel)).join('\n') @@ -167,23 +168,60 @@ const stringifyFileSelections = async (fileSelections: FileSelection[], modelSer const stringifyCodeSelections = (codeSelections: CodeSelection[]) => { return codeSelections.map(sel => stringifyCodeSelection(sel)).join('\n') } +const stringifySelectionNames = (currSelns: StagingSelectionItem[] | null): string => { + if (!currSelns) return '' + return currSelns.map(s => `${s.fileURI.fsPath}${s.range ? ` (lines ${s.range.startLineNumber}:${s.range.endLineNumber})` : ''}`).join('\n') +} +export const chat_userMessageContent = async (instructions: string, prevSelns: StagingSelectionItem[] | null, currSelns: StagingSelectionItem[] | null) => { - -export const chat_userMessage = async (instructions: string, selections: StagingSelectionItem[] | null, modelService: IModelService) => { - const fileSelections = selections?.filter(s => s.type === 'File') as FileSelection[] - const codeSelections = selections?.filter(s => s.type === 'Selection') as CodeSelection[] - - const filesStr = await stringifyFileSelections(fileSelections, modelService) - const codeStr = stringifyCodeSelections(codeSelections) + const selnsStr = stringifySelectionNames(currSelns) let str = '' - if (filesStr) str += `FILES\n${filesStr}\n` - if (codeStr) str += `SELECTIONS\n${codeStr}\n` - str += `INSTRUCTIONS\n${instructions}` + if (selnsStr) { str += `SELECTIONS\n${selnsStr}\n` } + str += `\nINSTRUCTIONS\n${instructions}` return str; }; +export const chat_userMessageContentWithAllFilesToo = async (instructions: string, prevSelns: StagingSelectionItem[] | null, currSelns: StagingSelectionItem[] | null, modelService: IModelService, fileService: IFileService) => { + + // ADD IN FILES AT TOP + const allSelections = [...currSelns || [], ...prevSelns || []] + + const codeSelections: CodeSelection[] = [] + const fileSelections: FileSelection[] = [] + const filesURIs = new Set() + + for (const selection of allSelections) { + if (selection.type === 'Selection') { + codeSelections.push(selection) + } + else if (selection.type === 'File') { + const fileSelection = selection + const path = fileSelection.fileURI.fsPath + if (!filesURIs.has(path)) { + filesURIs.add(path) + fileSelections.push(fileSelection) + } + } + } + + const filesStr = await stringifyFileSelections(fileSelections, modelService, fileService) + const selnsStr = stringifyCodeSelections(codeSelections) + + // ACTUAL MESSAGE CONTENT + const messageContent = await chat_userMessageContent(instructions, prevSelns, currSelns) + + + let str = '' + + str += 'ALL FILE CONTENTS\n' + if (filesStr) str += `${filesStr}\n` + if (selnsStr) str += `${selnsStr}\n` + if (messageContent) str += `\n${messageContent}\n` + + return str; +}; diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx index 86afcc33..351a399a 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx @@ -92,7 +92,6 @@ const RenderToken = ({ token, nested = false, noSpace = false, chatLocation, tok // deal with built-in tokens first (assume marked token) const t = token as MarkedToken - console.log(t.raw) if (t.type === "space") { return {t.raw} 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 2aaf9dd2..52944476 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 @@ -7,7 +7,7 @@ import React, { ButtonHTMLAttributes, FormEvent, FormHTMLAttributes, Fragment, K import { useAccessor, useSidebarState, useChatThreadsState, useChatThreadsStreamState, useUriState, useSettingsState } from '../util/services.js'; -import { ChatMessage, StagingInfo, StagingSelectionItem } from '../../../chatThreadService.js'; +import { ChatMessage, StagingSelectionItem } from '../../../chatThreadService.js'; import { BlockCode } from '../markdown/BlockCode.js'; import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js'; @@ -156,8 +156,8 @@ interface VoidChatAreaProps { showSelections?: boolean; showProspectiveSelections?: boolean; - staging?: StagingInfo - setStaging?: (s: StagingInfo) => void + selections?: StagingSelectionItem[] + setSelections?: (s: StagingSelectionItem[]) => void // selections?: any[]; // onSelectionsChange?: (selections: any[]) => void; @@ -180,8 +180,8 @@ export const VoidChatArea: React.FC = ({ featureName, showSelections = false, showProspectiveSelections = true, - staging, - setStaging, + selections, + setSelections, }) => { return (
= ({ }} > {/* Selections section */} - {showSelections && staging && setStaging && ( + {showSelections && selections && setSelections && ( setStaging({ ...staging, selections })} + selections={selections} + setSelections={setSelections} showProspectiveSelections={showProspectiveSelections} /> )} @@ -550,9 +550,23 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM const accessor = useAccessor() const chatThreadsService = accessor.get('IChatThreadService') - // edit mode state - const [staging, setStaging] = chatThreadsService._useFocusedStagingState(messageIdx) - const mode: ChatBubbleMode = staging.isBeingEdited ? 'edit' : 'display' + // global state + let isBeingEdited = false + let setIsBeingEdited = (v: boolean) => { } + let stagingSelections: StagingSelectionItem[] = [] + let setStagingSelections = (s: StagingSelectionItem[]) => { } + + if (messageIdx !== undefined) { + const [_state, _setState] = chatThreadsService._useCurrentMessageState(messageIdx) + isBeingEdited = _state.isBeingEdited + setIsBeingEdited = (v) => _setState({ isBeingEdited: v }) + stagingSelections = _state.stagingSelections + setStagingSelections = (s) => { _setState({ stagingSelections: s }) } + } + + + // local state + const mode: ChatBubbleMode = isBeingEdited ? 'edit' : 'display' const [isFocused, setIsFocused] = useState(false) const [isHovered, setIsHovered] = useState(false) const [isDisabled, setIsDisabled] = useState(false) @@ -565,10 +579,8 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM const canInitialize = role === 'user' && mode === 'edit' && textAreaRefState const shouldInitialize = _justEnabledEdit.current || _mustInitialize.current if (canInitialize && shouldInitialize) { - setStaging({ - ...staging, - selections: chatMessage.selections || [], - }) + setStagingSelections(chatMessage.selections || []) + if (textAreaFnsRef.current) textAreaFnsRef.current.setValue(chatMessage.displayContent || '') @@ -581,14 +593,14 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM }, [role, mode, _justEnabledEdit, textAreaRefState, textAreaFnsRef.current, _justEnabledEdit.current, _mustInitialize.current]) const EditSymbol = mode === 'display' ? Pencil : X const onOpenEdit = () => { - setStaging({ ...staging, isBeingEdited: true }) + setIsBeingEdited(true) chatThreadsService.setFocusedMessageIdx(messageIdx) _justEnabledEdit.current = true } const onCloseEdit = () => { setIsFocused(false) setIsHovered(false) - setStaging({ ...staging, isBeingEdited: false }) + setIsBeingEdited(false) chatThreadsService.setFocusedMessageIdx(undefined) } @@ -614,7 +626,7 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM chatThreadsService.cancelStreaming(thread.id) // reset state - setStaging({ ...staging, isBeingEdited: false }) + setIsBeingEdited(false) chatThreadsService.setFocusedMessageIdx(undefined) // stream the edit @@ -649,8 +661,8 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM showSelections={true} showProspectiveSelections={false} featureName="Ctrl+L" - staging={staging} - setStaging={setStaging} + selections={stagingSelections} + setSelections={setStagingSelections} > { const currentThread = chatThreadsService.getCurrentThread() const previousMessages = currentThread?.messages ?? [] - const [staging, setStaging] = chatThreadsService._useFocusedStagingState() + + const [_state, _setState] = chatThreadsService._useCurrentThreadState() + const selections = _state.stagingSelections + const setSelections = (s: StagingSelectionItem[]) => { _setState({ stagingSelections: s }) } // stream state const currThreadStreamState = useChatThreadsStreamState(chatThreadsState.currentThreadId) @@ -797,11 +812,11 @@ export const SidebarChat = () => { const userMessage = textAreaRef.current?.value ?? '' await chatThreadsService.addUserMessageAndStreamResponse(userMessage) - setStaging({ ...staging, selections: [], }) // clear staging + setSelections([]) // clear staging textAreaFnsRef.current?.setValue('') textAreaRef.current?.focus() // focus input after submit - }, [chatThreadsService, isDisabled, isStreaming, textAreaRef, textAreaFnsRef, staging, setStaging]) + }, [chatThreadsService, isDisabled, isStreaming, textAreaRef, textAreaFnsRef, selections, setSelections]) const onAbort = () => { const threadId = currentThread.id @@ -887,8 +902,8 @@ export const SidebarChat = () => { isDisabled={isDisabled} showSelections={true} showProspectiveSelections={prevMessagesHTML.length === 0} - staging={staging} - setStaging={setStaging} + selections={selections} + setSelections={setSelections} onClickAnywhere={() => { textAreaRef.current?.focus() }} featureName="Ctrl+L" > diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/delete.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/delete.tsx new file mode 100644 index 00000000..07a09338 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/delete.tsx @@ -0,0 +1 @@ +A B C diff --git a/src/vs/workbench/contrib/void/browser/sidebarActions.ts b/src/vs/workbench/contrib/void/browser/sidebarActions.ts index d65c51a7..2e64c53f 100644 --- a/src/vs/workbench/contrib/void/browser/sidebarActions.ts +++ b/src/vs/workbench/contrib/void/browser/sidebarActions.ts @@ -135,9 +135,20 @@ registerAction2(class extends Action2 { const chatThreadService = accessor.get(IChatThreadService) const focusedMessageIdx = chatThreadService.getFocusedMessageIdx() - const [staging, setStaging] = chatThreadService._useFocusedStagingState(focusedMessageIdx) - const selections = staging.selections || [] - const setSelections = (s: StagingSelectionItem[]) => setStaging({ ...staging, selections: s }) + + // set the selections to the proper value + let selections: StagingSelectionItem[] = [] + let setSelections = (s: StagingSelectionItem[]) => { } + + if (focusedMessageIdx === undefined) { + const [state, setState] = chatThreadService._useCurrentThreadState() + selections = state.stagingSelections + setSelections = (s) => setState({ stagingSelections: s }) + } else { + const [state, setState] = chatThreadService._useCurrentMessageState(focusedMessageIdx) + selections = state.stagingSelections + setSelections = (s) => setState({ stagingSelections: s }) + } // if matches with existing selection, overwrite (since text may change) const matchingStagingEltIdx = findMatchingStagingIndex(selections, selection) diff --git a/src/vs/workbench/contrib/void/common/toolsService.ts b/src/vs/workbench/contrib/void/common/toolsService.ts index 8ffd6b9b..e96186c9 100644 --- a/src/vs/workbench/contrib/void/common/toolsService.ts +++ b/src/vs/workbench/contrib/void/common/toolsService.ts @@ -4,7 +4,7 @@ import { IFileService, IFileStat } from '../../../../platform/files/common/files import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js' import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js' import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js' -import { VSReadFileRaw } from '../../../../workbench/contrib/void/browser/helpers/readFile.js' +import { _VSReadFileRaw } from '../../../../workbench/contrib/void/browser/helpers/readFile.js' import { QueryBuilder } from '../../../../workbench/services/search/common/queryBuilder.js' import { ISearchService } from '../../../../workbench/services/search/common/search.js' @@ -140,7 +140,7 @@ export class ToolService implements IToolService { this.contextToolCallFns = { read_file: async ({ uri: uriStr }) => { const uri = validateURI(uriStr) - const fileContents = await VSReadFileRaw(fileService, uri) + const fileContents = await _VSReadFileRaw(fileService, uri) return fileContents ?? '(could not read file)' }, list_dir: async ({ uri: uriStr }) => { From 36b1b56690870b1d99565e44837fc867ff672aa6 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Sun, 16 Feb 2025 00:53:11 -0800 Subject: [PATCH 04/10] bug --- .../contrib/void/browser/react/src/sidebar-tsx/delete.tsx | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/delete.tsx diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/delete.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/delete.tsx deleted file mode 100644 index 07a09338..00000000 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/delete.tsx +++ /dev/null @@ -1 +0,0 @@ -A B C From 41fe5c50e2567fd58083bf52e26c30530df6aec0 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Sun, 16 Feb 2025 01:48:25 -0800 Subject: [PATCH 05/10] fix scrollbars --- .../react/src/util/useScrollbarStyles.tsx | 188 ++++++++++-------- 1 file changed, 109 insertions(+), 79 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/useScrollbarStyles.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/useScrollbarStyles.tsx index 7f8ceb34..94df3aac 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/useScrollbarStyles.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/useScrollbarStyles.tsx @@ -1,7 +1,6 @@ import { useEffect } from 'react'; export const useScrollbarStyles = (containerRef: React.MutableRefObject) => { - useEffect(() => { if (!containerRef.current) return; @@ -12,90 +11,121 @@ export const useScrollbarStyles = (containerRef: React.MutableRefObject { + // Get all matching elements within the container, including the container itself + const scrollElements = [ + ...(containerRef.current?.matches(overflowSelector) ? [containerRef.current] : []), + ...Array.from(containerRef.current?.querySelectorAll(overflowSelector) || []) + ]; - // Apply styles and listeners to each scroll element - scrollElements.forEach(element => { - // Add the scrollable class directly to the overflow element - element.classList.add('void-scrollable-element'); - - let fadeTimeout: NodeJS.Timeout | null = null; - let fadeInterval: NodeJS.Timeout | null = null; - - const fadeIn = () => { - if (fadeInterval) clearInterval(fadeInterval); - - let step = 0; - fadeInterval = setInterval(() => { - if (step <= 10) { - element.classList.remove(`show-scrollbar-${step - 1}`); - element.classList.add(`show-scrollbar-${step}`); - step++; - } else { - clearInterval(fadeInterval!); - } - }, 10); - }; - - const fadeOut = () => { - if (fadeInterval) clearInterval(fadeInterval); - - let step = 10; - fadeInterval = setInterval(() => { - if (step >= 0) { - element.classList.remove(`show-scrollbar-${step + 1}`); - element.classList.add(`show-scrollbar-${step}`); - step--; - } else { - clearInterval(fadeInterval!); - } - }, 60); - }; - - const onMouseEnter = () => { - if (fadeTimeout) clearTimeout(fadeTimeout); - if (fadeInterval) clearInterval(fadeInterval); - fadeIn(); - }; - - const onMouseLeave = () => { - if (fadeTimeout) clearTimeout(fadeTimeout); - fadeTimeout = setTimeout(() => { - fadeOut(); - }, 10); - }; - - element.addEventListener('mouseenter', onMouseEnter); - element.addEventListener('mouseleave', onMouseLeave); - - // Store cleanup function - const cleanup = () => { - element.removeEventListener('mouseenter', onMouseEnter); - element.removeEventListener('mouseleave', onMouseLeave); - if (fadeTimeout) clearTimeout(fadeTimeout); - if (fadeInterval) clearInterval(fadeInterval); - element.classList.remove('void-scrollable-element'); - // Remove any remaining show-scrollbar classes - for (let i = 0; i <= 10; i++) { - element.classList.remove(`show-scrollbar-${i}`); - } - }; - - // Store the cleanup function on the element for later use - (element as any).__scrollbarCleanup = cleanup; - }); - - return () => { - // Clean up all scroll elements + // Clean up existing elements first scrollElements.forEach(element => { if ((element as any).__scrollbarCleanup) { (element as any).__scrollbarCleanup(); } }); + + // Apply styles and listeners to each scroll element + scrollElements.forEach(element => { + // Add the scrollable class directly to the overflow element + element.classList.add('void-scrollable-element'); + + let fadeTimeout: NodeJS.Timeout | null = null; + let fadeInterval: NodeJS.Timeout | null = null; + + const fadeIn = () => { + if (fadeInterval) clearInterval(fadeInterval); + + let step = 0; + fadeInterval = setInterval(() => { + if (step <= 10) { + element.classList.remove(`show-scrollbar-${step - 1}`); + element.classList.add(`show-scrollbar-${step}`); + step++; + } else { + clearInterval(fadeInterval!); + } + }, 10); + }; + + const fadeOut = () => { + if (fadeInterval) clearInterval(fadeInterval); + + let step = 10; + fadeInterval = setInterval(() => { + if (step >= 0) { + element.classList.remove(`show-scrollbar-${step + 1}`); + element.classList.add(`show-scrollbar-${step}`); + step--; + } else { + clearInterval(fadeInterval!); + } + }, 60); + }; + + const onMouseEnter = () => { + if (fadeTimeout) clearTimeout(fadeTimeout); + if (fadeInterval) clearInterval(fadeInterval); + fadeIn(); + }; + + const onMouseLeave = () => { + if (fadeTimeout) clearTimeout(fadeTimeout); + fadeTimeout = setTimeout(() => { + fadeOut(); + }, 10); + }; + + element.addEventListener('mouseenter', onMouseEnter); + element.addEventListener('mouseleave', onMouseLeave); + + // Store cleanup function + const cleanup = () => { + element.removeEventListener('mouseenter', onMouseEnter); + element.removeEventListener('mouseleave', onMouseLeave); + if (fadeTimeout) clearTimeout(fadeTimeout); + if (fadeInterval) clearInterval(fadeInterval); + element.classList.remove('void-scrollable-element'); + // Remove any remaining show-scrollbar classes + for (let i = 0; i <= 10; i++) { + element.classList.remove(`show-scrollbar-${i}`); + } + }; + + // Store the cleanup function on the element for later use + (element as any).__scrollbarCleanup = cleanup; + }); + }; + + // Initialize for the first time + initializeScrollbarStyles(); + + // Set up mutation observer + const observer = new MutationObserver((mutations) => { + initializeScrollbarStyles(); + }); + + // Start observing the container for child changes + observer.observe(containerRef.current, { + childList: true, + subtree: true + }); + + return () => { + observer.disconnect(); + // Your existing cleanup code... + if (containerRef.current) { + const scrollElements = [ + ...(containerRef.current.matches(overflowSelector) ? [containerRef.current] : []), + ...Array.from(containerRef.current.querySelectorAll(overflowSelector)) + ]; + scrollElements.forEach(element => { + if ((element as any).__scrollbarCleanup) { + (element as any).__scrollbarCleanup(); + } + }); + } }; }, [containerRef]); }; From 74f8303803c6308770e0d525004f5b6decc64da2 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Sun, 16 Feb 2025 20:11:13 -0800 Subject: [PATCH 06/10] discard star changes --- .../browser/parts/editor/editorActions.ts | 20 +--------------- .../editor/media/multieditortabscontrol.css | 18 +++++++++++---- .../parts/editor/multiEditorTabsControl.ts | 23 +++++++++---------- 3 files changed, 26 insertions(+), 35 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 39c4275b..1b1a39b3 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -432,7 +432,7 @@ export class UnpinEditorAction extends Action { label: string, @ICommandService private readonly commandService: ICommandService ) { - super(id, label, ThemeIcon.asClassName(Codicon.starFull)); + super(id, label, ThemeIcon.asClassName(Codicon.pinned)); } override run(context?: IEditorCommandsContext): Promise { @@ -440,24 +440,6 @@ export class UnpinEditorAction extends Action { } } -export class PinEditorAction extends Action { - - static readonly ID = 'workbench.action.pinEditor'; - static readonly LABEL = localize('pinEditor', "Pin Editor"); - - constructor( - id: string, - label: string, - @ICommandService private readonly commandService: ICommandService - ) { - super(id, label, ThemeIcon.asClassName(Codicon.star)); - } - - override async run(context?: IEditorCommandsContext): Promise { - return this.commandService.executeCommand('workbench.action.pinEditor', undefined, context); - } -} - export class CloseEditorTabAction extends Action { static readonly ID = 'workbench.action.closeActiveEditor'; diff --git a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css index dd6c233f..93559402 100644 --- a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css @@ -385,7 +385,7 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-right.sizing-shrink > .tab-actions, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-right.sizing-fixed > .tab-actions { flex: 0; - overflow: visible; /* ensure tab actions are always visible */ + overflow: hidden; /* let the tab actions be pushed out of view when sizing is set to shrink/fixed to make more room */ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty.tab-actions-right.sizing-shrink > .tab-actions, @@ -399,8 +399,18 @@ overflow: visible; /* ...but still show the tab actions on hover, focus and when dirty or sticky */ } +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-action-off:not(.dirty) > .tab-actions, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sticky-compact > .tab-actions { - display: none; /* only hide tab actions when sticky-compact */ + display: none; /* hide the tab actions when we are configured to hide it (unless dirty, but always when sticky-compact) */ +} + +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active > .tab-actions .action-label, /* always show tab actions for active tab */ +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab > .tab-actions .action-label:focus, /* always show tab actions on focus */ +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab:hover > .tab-actions .action-label, /* always show tab actions on hover */ +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active:hover > .tab-actions .action-label, /* always show tab actions on hover */ +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.sticky:not(.pinned-action-off) > .tab-actions .action-label, /* always show tab actions for sticky tabs */ +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-actions .action-label { /* always show tab actions for dirty tabs */ + opacity: 1; } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-actions .actions-container { @@ -434,11 +444,11 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-actions .action-label, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sticky:not(.pinned-action-off) > .tab-actions .action-label, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover > .tab-actions .action-label { - opacity: 1; + opacity: 0.5; /* show tab actions dimmed for inactive group */ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-actions .action-label { - opacity: 1; + opacity: 0; } /* Tab Actions: Off */ diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index bbda32c4..b23be82e 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -35,7 +35,7 @@ import { MergeGroupMode, IMergeGroupOptions } from '../../../services/editor/com import { addDisposableListener, EventType, EventHelper, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode, DragAndDropObserver, isMouseEvent, getWindow } from '../../../../base/browser/dom.js'; import { localize } from '../../../../nls.js'; import { IEditorGroupsView, EditorServiceImpl, IEditorGroupView, IInternalEditorOpenOptions, IEditorPartsView } from './editor.js'; -import { CloseEditorTabAction, PinEditorAction, UnpinEditorAction } from './editorActions.js'; +import { CloseEditorTabAction, UnpinEditorAction } from './editorActions.js'; import { assertAllDefined, assertIsDefined } from '../../../../base/common/types.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { basenameOrAuthority } from '../../../../base/common/resources.js'; @@ -113,7 +113,6 @@ export class MultiEditorTabsControl extends EditorTabsControl { private readonly closeEditorAction = this._register(this.instantiationService.createInstance(CloseEditorTabAction, CloseEditorTabAction.ID, CloseEditorTabAction.LABEL)); private readonly unpinEditorAction = this._register(this.instantiationService.createInstance(UnpinEditorAction, UnpinEditorAction.ID, UnpinEditorAction.LABEL)); - private readonly pinEditorAction = this._register(this.instantiationService.createInstance(PinEditorAction, PinEditorAction.ID, PinEditorAction.LABEL)); // Add this line private readonly tabResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER)); private tabLabels: IEditorInputLabel[] = []; @@ -1519,28 +1518,28 @@ export class MultiEditorTabsControl extends EditorTabsControl { this.redrawTabLabel(editor, tabIndex, tabContainer, tabLabelWidget, tabLabel); // Action - const hasCloseAction = options.tabActionCloseVisibility; - const hasAction = true; // Always show actions + const hasUnpinAction = isTabSticky && options.tabActionUnpinVisibility; + const hasCloseAction = !hasUnpinAction && options.tabActionCloseVisibility; + const hasAction = hasUnpinAction || hasCloseAction; - // Determine which action to show let tabAction; - if (isTabSticky) { - tabAction = this.unpinEditorAction; + if (hasAction) { + tabAction = hasUnpinAction ? this.unpinEditorAction : this.closeEditorAction; } else { - tabAction = this.pinEditorAction; // Use pin action instead of close action + // Even if the action is not visible, add it as it contains the dirty indicator + tabAction = isTabSticky ? this.unpinEditorAction : this.closeEditorAction; } - // Update action bar if (!tabActionBar.hasAction(tabAction)) { if (!tabActionBar.isEmpty()) { tabActionBar.clear(); } + tabActionBar.push(tabAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(tabAction) }); } - tabContainer.classList.toggle('sticky', isTabSticky); - tabContainer.classList.toggle(`pinned-action-off`, false); - tabContainer.classList.toggle(`close-action-off`, !hasCloseAction); + tabContainer.classList.toggle(`pinned-action-off`, isTabSticky && !hasUnpinAction); + tabContainer.classList.toggle(`close-action-off`, !hasUnpinAction && !hasCloseAction); for (const option of ['left', 'right']) { tabContainer.classList.toggle(`tab-actions-${option}`, hasAction && options.tabActionLocation === option); From 89d08071fcbc45af5477e7a2bf160e9b0b80b3eb Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Mon, 17 Feb 2025 00:23:48 -0800 Subject: [PATCH 07/10] scrollbar fix --- .../react/src/util/useScrollbarStyles.tsx | 135 +++++++++--------- 1 file changed, 66 insertions(+), 69 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/useScrollbarStyles.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/useScrollbarStyles.tsx index 94df3aac..1ba69c24 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/useScrollbarStyles.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/useScrollbarStyles.tsx @@ -19,90 +19,87 @@ export const useScrollbarStyles = (containerRef: React.MutableRefObject { - if ((element as any).__scrollbarCleanup) { - (element as any).__scrollbarCleanup(); - } + element.classList.add('void-scrollable-element'); }); - // Apply styles and listeners to each scroll element + // Only initialize fade effects for elements that haven't been initialized yet scrollElements.forEach(element => { - // Add the scrollable class directly to the overflow element - element.classList.add('void-scrollable-element'); + if (!(element as any).__scrollbarCleanup) { + let fadeTimeout: NodeJS.Timeout | null = null; + let fadeInterval: NodeJS.Timeout | null = null; - let fadeTimeout: NodeJS.Timeout | null = null; - let fadeInterval: NodeJS.Timeout | null = null; + const fadeIn = () => { + if (fadeInterval) clearInterval(fadeInterval); - const fadeIn = () => { - if (fadeInterval) clearInterval(fadeInterval); + let step = 0; + fadeInterval = setInterval(() => { + if (step <= 10) { + element.classList.remove(`show-scrollbar-${step - 1}`); + element.classList.add(`show-scrollbar-${step}`); + step++; + } else { + clearInterval(fadeInterval!); + } + }, 10); + }; - let step = 0; - fadeInterval = setInterval(() => { - if (step <= 10) { - element.classList.remove(`show-scrollbar-${step - 1}`); - element.classList.add(`show-scrollbar-${step}`); - step++; - } else { - clearInterval(fadeInterval!); + const fadeOut = () => { + if (fadeInterval) clearInterval(fadeInterval); + + let step = 10; + fadeInterval = setInterval(() => { + if (step >= 0) { + element.classList.remove(`show-scrollbar-${step + 1}`); + element.classList.add(`show-scrollbar-${step}`); + step--; + } else { + clearInterval(fadeInterval!); + } + }, 60); + }; + + const onMouseEnter = () => { + if (fadeTimeout) clearTimeout(fadeTimeout); + if (fadeInterval) clearInterval(fadeInterval); + fadeIn(); + }; + + const onMouseLeave = () => { + if (fadeTimeout) clearTimeout(fadeTimeout); + fadeTimeout = setTimeout(() => { + fadeOut(); + }, 10); + }; + + element.addEventListener('mouseenter', onMouseEnter); + element.addEventListener('mouseleave', onMouseLeave); + + // Store cleanup function + const cleanup = () => { + element.removeEventListener('mouseenter', onMouseEnter); + element.removeEventListener('mouseleave', onMouseLeave); + if (fadeTimeout) clearTimeout(fadeTimeout); + if (fadeInterval) clearInterval(fadeInterval); + element.classList.remove('void-scrollable-element'); + // Remove any remaining show-scrollbar classes + for (let i = 0; i <= 10; i++) { + element.classList.remove(`show-scrollbar-${i}`); } - }, 10); - }; + }; - const fadeOut = () => { - if (fadeInterval) clearInterval(fadeInterval); - - let step = 10; - fadeInterval = setInterval(() => { - if (step >= 0) { - element.classList.remove(`show-scrollbar-${step + 1}`); - element.classList.add(`show-scrollbar-${step}`); - step--; - } else { - clearInterval(fadeInterval!); - } - }, 60); - }; - - const onMouseEnter = () => { - if (fadeTimeout) clearTimeout(fadeTimeout); - if (fadeInterval) clearInterval(fadeInterval); - fadeIn(); - }; - - const onMouseLeave = () => { - if (fadeTimeout) clearTimeout(fadeTimeout); - fadeTimeout = setTimeout(() => { - fadeOut(); - }, 10); - }; - - element.addEventListener('mouseenter', onMouseEnter); - element.addEventListener('mouseleave', onMouseLeave); - - // Store cleanup function - const cleanup = () => { - element.removeEventListener('mouseenter', onMouseEnter); - element.removeEventListener('mouseleave', onMouseLeave); - if (fadeTimeout) clearTimeout(fadeTimeout); - if (fadeInterval) clearInterval(fadeInterval); - element.classList.remove('void-scrollable-element'); - // Remove any remaining show-scrollbar classes - for (let i = 0; i <= 10; i++) { - element.classList.remove(`show-scrollbar-${i}`); - } - }; - - // Store the cleanup function on the element for later use - (element as any).__scrollbarCleanup = cleanup; + // Store the cleanup function on the element for later use + (element as any).__scrollbarCleanup = cleanup; + } }); }; // Initialize for the first time initializeScrollbarStyles(); - // Set up mutation observer - const observer = new MutationObserver((mutations) => { + // Set up mutation observer to do the same + const observer = new MutationObserver(() => { initializeScrollbarStyles(); }); From b92420012c01a69a698152d3b5d5db44a90e7f94 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Mon, 17 Feb 2025 15:18:11 -0800 Subject: [PATCH 08/10] minor --- .../contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx | 1 - 1 file changed, 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 52944476..88b47355 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 @@ -703,7 +703,6 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM : role === 'user' ? `px-2 self-end w-fit max-w-full whitespace-pre-wrap` // user words should be pre : role === 'assistant' ? `px-2 self-start w-full max-w-full` : '' } - ${role !== 'assistant' ? 'my-2' : ''} `} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} From d3aa0bc3cc5a5da3cfc29d42ba5c33814c1112e2 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Mon, 17 Feb 2025 17:56:20 -0800 Subject: [PATCH 09/10] fix readfile --- .../contrib/void/browser/helpers/readFile.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/helpers/readFile.ts b/src/vs/workbench/contrib/void/browser/helpers/readFile.ts index f7752b84..7c03f036 100644 --- a/src/vs/workbench/contrib/void/browser/helpers/readFile.ts +++ b/src/vs/workbench/contrib/void/browser/helpers/readFile.ts @@ -20,7 +20,7 @@ export const VSReadFile = async (modelService: IModelService, fileService: IFile // read files from VSCode. preferred (but appears to only work if the model of this URI already exists. If it doesn't use the other function.) export const _VSReadModel = async (modelService: IModelService, uri: URI): Promise => { - // attempt to read saved model (sometimes doesn't work if page is reloaded) + // attempt to read saved model (doesn't work if application was reloaded...) const model = modelService.getModel(uri) if (model) { return model.getValue(EndOfLinePreference.LF) @@ -38,7 +38,12 @@ export const _VSReadModel = async (modelService: IModelService, uri: URI): Promi } export const _VSReadFileRaw = async (fileService: IFileService, uri: URI) => { - const res = await fileService.readFile(uri) - const str = res.value.toString() - return str + try { + const res = await fileService.readFile(uri) + const str = res.value.toString() + return str + } catch (e) { + return '' + } + } From baa89cc17d88379f6d0774ca6a1479dba1560cdc Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Tue, 18 Feb 2025 14:14:38 -0800 Subject: [PATCH 10/10] dummy marker service --- .../void/browser/MarkerCheckService.ts | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/vs/workbench/contrib/void/browser/MarkerCheckService.ts diff --git a/src/vs/workbench/contrib/void/browser/MarkerCheckService.ts b/src/vs/workbench/contrib/void/browser/MarkerCheckService.ts new file mode 100644 index 00000000..f41c0513 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/MarkerCheckService.ts @@ -0,0 +1,119 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; +import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +import { IMarkerService, MarkerSeverity } from '../../../../platform/markers/common/markers.js'; +import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js'; +import { ITextModelService } from '../../../../editor/common/services/resolverService.js'; +import { Range } from '../../../../editor/common/core/range.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { CodeActionContext, CodeActionTriggerType } from '../../../../editor/common/languages.js'; + +export interface IMarkerCheckService { + readonly _serviceBrand: undefined; +} + +export const IMarkerCheckService = createDecorator('markerCheckService'); + +class MarkerCheckService extends Disposable implements IMarkerCheckService { + _serviceBrand: undefined; + + constructor( + @IMarkerService private readonly _markerService: IMarkerService, + @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, + @ITextModelService private readonly _textModelService: ITextModelService, + ) { + super(); + + setInterval(async () => { + const allMarkers = this._markerService.read(); + const errors = allMarkers.filter(marker => marker.severity === MarkerSeverity.Error); + + if (errors.length > 0) { + for (const error of errors) { + + console.log(`----------------------------------------------`); + + console.log(`${error.resource.toString()}: ${error.startLineNumber} ${error.message} ${error.severity}`); // ! all errors in the file + + try { + // Get the text model for the file + const modelReference = await this._textModelService.createModelReference(error.resource); + const model = modelReference.object.textEditorModel; + + // Create a range from the marker + const range = new Range( + error.startLineNumber, + error.startColumn, + error.endLineNumber, + error.endColumn + ); + + // Get code action providers for this model + const codeActionProvider = this._languageFeaturesService.codeActionProvider; + const providers = codeActionProvider.ordered(model); + + if (providers.length > 0) { + // Request code actions from each provider + for (const provider of providers) { + const context: CodeActionContext = { + trigger: CodeActionTriggerType.Invoke, // keeping 'trigger' since it works + only: 'quickfix' // adding this to filter for quick fixes + }; + + const actions = await provider.provideCodeActions( + model, + range, + context, + CancellationToken.None + ); + + if (actions?.actions?.length) { + + const quickFixes = actions.actions.filter(action => action.isPreferred); // ! all quickFixes for the error + const quickFixesForImports = actions.actions.filter(action => action.isPreferred && action.title.includes('import')); // ! all possible imports + quickFixesForImports + + if (quickFixes.length > 0) { + console.log('Available Quick Fixes:'); + quickFixes.forEach(action => { + console.log(`- ${action.title}`); + }); + } + } + } + } + + // Dispose the model reference + modelReference.dispose(); + } catch (e) { + console.error('Error getting quick fixes:', e); + } + } + } + }, 5000); + } + + + // private _onMarkersChanged = (changedResources: readonly URI[]): void => { + // for (const resource of changedResources) { + // const markers = this._markerService.read({ resource }); + + // if (markers.length === 0) { + // console.log(`${resource.toString()}: No diagnostics`); + // continue; + // } + + // console.log(`Diagnostics for ${resource.toString()}:`); + // markers.forEach(marker => this._logMarker(marker)); + // } + // }; + + +} + +registerSingleton(IMarkerCheckService, MarkerCheckService, InstantiationType.Eager);