Compare commits

...

19 Commits

Author SHA1 Message Date
Andrew Pareles 4e392bee8e start adding icons for copying files and folders in the explorer 2025-01-06 22:25:41 -08:00
Andrew Pareles f96d320ce0 fix tricky reject bug 2025-01-06 02:02:48 -08:00
Andrew Pareles bb7e69b20b small fix for realigning 2025-01-06 01:43:54 -08:00
Andrew Pareles f19e579ce9 extract code works! ignore </MID> etc 2025-01-06 01:19:27 -08:00
Andrew Pareles dd133d2cd5 ctrlK roundRange 2025-01-05 21:27:16 -08:00
Andrew Pareles 2ba04a78a8 add X to ctrlK 2025-01-05 21:02:31 -08:00
Andrew Pareles fb948b331f ctrlK 2025-01-05 18:03:36 -08:00
Mathew Pareles 35091eb8f2 change wording 2025-01-05 17:11:10 -08:00
Mathew Pareles b9e61bf4e2 styles 2025-01-05 17:10:20 -08:00
Andrew Pareles 55ec3365a2 empty seln 2025-01-05 17:04:07 -08:00
Mathew Pareles d6811e872a fix color 2025-01-05 16:57:39 -08:00
Mathew Pareles c176b8020f styles 2025-01-05 16:35:43 -08:00
Andrew Pareles 6dcf684429 updateService changes 2025-01-03 17:11:18 -08:00
Mathew Pareles 26ddd6418c extract <MID> result 2025-01-03 16:54:04 -08:00
Mathew Pareles cffe558cf5 code editor bg 2025-01-03 01:35:36 -08:00
Mathew Pareles 9bc551f0ac code editor bg 2025-01-03 01:35:36 -08:00
Mathew Pareles 01d7878671 code editor bg 2025-01-03 01:31:47 -08:00
Mathew Pareles 2519f094cc input and dropdown styles 2025-01-03 00:00:03 -08:00
Andrew Pareles a72305819a copy 2025-01-02 19:15:08 -08:00
28 changed files with 463 additions and 214 deletions
@@ -15,6 +15,7 @@ import { IRequestService } from '../../request/common/request.js';
import { AvailableForDownload, DisablementReason, IUpdateService, State, StateType, UpdateType } from '../common/update.js';
export function createUpdateURL(platform: string, quality: string, productService: IProductService): string {
// return `https://voideditor.dev/api/update/${platform}/stable`;
return `${productService.updateUrl}/api/update/${platform}/${quality}/${productService.commit}`;
}
@@ -70,9 +71,11 @@ export abstract class AbstractUpdateService implements IUpdateService {
*/
protected async initialize(): Promise<void> {
if (!this.environmentMainService.isBuilt) {
console.log('is NOT built, canceling update service')
this.setState(State.Disabled(DisablementReason.NotBuilt));
return; // updates are never enabled when running out of sources
}
console.log('is built, continuing with update service')
if (this.environmentMainService.disableUpdates) {
this.setState(State.Disabled(DisablementReason.DisabledByEnvironment));
@@ -86,16 +89,19 @@ export abstract class AbstractUpdateService implements IUpdateService {
return;
}
const updateMode = this.configurationService.getValue<'none' | 'manual' | 'start' | 'default'>('update.mode');
const quality = this.getProductQuality(updateMode);
// Void - for now, always update
const updateMode = this.configurationService.getValue<'none' | 'manual' | 'start' | 'default'>('update.mode');
const quality = this.getProductQuality(updateMode);
if (!quality) {
this.setState(State.Disabled(DisablementReason.ManuallyDisabled));
this.logService.info('update#ctor - updates are disabled by user preference');
return;
}
this.url = this.buildUpdateFeedUrl(quality);
// const quality = 'stable'
this.url = this.doBuildUpdateFeedUrl(quality);
if (!this.url) {
this.setState(State.Disabled(DisablementReason.InvalidConfiguration));
this.logService.info('update#ctor - updates are disabled as the update URL is badly formed');
@@ -131,13 +137,10 @@ export abstract class AbstractUpdateService implements IUpdateService {
return updateMode === 'none' ? undefined : this.productService.quality;
}
private scheduleCheckForUpdates(delay = 60 * 60 * 1000): Promise<void> {
return timeout(delay)
.then(() => this.checkForUpdates(false))
.then(() => {
// Check again after 1 hour
return this.scheduleCheckForUpdates(60 * 60 * 1000);
});
private async scheduleCheckForUpdates(delay = 60 * 60 * 1000): Promise<void> {
await timeout(delay);
await this.checkForUpdates(false);
return await this.scheduleCheckForUpdates(60 * 60 * 1000);
}
async checkForUpdates(explicit: boolean): Promise<void> {
@@ -160,6 +163,7 @@ export abstract class AbstractUpdateService implements IUpdateService {
await this.doDownloadUpdate(this.state);
}
// override implemented by windows and linux
protected async doDownloadUpdate(state: AvailableForDownload): Promise<void> {
// noop
}
@@ -174,6 +178,7 @@ export abstract class AbstractUpdateService implements IUpdateService {
await this.doApplyUpdate();
}
// windows overrides this
protected async doApplyUpdate(): Promise<void> {
// noop
}
@@ -236,6 +241,6 @@ export abstract class AbstractUpdateService implements IUpdateService {
// noop
}
protected abstract buildUpdateFeedUrl(quality: string): string | undefined;
protected abstract doBuildUpdateFeedUrl(quality: string): string | undefined;
protected abstract doCheckForUpdates(context: any): void;
}
@@ -73,7 +73,7 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau
this.setState(State.Idle(UpdateType.Archive, message));
}
protected buildUpdateFeedUrl(quality: string): string | undefined {
protected doBuildUpdateFeedUrl(quality: string): string | undefined {
let assetID: string;
if (!this.productService.darwinUniversalAssetId) {
assetID = process.arch === 'x64' ? 'darwin' : 'darwin-arm64';
@@ -30,7 +30,7 @@ export class LinuxUpdateService extends AbstractUpdateService {
super(lifecycleMainService, configurationService, environmentMainService, requestService, logService, productService);
}
protected buildUpdateFeedUrl(quality: string): string {
protected doBuildUpdateFeedUrl(quality: string): string {
return createUpdateURL(`linux-${process.arch}`, quality, this.productService);
}
@@ -99,7 +99,7 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
await super.initialize();
}
protected buildUpdateFeedUrl(quality: string): string | undefined {
protected doBuildUpdateFeedUrl(quality: string): string | undefined {
let platform = `win32-${process.arch}`;
if (getUpdateType() === UpdateType.Archive) {
@@ -177,6 +177,7 @@ export type SettingName = keyof SettingsForProvider<ProviderName>
type DisplayInfoForProviderName = {
title: string,
desc?: string,
}
export const displayInfoOfProviderName = (providerName: ProviderName): DisplayInfoForProviderName => {
@@ -203,7 +204,7 @@ export const displayInfoOfProviderName = (providerName: ProviderName): DisplayIn
}
else if (providerName === 'openAICompatible') {
return {
title: 'Other',
title: 'OpenAI-Compatible',
}
}
else if (providerName === 'gemini') {
@@ -256,7 +257,7 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName
: providerName === 'openAICompatible' ? 'https://my-website.com/v1'
: '(never)',
subTextMd: providerName === 'ollama' ? 'Read about advanced [Endpoints here](https://github.com/ollama/ollama/blob/main/docs/faq.md#how-can-i-expose-ollama-on-my-network).' :
subTextMd: providerName === 'ollama' ? 'If you would like to change this endpoint, please read more about [Endpoints here](https://github.com/ollama/ollama/blob/main/docs/faq.md#how-can-i-expose-ollama-on-my-network).' :
undefined,
}
}
@@ -289,6 +289,7 @@ export interface IFileTemplateData {
readonly templateDisposables: DisposableStore;
readonly elementDisposables: DisposableStore;
readonly label: IResourceLabel;
readonly voidLabels: IResourceLabel;
readonly container: HTMLElement;
readonly contribs: IExplorerFileContribution[];
currentContext?: ExplorerItem;
@@ -347,15 +348,24 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
renderTemplate(container: HTMLElement): IFileTemplateData {
const templateDisposables = new DisposableStore();
// Create void buttons container
const voidButtonsContainer = DOM.append(container, DOM.$('div'));
voidButtonsContainer.style.position = 'absolute'
voidButtonsContainer.style.top = '0'
voidButtonsContainer.style.right = '0'
// const voidButtons = DOM.append(voidButtonsContainer, DOM.$('span'));
// voidButtons.textContent = 'voidbuttons'
// voidButtons.addEventListener('click', () => {
// console.log('ON CLICK', templateData.currentContext?.children)
// })
const voidLabels = this.labels.create(voidButtonsContainer, { supportHighlights: false, supportIcons: false, });
voidLabels.element.textContent = 'hi333'
const label = templateDisposables.add(this.labels.create(container, { supportHighlights: true }));
templateDisposables.add(label.onDidRender(() => {
try {
if (templateData.currentContext) {
this.updateWidth(templateData.currentContext);
}
} catch (e) {
// noop since the element might no longer be in the tree, no update of width necessary
}
try { if (templateData.currentContext) this.updateWidth(templateData.currentContext); }
catch (e) { /* noop since the element might no longer be in the tree, no update of width necessary*/ }
}));
const contribs = explorerFileContribRegistry.create(this.instantiationService, container, templateDisposables);
@@ -365,10 +375,12 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
contr.setResource(templateData.currentContext?.resource);
}));
const templateData: IFileTemplateData = { templateDisposables, elementDisposables: templateDisposables.add(new DisposableStore()), label, container, contribs };
const templateData: IFileTemplateData = { templateDisposables, elementDisposables: templateDisposables.add(new DisposableStore()), label, voidLabels, container, contribs };
return templateData;
}
// Void cares about this function, this is where elements in the tree are rendered
renderElement(node: ITreeNode<ExplorerItem, FuzzyScore>, index: number, templateData: IFileTemplateData): void {
const stat = node.element;
templateData.currentContext = stat;
@@ -382,8 +394,7 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
templateData.label.element.style.display = 'flex';
this.renderStat(stat, stat.name, undefined, node.filterData, templateData);
}
// Input Box
// Input Box (Void - shown only if currently editing - this is the box that appears when user edits the name of the file)
else {
templateData.label.element.style.display = 'none';
templateData.contribs.forEach(c => c.setResource(undefined));
@@ -477,6 +488,13 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
separator: this.labelService.getSeparator(stat.resource.scheme, stat.resource.authority),
domId
});
templateData.voidLabels.setResource({ resource: undefined, name: 'hi', }, {
hideIcon: true,
extraClasses: realignNestedChildren ? [...extraClasses, 'align-nest-icon-with-parent-icon'] : extraClasses,
forceLabel: true,
});
}
private renderInputBox(container: HTMLElement, stat: ExplorerItem, editableData: IEditableData): IDisposable {
@@ -95,7 +95,12 @@ suite('Files - ExplorerView', () => {
label: <any>{
container: label,
onDidRender: emitter.event
}
},
voidLabels: <any>{
container: label,
onDidRender: emitter.event
},
}, 1, false);
ds.add(navigationController);
@@ -17,7 +17,7 @@ import { IEditorService } from '../../../services/editor/common/editorService.js
import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js';
import { EditorResourceAccessor } from '../../../common/editor.js';
import { IModelService } from '../../../../editor/common/services/model.js';
import { extractCodeFromResult } from './helpers/extractCodeFromResult.js';
import { extractCodeFromRegular } from './helpers/extractCodeFromResult.js';
// The extension this was called from is here - https://github.com/voideditor/void/blob/autocomplete/extensions/void/src/extension/extension.ts
@@ -652,7 +652,7 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
// newAutocompletion.abortRef = { current: () => { } }
newAutocompletion.status = 'finished'
// newAutocompletion.promise = undefined
newAutocompletion.insertText = postprocessResult(extractCodeFromResult(fullText))
newAutocompletion.insertText = postprocessResult(extractCodeFromRegular(fullText))
resolve(newAutocompletion.insertText)
@@ -3,10 +3,66 @@
* Void Editor additions licensed under the AGPL 3.0 License.
*--------------------------------------------------------------------------------------------*/
export const extractCodeFromResult = (result: string) => {
// modelWasTrainedOnFIM should be false here
export const extractCodeFromFIM = ({ text, midTag, modelWasTrainedOnFIM }: { text: string, midTag: string, modelWasTrainedOnFIM: false }) => {
/* desired matches
`
``
```
<
<P
<PR
<PRE
<PRE>
<PRE> a
<PRE> a </PRE>
<PRE> a </PRE><
<PRE> a </PRE><M
<PRE> a </PRE><MI
<PRE> a </PRE><MID
<PRE> a </PRE><MID>
<PRE> a <PRE/> ->
*/
/* ------------- summary of the regex -------------
[optional ` | `` | ```]
(match optional_language_name)
[optional strings here]
[required <MID> tag]
(match the stuff between mid tags)
[optional <MID/> tag]
[optional ` | `` | ```]
*/
// const regex = /[\s\S]*?(?:`{1,3}\s*([a-zA-Z_]+[\w]*)?[\s\S]*?)?<MID>([\s\S]*?)(?:<\/MID>|`{1,3}|$)/;
const regex = new RegExp(
`[\\s\\S]*?(?:\`{1,3}\\s*([a-zA-Z_]+[\\w]*)?[\\s\\S]*?)?<${midTag}>([\\s\\S]*?)(?:</${midTag}>|\`{1,3}|$)`,
''
);
const match = text.match(regex);
if (match) {
const [_, languageName, codeBetweenMidTags] = match;
return [languageName, codeBetweenMidTags] as const
} else {
return [undefined, extractCodeFromRegular(text)] as const
}
}
export const extractCodeFromRegular = (result: string) => {
// Match either:
// 1. ```language\n<code>```
// 2. ```<code>```
// 4 <PRE> A
// 3. <PRE> A </PRE><MID> B </MID> -> B
const match = result.match(/```(?:\w+\n)?([\s\S]*?)```|```([\s\S]*?)```/);
if (!match) {
@@ -27,15 +27,16 @@ 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 { ctrlKStream_prefixAndSuffix, ctrlKStream_prompt, ctrlKStream_systemMessage, ctrlLStream_prompt, ctrlLStream_systemMessage } from './prompt/prompts.js';
import { ctrlKStream_prefixAndSuffix, ctrlKStream_prompt, ctrlKStream_systemMessage, ctrlLStream_prompt, ctrlLStream_systemMessage, defaultFimTags } from './prompt/prompts.js';
import { ILLMMessageService } from '../../../../platform/void/common/llmMessageService.js';
import { IPosition } from '../../../../editor/common/core/position.js';
import { mountCtrlK } from '../browser/react/out/ctrl-k-tsx/index.js'
import { mountCtrlK } from '../browser/react/out/quick-edit-tsx/index.js'
import { QuickEditPropsType } from './quickEditActions.js';
import { InputBox } from '../../../../base/browser/ui/inputbox/inputBox.js';
import { LLMMessage } from '../../../../platform/void/common/llmMessageTypes.js';
import { IModelContentChangedEvent } from '../../../../editor/common/textModelEvents.js';
import { extractCodeFromFIM, extractCodeFromRegular } from './helpers/extractCodeFromResult.js';
const configOfBG = (color: Color) => {
return { dark: color, light: color, hcDark: color, hcLight: color, }
@@ -822,7 +823,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
const lastDiff = computedDiffs.pop()
if (!lastDiff) {
console.log('!lastDiff')
// console.log('!lastDiff')
// if the writing is identical so far, display no changes
originalCodeStartLine = 1
newCodeEndLine = 1
@@ -842,7 +843,8 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
const newCodeTop = llmText.split('\n').slice(0, (newCodeEndLine - 1) + 1).join('\n')
const oldFileBottom = diffZone.originalCode.split('\n').slice((originalCodeStartLine - 1) + 1, Infinity).join('\n')
const newCode = `${newCodeTop}\n${oldFileBottom}`
// oriignalCode[1 + line...Infinity]. Must make sure 1 + line < originalCode.length. This is another way to check:
const newCode = (newCodeTop && oldFileBottom) ? `${newCodeTop}\n${oldFileBottom}` : (oldFileBottom || newCodeTop)
this._writeText(uri, newCode,
{ startLineNumber: diffZone.startLine, startColumn: 1, endLineNumber: diffZone.endLine, endColumn: Number.MAX_SAFE_INTEGER, }, // 1-indexed
@@ -1044,6 +1046,10 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
// this._deleteDiffArea(ctrlKZone)
// }
// TODO ctrl+K case should be replaced with an actual check for model.isFIM
const modelWasTrainedOnFIM = featureName === 'Ctrl+K' ? false : false
const modelFimTags = defaultFimTags
const adding: Omit<DiffZone, 'diffareaid'> = {
type: 'DiffZone',
originalCode,
@@ -1072,7 +1078,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
}
else if (featureName === 'Ctrl+K') {
const { prefix, suffix } = ctrlKStream_prefixAndSuffix({ fullFileStr: currentFileStr, startLine, endLine })
const userContent = ctrlKStream_prompt({ selection: originalCode, userMessage, prefix, suffix })
const userContent = ctrlKStream_prompt({ selection: originalCode, userMessage, prefix, suffix, modelWasTrainedOnFIM, fimTags: modelFimTags })
console.log('PREFIX:\n', prefix)
console.log('SUFFIX:\n', suffix)
console.log('USER CONTENT:\n', userContent)
@@ -1103,17 +1109,30 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
// refresh now in case onText takes a while to get 1st message
this._refreshStylesAndDiffsInURI(uri)
const extractText = (fullText: string) => {
if (featureName === 'Ctrl+K') {
const [_, textSoFar] = extractCodeFromFIM({ text: fullText, midTag: modelFimTags.midTag, modelWasTrainedOnFIM })
return textSoFar
}
else if (featureName === 'Ctrl+L') {
return extractCodeFromRegular(fullText)
}
throw 1
}
streamRequestIdRef.current = this._llmMessageService.sendLLMMessage({
featureName,
logging: { loggingName: `startApplying - ${featureName}` },
messages,
onText: ({ newText, fullText }) => {
this._writeDiffZoneLLMText(diffZone, fullText, latestCurrentFileEnd, latestOriginalFileStart)
this._writeDiffZoneLLMText(diffZone, extractText(fullText), latestCurrentFileEnd, latestOriginalFileStart)
this._refreshStylesAndDiffsInURI(uri)
},
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
this._writeText(uri, fullText,
this._writeText(uri, extractText(fullText),
{ startLineNumber: diffZone.startLine, startColumn: 1, endLineNumber: diffZone.endLine, endColumn: Number.MAX_SAFE_INTEGER }, // 1-indexed
{ shouldRealignDiffAreas: false }
)
@@ -1279,8 +1298,18 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
// B| <-- endLine (we want to delete this whole line)
// C
else if (diff.type === 'insertion') {
writeText = ''
toRange = { startLineNumber: diff.startLine, startColumn: 1, endLineNumber: diff.endLine + 1, endColumn: 1 } // 1-indexed
// console.log('REJECTING:', diff)
// handle the case where the insertion was a newline at end of diffarea (applying to the next line doesnt work because it doesnt exist, vscode just doesnt delete the correct # of newlines)
if (diff.endLine === diffArea.endLine) {
// delete the line before instead of after
writeText = ''
toRange = { startLineNumber: diff.startLine - 1, startColumn: Number.MAX_SAFE_INTEGER, endLineNumber: diff.endLine, endColumn: 1 } // 1-indexed
}
else {
writeText = ''
toRange = { startLineNumber: diff.startLine, startColumn: 1, endLineNumber: diff.endLine + 1, endColumn: 1 } // 1-indexed
}
}
// if it was an edit, just edit the range
// (this image applies to writeText and toRange, not newOriginalCode)
@@ -324,13 +324,25 @@ export const ctrlKStream_prefixAndSuffix = ({ fullFileStr, startLine, endLine }:
}
export const ctrlKStream_prompt = ({ selection, prefix, suffix, userMessage }: { selection: string, prefix: string, suffix: string, userMessage: string, }) => {
const onlySpeaksFIM = false
if (onlySpeaksFIM) {
const preTag = 'PRE'
const sufTag = 'SUF'
const midTag = 'MID'
export type FimTagsType = {
preTag: string,
sufTag: string,
midTag: string
}
export const defaultFimTags: FimTagsType = {
preTag: 'BEFORE',
sufTag: 'AFTER',
midTag: 'SELECTION',
}
export const ctrlKStream_prompt = ({ selection, prefix, suffix, userMessage, modelWasTrainedOnFIM, fimTags }: { selection: string, prefix: string, suffix: string, userMessage: string, modelWasTrainedOnFIM: boolean, fimTags: FimTagsType }) => {
const { preTag, sufTag, midTag } = fimTags
if (modelWasTrainedOnFIM) {
// const preTag = 'PRE'
// const sufTag = 'SUF'
// const midTag = 'MID'
return `\
<${preTag}>
/* Original Selection:
@@ -341,32 +353,34 @@ ${prefix}</${preTag}>
<${sufTag}>${suffix}</${sufTag}>
<${midTag}>`
}
// prompt the model on how to do FIM
// prompt the model artifically on how to do FIM
else {
const preTag = 'PRE'
const sufTag = 'SUF'
const midTag = 'MID'
// const preTag = 'BEFORE'
// const sufTag = 'AFTER'
// const midTag = 'SELECTION'
return `\
Here is the user's original selection:
The user is selecting this code as their SELECTION:
\`\`\`
<${midTag}>${selection}</${midTag}>
\`\`\`
The user wants to apply the following instructions to the selection:
The user wants to apply the following INSTRUCTIONS to the SELECTION:
${userMessage}
Please rewrite the selection following the user's instructions.
Please edit the SELECTION following the user's INSTRUCTIONS, and return the new SELECTION.
Instructions to follow:
1. Follow the user's instructions
2. You may ONLY CHANGE the selection, and nothing else in the file
3. Make sure all brackets in the new selection are balanced the same was as in the original selection
3. Be careful not to duplicate or remove variables, comments, or other syntax by mistake
Note that the SELECTION has code that comes before it. This code is indicated with <${preTag}>...before<${preTag}/>.
Note also that the SELECTION has code that comes after it. This code is indicated with <${sufTag}>...after<${sufTag}/>.
Instructions:
1. Your OUTPUT should be a SINGLE PIECE OF CODE of the form <${midTag}>...new_selection<${midTag}/>. Do not give any explanation before or after this. ONLY output this format, nothing more.
2. You may ONLY CHANGE the original SELECTION, and NOT the content in the <${preTag}>...<${preTag}/> or <${sufTag}>...<${sufTag}/> tags.
3. Make sure all brackets in the new selection are balanced the same as in the original selection.
4. Be careful not to duplicate or remove variables, comments, or other syntax by mistake.
Complete the following:
<${preTag}>${prefix}</${preTag}>
<${sufTag}>${suffix}</${sufTag}>
<${midTag}>`
<${sufTag}>${suffix}</${sufTag}>`
}
};
@@ -11,6 +11,7 @@ import { IMetricsService } from '../../../../platform/void/common/metricsService
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
import { IInlineDiffsService } from './inlineDiffsService.js';
import { InputBox } from '../../../../base/browser/ui/inputbox/inputBox.js';
import { roundRangeToLines } from './sidebarActions.js';
export type QuickEditPropsType = {
@@ -54,7 +55,7 @@ registerAction2(class extends Action2 {
if (!editor) return;
const model = editor.getModel()
if (!model) return;
const selection = editor.getSelection()
const selection = roundRangeToLines(editor.getSelection(), { emptySelectionBehavior: 'line' })
if (!selection) return;
@@ -69,7 +69,7 @@ export const BlockCode = ({ text, buttonsOnHover, language }: { text: string, bu
const isSingleLine = !text.includes('\n')
return (<>
<div className={`relative group w-full bg-vscode-editor-bg overflow-hidden isolate`}>
<div className={`relative group w-full overflow-hidden`}>
{buttonsOnHover === null ? null : (
<div className="z-[1] absolute top-0 right-0 opacity-0 group-hover:opacity-100 duration-200">
<div className={`flex space-x-2 ${isSingleLine ? '' : 'p-2'}`}>{buttonsOnHover}</div>
@@ -49,14 +49,14 @@ const CodeButtonsOnHover = ({ text }: { text: string }) => {
return <>
<button
className={`${isSingleLine ? '' : 'p-1'} text-xs hover:brightness-110 bg-vscode-input-bg border border-vscode-input-border rounded text-xs text-vscode-input-fg`}
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-1 text-void-fg-1 hover:brightness-110 border border-vscode-input-border rounded`}
onClick={onCopy}
>
{copyButtonState}
</button>
<button
// btn btn-secondary btn-sm border text-xs text-vscode-input-fg border-vscode-input-border rounded
className={`${isSingleLine ? '' : 'p-1'} text-xs hover:brightness-110 bg-vscode-input-bg border border-vscode-input-border rounded text-xs text-vscode-input-fg`}
// btn btn-secondary btn-sm border text-sm border-vscode-input-border rounded
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-1 text-void-fg-1 hover:brightness-110 border border-vscode-input-border rounded`}
onClick={onApply}
>
Apply
@@ -215,7 +215,7 @@ const RenderToken = ({ token, nested = false }: { token: Token | string, nested?
// default
return (
<div className="bg-orange-50 rounded-sm overflow-hidden">
<span className="text-xs text-orange-500">Unknown type:</span>
<span className="text-sm text-orange-500">Unknown type:</span>
{t.raw}
</div>
)
@@ -6,16 +6,16 @@
import { useEffect, useState } from 'react'
import { useIsDark, useSidebarState } from '../util/services.js'
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
import { CtrlKChat } from './CtrlKChat.js'
import { QuickEditChat } from './QuickEditChat.js'
import { QuickEditPropsType } from '../../../quickEditActions.js'
export const CtrlK = (props: QuickEditPropsType) => {
export const QuickEdit = (props: QuickEditPropsType) => {
const isDark = useIsDark()
return <div className={`@@void-scope ${isDark ? 'dark' : ''}`}>
<ErrorBoundary>
<CtrlKChat {...props} />
<QuickEditChat {...props} />
</ErrorBoundary>
</div>
@@ -14,13 +14,14 @@ import { ButtonStop, ButtonSubmit } from '../sidebar-tsx/SidebarChat.js';
import { ModelDropdown } from '../void-settings-tsx/ModelDropdown.js';
import { X } from 'lucide-react';
export const CtrlKChat = ({ diffareaid, onGetInputBox, onUserUpdateText, onChangeHeight, initText }: QuickEditPropsType) => {
export const QuickEditChat = ({ diffareaid, onGetInputBox, onUserUpdateText, onChangeHeight, initText }: QuickEditPropsType) => {
const accessor = useAccessor()
const inlineDiffsService = accessor.get('IInlineDiffsService')
const sizerRef = useRef<HTMLDivElement | null>(null)
const inputBoxRef: React.MutableRefObject<InputBox | null> = useRef(null);
useEffect(() => {
const inputContainer = sizerRef.current
if (!inputContainer) return;
@@ -67,6 +68,10 @@ export const CtrlKChat = ({ diffareaid, onGetInputBox, onUserUpdateText, onChang
}, [inlineDiffsService])
const onX = useCallback(() => {
inlineDiffsService.removeCtrlKZone({ diffareaid })
}, [inlineDiffsService, diffareaid])
// sync init value
const alreadySetRef = useRef(false)
useEffect(() => {
@@ -115,12 +120,7 @@ export const CtrlKChat = ({ diffareaid, onGetInputBox, onUserUpdateText, onChang
@@[&_div.monaco-inputbox]:!void-border-none
@@[&_div.monaco-inputbox]:!void-outline-none`}
>
<div className='flex flex-row justify-between items-end gap-1'>
<div className='absolute size-0.5 top-0 right-4 z-[1]'>
<X
onClick={() => { inlineDiffsService.removeCtrlKZone({ diffareaid }) }}
/>
</div>
<div className='flex flex-row items-center justify-between items-end gap-1'>
{/* input */}
<div // copied from SidebarChat.tsx
@@ -132,6 +132,12 @@ export const CtrlKChat = ({ diffareaid, onGetInputBox, onUserUpdateText, onChang
onChangeText={onChangeText}
onCreateInstance={useCallback((instance: InputBox) => {
inputBoxRef.current = instance;
// if presses the esc key, X
instance.element.addEventListener('keydown', (e) => {
if (e.key === 'Escape')
onX()
})
onGetInputBox(instance);
instance.focus()
}, [onGetInputBox])}
@@ -139,6 +145,16 @@ export const CtrlKChat = ({ diffareaid, onGetInputBox, onUserUpdateText, onChang
/>
</div>
<div className='absolute pt-1 -top-1 -right-1'>
<span className='cursor-pointer rounded-md z-1'>
<X
className='size-4 text-vscode-toolbar-foreground'
onClick={onX}
/>
</span>
</div>
</div>
@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import { mountFnGenerator } from '../util/mountFnGenerator.js'
import { CtrlK } from './CtrlK.js'
import { QuickEdit } from './QuickEdit.js'
export const mountCtrlK = mountFnGenerator(CtrlK)
export const mountCtrlK = mountFnGenerator(QuickEdit)
@@ -36,38 +36,38 @@ export const ErrorDisplay = ({
return (
<div className={`rounded-lg border border-red-200 bg-red-50 p-4 overflow-auto`}>
{/* Header */}
<div className="flex items-start justify-between">
<div className="flex gap-3">
<AlertCircle className="h-5 w-5 text-red-600 mt-0.5" />
<div className="flex-1">
<h3 className="font-semibold text-red-800">
<div className='flex items-start justify-between'>
<div className='flex gap-3'>
<AlertCircle className='h-5 w-5 text-red-600 mt-0.5' />
<div className='flex-1'>
<h3 className='font-semibold text-red-800'>
{/* eg Error */}
Error
</h3>
<p className="text-red-700 mt-1">
<p className='text-red-700 mt-1'>
{/* eg Something went wrong */}
{message}
</p>
</div>
</div>
<div className="flex gap-2">
<div className='flex gap-2'>
{details && (
<button className="text-red-600 hover:text-red-800 p-1 rounded"
<button className='text-red-600 hover:text-red-800 p-1 rounded'
onClick={() => setIsExpanded(!isExpanded)}
>
{isExpanded ? (
<ChevronUp className="h-5 w-5" />
<ChevronUp className='h-5 w-5' />
) : (
<ChevronDown className="h-5 w-5" />
<ChevronDown className='h-5 w-5' />
)}
</button>
)}
{showDismiss && onDismiss && (
<button className="text-red-600 hover:text-red-800 p-1 rounded"
<button className='text-red-600 hover:text-red-800 p-1 rounded'
onClick={onDismiss}
>
<X className="h-5 w-5" />
<X className='h-5 w-5' />
</button>
)}
</div>
@@ -75,10 +75,10 @@ export const ErrorDisplay = ({
{/* Expandable Details */}
{isExpanded && details && (
<div className="mt-4 space-y-3 border-t border-red-200 pt-3 overflow-auto">
<div className='mt-4 space-y-3 border-t border-red-200 pt-3 overflow-auto'>
<div>
<span className="font-semibold text-red-800">Full Error: </span>
<pre className="text-red-700">{details}</pre>
<span className='font-semibold text-red-800'>Full Error: </span>
<pre className='text-red-700'>{details}</pre>
</div>
</div>
)}
@@ -24,7 +24,14 @@ export const Sidebar = ({ className }: { className: string }) => {
const isDark = useIsDark()
// ${isDark ? 'dark' : ''}
return <div className={`@@void-scope`} style={{ width: '100%', height: '100%' }}>
<div className={`w-full h-full flex flex-col py-2 bg-vscode-sidebar-bg`}>
<div
// default background + text styles for sidebar
className={`
w-full h-full py-2
bg-void-bg-2
text-void-fg-1
`}
>
{/* <span onClick={() => {
const tabs = ['chat', 'settings', 'threadSelector']
@@ -32,7 +39,7 @@ export const Sidebar = ({ className }: { className: string }) => {
sidebarStateService.setState({ currentTab: tabs[(index + 1) % tabs.length] as any })
}}>clickme {tab}</span> */}
<div className={`w-full h-auto mb-2 ${isHistoryOpen ? '' : 'hidden'} ring-2 ring-widget-shadow z-10`}>
<div className={`w-full h-auto mb-2 ${isHistoryOpen ? '' : 'hidden'} ring-inset ring-2 ring-widget-shadow z-10`}>
<ErrorBoundary>
<SidebarThreadSelector />
</ErrorBoundary>
@@ -272,7 +272,7 @@ export const SelectedFiles = (
return (
!!selections && selections.length !== 0 && (
<div
className='flex flex-wrap gap-2 text-left'
className='flex flex-wrap gap-0.5 text-left'
>
{selections.map((selection, i) => {
@@ -285,13 +285,13 @@ export const SelectedFiles = (
{/* selection summary */}
<div
// className="relative rounded rounded-e-2xl flex items-center space-x-2 mx-1 mb-1 disabled:cursor-default"
className={`flex items-center gap-1 relative
rounded-md p-1
className={`flex items-center gap-0.5 relative
rounded-md px-1
w-fit h-fit
select-none
bg-vscode-editor-bg hover:brightness-95
bg-void-bg-3 hover:brightness-95
text-void-fg-1 text-xs text-nowrap
border border-vscode-commandcenter-border rounded-xs
text-xs text-vscode-editor-fg text-nowrap
`}
onClick={() => {
setSelectionIsOpened(s => {
@@ -311,12 +311,7 @@ export const SelectedFiles = (
{/* X button */}
{type === 'staging' &&
<span
className='
cursor-pointer
bg-vscode-editorwidget-bg hover:bg-vscode-toolbar-hover-bg
rounded-md
z-1
'
className='cursor-pointer hover:bg-vscode-toolbar-hover-bg rounded-md z-1'
onClick={(e) => {
e.stopPropagation();
if (type !== 'staging') return;
@@ -347,7 +342,7 @@ export const SelectedFiles = (
</div>
{/* selection text */}
{isThisSelectionOpened &&
<div className='w-full p-1 rounded-sm border-vscode-editor-border'>
<div className='w-full px-1 rounded-sm border-vscode-editor-border'>
<BlockCode text={selection.selectionStr!} language={getLanguageFromFileName(selection.fileURI.path)} />
</div>
}
@@ -636,13 +631,13 @@ export const SidebarChat = () => {
// .split(' ')
// .map(style => `@@[&_div.monaco-inputbox]:!void-${style}`)
// .join(' ');
`@@[&_textarea]:!void-bg-transparent
`
@@[&_textarea]:!void-outline-none
@@[&_textarea]:!void-text-vscode-input-fg
@@[&_textarea]:!void-min-h-[81px]
@@[&_textarea]:!void-max-h-[500px]
@@[&_div.monaco-inputbox]:!void-border-none
@@[&_div.monaco-inputbox]:!void-outline-none`
@@[&_div.monaco-inputbox]:!void-outline-none
`
}
>
@@ -53,7 +53,7 @@ export const SidebarThreadSelector = () => {
</div>
{/* a list of all the past threads */}
<div className='flex flex-col gap-y-1 overflow-y-auto'>
<div className='px-1'><div className='flex flex-col gap-y-1 overflow-y-auto'>
{sortedThreadIds.map((threadId) => {
if (!allThreads)
return <>Error: Threads not found.</>
@@ -88,7 +88,7 @@ export const SidebarThreadSelector = () => {
</button>
)
})}
</div>
</div></div>
</div>
)
@@ -20,6 +20,16 @@
.inherit-bg-all-restyle > * {
background-color: inherit !important;
}
.bg-editor-style-override {
--vscode-sideBar-background: var(--vscode-editor-background);
}
/* html {
font-size: var(--vscode-font-size);
@@ -60,6 +60,10 @@ export const VoidInputBox = ({ onChangeText, onCreateInstance, inputBoxRef, plac
const contextViewProvider = accessor.get('IContextViewService')
return <WidgetComponent
ctor={InputBox}
className='
bg-void-bg-1
@@[&_::placeholder]:!void-text-void-fg-3
'
propsFn={useCallback((container) => [
container,
contextViewProvider,
@@ -193,11 +197,12 @@ export const VoidCheckBox = ({ label, value, onClick, className }: { label: stri
}
export const VoidSelectBox = <T,>({ onChangeSelection, onCreateInstance, selectBoxRef, options }: {
export const VoidSelectBox = <T,>({ onChangeSelection, onCreateInstance, selectBoxRef, options, className }: {
onChangeSelection: (value: T) => void;
onCreateInstance?: ((instance: SelectBox) => void | IDisposable[]);
selectBoxRef?: React.MutableRefObject<SelectBox | null>;
options: readonly { text: string, value: T }[];
className?:string;
}) => {
const accessor = useAccessor()
const contextViewProvider = accessor.get('IContextViewService')
@@ -205,7 +210,9 @@ export const VoidSelectBox = <T,>({ onChangeSelection, onCreateInstance, selectB
let containerRef = useRef<HTMLDivElement | null>(null);
return <WidgetComponent
className='@@select-child-restyle'
className={`@@select-child-restyle
@@[&_select]:!void-text-void-fg-3
${className ?? ''}`}
ctor={SelectBox}
propsFn={useCallback((container) => {
containerRef.current = container
@@ -301,9 +308,9 @@ export const VoidCodeEditor = ({ initValue, language }: { initValue: string, lan
return <div ref={divRef}>
<WidgetComponent
className='relative z-0 text-sm bg-vscode-editor-bg'
ctor={useCallback((container) =>
instantiationService.createInstance(
className='relative z-0 @@bg-editor-style-override' // text-sm
ctor={useCallback((container) => {
return instantiationService.createInstance(
CodeEditorWidget,
container,
{
@@ -347,7 +354,7 @@ export const VoidCodeEditor = ({ initValue, language }: { initValue: string, lan
{
isSimpleWidget: true,
})
, [instantiationService])
}, [instantiationService])
}
onCreateInstance={useCallback((editor: CodeEditorWidget) => {
@@ -32,6 +32,7 @@ const ModelSelectBox = ({ options, featureName }: { options: ModelOption[], feat
let weChangedText = false
return <VoidSelectBox
className='@@[&_select]:!void-text-xs'
options={options}
onChangeSelection={useCallback((newVal: ModelSelection) => {
if (weChangedText) return
@@ -84,17 +85,20 @@ const DummySelectBox = () => {
className={`
flex items-center
flex-nowrap text-ellipsis
text-vscode-charts-yellow
hover:brightness-90 transition-all duration-200
text-void-warning brightness-90 opacity-90
hover:brightness-75 transition-all duration-200
cursor-pointer
text-xs
`}
onClick={openSettings}
>
<IconWarning
size={20}
size={14}
className='mr-1 brightness-90'
/>
<span>Model required</span>
<span>Provider required</span>
</div>
// return <VoidSelectBox
// options={[{ text: 'Please add a model!', value: null }]}
@@ -9,16 +9,16 @@ import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, Voi
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
import { VoidCheckBox, VoidInputBox, VoidSelectBox, VoidSwitch } from '../util/inputs.js'
import { useAccessor, useIsDark, useRefreshModelListener, useRefreshModelState, useSettingsState } from '../util/services.js'
import { X, RefreshCw, Loader2, Check } from 'lucide-react'
import { X, RefreshCw, Loader2, Check, MoveRight } from 'lucide-react'
import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js'
const SubtleButton = ({ onClick, text, icon, disabled }: { onClick: () => void, text: string, icon: React.ReactNode, disabled: boolean }) => {
return <div className='flex items-center px-3 rounded-sm overflow-hidden gap-2 hover:bg-black/10 dark:hover:bg-gray-300/10'>
return <div className='flex items-center text-void-fg-3 mb-1 px-3 rounded-sm overflow-hidden gap-2 hover:bg-black/10 dark:hover:bg-gray-300/10'>
<button className='flex items-center' disabled={disabled} onClick={onClick}>
{icon}
</button>
<span className='opacity-50'>
<span>
{text}
</span>
</div>
@@ -195,16 +195,19 @@ export const ModelDump = () => {
const disabled = !providerEnabled
return <div key={`${modelName}${providerName}`} className={`flex items-center justify-between gap-4 hover:bg-black/10 dark:hover:bg-gray-300/10 py-1 px-3 rounded-sm overflow-hidden cursor-default truncate ${isNewProviderName ? 'mt-4' : ''}`}>
return <div key={`${modelName}${providerName}`}
className={`flex items-center justify-between gap-4 hover:bg-black/10 dark:hover:bg-gray-300/10 py-1 px-3 rounded-sm overflow-hidden cursor-default truncate
`}
>
{/* left part is width:full */}
<div className={`w-full flex items-center gap-4`}>
<span className='min-w-40'>{isNewProviderName ? displayInfoOfProviderName(providerName).title : ''}</span>
<span className='min-w-20'>{isNewProviderName ? displayInfoOfProviderName(providerName).title : ''}</span>
<span>{modelName}</span>
{/* <span>{`${modelName} (${providerName})`}</span> */}
</div>
{/* right part is anything that fits */}
<div className='w-fit flex items-center gap-4'>
<span className='opacity-50'>{isAutodetected ? '(detected locally)' : isDefault ? '' : '(custom model)'}</span>
<span className='opacity-50'>{isAutodetected ? '(detected locally)' : isDefault ? '' : '(custom model)'}</span>
<VoidSwitch
value={disabled ? false : !isHidden}
@@ -241,8 +244,8 @@ const ProviderSetting = ({ providerName, settingName }: { providerName: Provider
return <ErrorBoundary>
<div className='my-1'>
<VoidInputBox
// placeholder={`${providerTitle} ${settingTitle} (${placeholder}).`}
placeholder={`${settingTitle} (${placeholder}).`}
// placeholder={`${providerTitle} ${settingTitle} (${placeholder})`}
placeholder={`${settingTitle} (${placeholder})`}
onChangeText={useCallback((newVal) => {
if (weChangedTextRef) return
voidSettingsService.setSettingOfProvider(providerName, settingName, newVal)
@@ -281,7 +284,7 @@ const ProviderSetting = ({ providerName, settingName }: { providerName: Provider
}, [voidSettingsService, providerName, settingName])}
multiline={false}
/>
{subTextMd === undefined ? null : <div className='py-1 px-3 opacity-50 text-xs'>
{subTextMd === undefined ? null : <div className='py-1 px-3 opacity-50 text-sm'>
<ChatMarkdownRender string={subTextMd} />
</div>}
@@ -357,6 +360,7 @@ export const VoidProviderSettings = ({ providerNames }: { providerNames: Provide
// })}
// </>
// }
type TabName = 'models' | 'general'
export const VoidFeatureFlagSettings = () => {
const accessor = useAccessor()
@@ -380,12 +384,85 @@ export const VoidFeatureFlagSettings = () => {
}
export const FeaturesTab = () => {
return <>
<h2 className={`text-3xl mb-2`}>Local Providers</h2>
{/* <h3 className={`opacity-50 mb-2`}>{`Keep your data private by hosting AI locally on your computer.`}</h3> */}
{/* <h3 className={`opacity-50 mb-2`}>{`Instructions:`}</h3> */}
{/* <h3 className={`mb-2`}>{`Void can access any model that you host locally. We automatically detect your local models by default.`}</h3> */}
<h3 className={`text-void-fg-3 mb-2`}>{`Void can access any model that you host locally. We automatically detect your local models by default.`}</h3>
<div className='pl-4 select-text opacity-50'>
<span className={`text-sm mb-2`}><ChatMarkdownRender string={`1. Download [Ollama](https://ollama.com/download).`} /></span>
<span className={`text-sm mb-2`}><ChatMarkdownRender string={`2. Open your terminal.`} /></span>
<span className={`text-sm mb-2`}><ChatMarkdownRender string={`3. Run \`ollama run llama3.1\`. This installs Meta's llama3.1 model which is best for chat and inline edits. Requires 5GB of memory.`} /></span>
<span className={`text-sm mb-2`}><ChatMarkdownRender string={`4. Run \`ollama run qwen2.5-coder:1.5b\`. This installs a faster autocomplete model. Requires 1GB of memory.`} /></span>
<span className={`text-sm mb-2`}><ChatMarkdownRender string={`Void automatically detects locally running models and enables them.`} /></span>
{/* TODO we should create UI for downloading models without user going into terminal */}
</div>
<ErrorBoundary>
<VoidProviderSettings providerNames={localProviderNames} />
</ErrorBoundary>
<h2 className={`text-3xl mb-2 mt-16`}>Providers</h2>
<h3 className={`text-void-fg-3 mb-2`}>{`Void can access models from Anthropic, OpenAI, OpenRouter, and more.`}</h3>
{/* <h3 className={`opacity-50 mb-2`}>{`Access models like ChatGPT and Claude. We recommend using Anthropic or OpenAI as providers, or Groq as a faster alternative.`}</h3> */}
<ErrorBoundary>
<VoidProviderSettings providerNames={nonlocalProviderNames} />
</ErrorBoundary>
<h2 className={`text-3xl mb-2 mt-16`}>Models</h2>
<ErrorBoundary>
<VoidFeatureFlagSettings />
<RefreshableModels />
<ModelDump />
<AddModelMenuFull />
</ErrorBoundary>
</>
}
const OneClickSwitch = () => {
}
const GeneralTab = () => {
return <>
{/* <VoidFeatureFlagSettings /> */}
{/* keyboard shortcuts */}
<h2 className={`text-3xl mb-2`}>General Settings</h2>
<h3 className={`text-void-fg-3 mb-2`}>{`VS Code's built-in settings.`}</h3>
<h2 className={`text-3xl mb-2`}>Keyboard Settings</h2>
<h3 className={`text-void-fg-3 mb-2`}>{`Void can access models from Anthropic, OpenAI, OpenRouter, and more.`}</h3>
<h2 className={`text-3xl mb-2`}>One-click Switch</h2>
Transfer your VS Code settings to Void.
<h2 className={`text-3xl mb-2`}>Theme</h2>
<h2 className={`text-3xl mb-2`}>Rules for AI</h2>
</>
}
// full settings
export const Settings = () => {
const isDark = useIsDark()
const [tab, setTab] = useState<'models' | 'features'>('models')
const [tab, setTab] = useState<TabName>('models')
return <div className={`@@void-scope ${isDark ? 'dark' : ''}`}>
<div className='w-full h-full px-10 py-10 select-none'>
@@ -404,9 +481,9 @@ export const Settings = () => {
<button className={`text-left p-1 px-3 my-0.5 rounded-sm overflow-hidden ${tab === 'models' ? 'bg-black/10 dark:bg-gray-200/10' : ''} hover:bg-black/10 hover:dark:bg-gray-200/10 active:bg-black/10 active:dark:bg-gray-200/10 `}
onClick={() => { setTab('models') }}
>Models</button>
{/* <button className={`text-left p-1 px-3 my-0.5 rounded-sm overflow-hidden ${tab === 'features' ? 'bg-black/10 dark:bg-gray-200/10' : ''} hover:bg-black/10 hover:dark:bg-gray-200/10 active:bg-black/10 active:dark:bg-gray-200/10 `}
onClick={() => { setTab('features') }}
>Features</button> */}
<button className={`text-left p-1 px-3 my-0.5 rounded-sm overflow-hidden ${tab === 'general' ? 'bg-black/10 dark:bg-gray-200/10' : ''} hover:bg-black/10 hover:dark:bg-gray-200/10 active:bg-black/10 active:dark:bg-gray-200/10 `}
onClick={() => { setTab('general') }}
>General</button>
</div>
{/* separator */}
@@ -417,41 +494,11 @@ export const Settings = () => {
<div className='w-full overflow-y-auto'>
<div className={`${tab !== 'models' ? 'hidden' : ''}`}>
<h2 className={`text-3xl mb-2`}>Local Providers</h2>
{/* <h3 className={`text-md opacity-50 mb-2`}>{`Keep your data private by hosting AI locally on your computer.`}</h3> */}
{/* <h3 className={`text-md opacity-50 mb-2`}>{`Instructions:`}</h3> */}
<h3 className={`text-md mb-2`}>{`Void can access any model that you host locally. By default, we automatically detect your local models.`}</h3>
<div className='pl-4 select-text opacity-50'>
<h4 className={`text-xs mb-2`}><ChatMarkdownRender string={`1. Download [Ollama](https://ollama.com/download).`} /></h4>
<h4 className={`text-xs mb-2`}><ChatMarkdownRender string={`2. Open your terminal.`} /></h4>
<h4 className={`text-xs mb-2`}><ChatMarkdownRender string={`3. Run \`ollama run llama3.1\`. This installs Meta's llama model which is competitive with GPT-series models. It requires 5GB of memory.`} /></h4>
<h4 className={`text-xs mb-2`}><ChatMarkdownRender string={`4. Run \`ollama run qwen2.5-coder:1.5b\`. This is a faster autocomplete model and requires 1GB of memory.`} /></h4>
{/* TODO we should create UI for downloading models without user going into terminal */}
</div>
<ErrorBoundary>
<VoidProviderSettings providerNames={localProviderNames} />
</ErrorBoundary>
<h2 className={`text-3xl mb-2 mt-16`}>More Providers</h2>
<h3 className={`text-md mb-2`}>{`Void can also access models like ChatGPT and Claude. We recommend using Anthropic or OpenAI.`}</h3>
{/* <h3 className={`text-md opacity-50 mb-2`}>{`Access models like ChatGPT and Claude. We recommend using Anthropic or OpenAI as providers, or Groq as a faster alternative.`}</h3> */}
<ErrorBoundary>
<VoidProviderSettings providerNames={nonlocalProviderNames} />
</ErrorBoundary>
<h2 className={`text-3xl mb-2 mt-16`}>Models</h2>
<ErrorBoundary>
<VoidFeatureFlagSettings />
<RefreshableModels />
<ModelDump />
<AddModelMenuFull />
</ErrorBoundary>
<FeaturesTab />
</div>
<div className={`${tab !== 'features' ? 'hidden' : ''}`}>
<h2 className={`text-3xl mb-2`} onClick={() => { setTab('features') }}>Features</h2>
{/* <VoidFeatureFlagSettings /> */}
<div className={`${tab !== 'general' ? 'hidden' : ''}`}>
<GeneralTab />
</div>
</div>
@@ -9,7 +9,34 @@ module.exports = {
content: ['./src2/**/*.{jsx,tsx}'], // uses these files to decide how to transform the css file
theme: {
extend: {
fontSize: {
xs: '10px',
sm: '12px',
root: '13px',
lg: '14px',
xl: '16px',
'2xl': '18px',
'3xl': '20px',
'4xl': '24px',
'5xl': '30px',
'6xl': '36px',
'7xl': '48px',
'8xl': '64px',
'9xl': '72px',
},
// common colors to use, ordered light to dark
colors: {
"void-bg-1": "var(--vscode-input-background)",
"void-bg-2": "var(--vscode-sideBar-background)",
"void-bg-3": "var(--vscode-editor-background)",
"void-fg-1": "var(--vscode-editor-foreground)",
"void-fg-2": "var(--vscode-input-foreground)",
"void-fg-3": "var(--vscode-input-placeholderForeground)",
"void-warning": "var(--vscode-charts-orange)",
vscode: {
// see: https://code.visualstudio.com/api/extension-guides/webview#theming-webview-content
@@ -39,8 +66,8 @@ module.exports = {
"input-bg": "var(--vscode-input-background)",
"input-border": "var(--vscode-input-border)",
"input-fg": "var(--vscode-input-foreground)",
"input-placeholder-fg": "var(--vscode-placeholderForeground)",
"input-active-bg": "var(--vscode-activeBackground)",
"input-placeholder-fg": "var(--vscode-input-placeholderForeground)",
"input-active-bg": "var(--vscode-input-activeBackground)",
"input-option-active-border": "var(--vscode-inputOption-activeBorder)",
"input-option-active-fg": "var(--vscode-inputOption-activeForeground)",
"input-option-hover-bg": "var(--vscode-inputOption-hoverBackground)",
@@ -9,7 +9,7 @@ export default defineConfig({
entry: [
'./src2/sidebar-tsx/index.tsx',
'./src2/void-settings-tsx/index.tsx',
'./src2/ctrl-k-tsx/index.tsx',
'./src2/quick-edit-tsx/index.tsx',
'./src2/diff/index.tsx',
],
outDir: './out',
@@ -26,9 +26,18 @@ import { VOID_OPEN_SETTINGS_ACTION_ID } from './voidSettingsPane.js';
// ---------- Register commands and keybindings ----------
const roundRangeToLines = (range: IRange | null | undefined) => {
export const roundRangeToLines = (range: IRange | null | undefined, options: { emptySelectionBehavior: 'null' | 'line' }) => {
if (!range)
return null
// treat as no selection if selection is empty
if (range.endColumn === range.startColumn && range.endLineNumber === range.startLineNumber) {
if (options.emptySelectionBehavior === 'null')
return null
else if (options.emptySelectionBehavior === 'line')
return { startLineNumber: range.startLineNumber, startColumn: 1, endLineNumber: range.startLineNumber, endColumn: 1 }
}
// IRange is 1-indexed
const endLine = range.endColumn === 1 ? range.endLineNumber - 1 : range.endLineNumber // e.g. if the user triple clicks, it selects column=0, line=line -> column=0, line=line+1
const newRange: IRange = {
@@ -72,51 +81,49 @@ registerAction2(class extends Action2 {
stateService.fireFocusChat()
const editor = editorService.getActiveCodeEditor()
const selectionRange = roundRangeToLines(
// accessor.get(IEditorService).activeTextEditorControl?.getSelection()
editor?.getSelection()
// accessor.get(IEditorService).activeTextEditorControl?.getSelection()
const selectionRange = roundRangeToLines(editor?.getSelection(), { emptySelectionBehavior: 'null' })
// select whole lines
if (selectionRange) {
editor?.setSelection({ startLineNumber: selectionRange.startLineNumber, endLineNumber: selectionRange.endLineNumber, startColumn: 1, endColumn: Number.MAX_SAFE_INTEGER })
}
const selectionStr = getContentInRange(model, selectionRange)
const selection: CodeStagingSelection = !selectionRange || !selectionStr || (selectionRange.startLineNumber > selectionRange.endLineNumber) ? {
type: 'File',
fileURI: model.uri,
selectionStr: null,
range: null,
} : {
type: 'Selection',
fileURI: model.uri,
selectionStr: selectionStr,
range: selectionRange,
}
// add selection to staging
const threadHistoryService = accessor.get(IThreadHistoryService)
const currentStaging = threadHistoryService.state._currentStagingSelections
const currentStagingEltIdx = currentStaging?.findIndex(s =>
s.fileURI.fsPath === model.uri.fsPath
&& s.range?.startLineNumber === selection.range?.startLineNumber
&& s.range?.endLineNumber === selection.range?.endLineNumber
)
if (selectionRange) {
// select whole lines
editor?.setSelection({ startLineNumber: selectionRange.startLineNumber, endLineNumber: selectionRange.endLineNumber, startColumn: 1, endColumn: Number.MAX_SAFE_INTEGER })
const selectionStr = getContentInRange(model, selectionRange)
const selection: CodeStagingSelection = selectionStr === null || selectionRange.startLineNumber > selectionRange.endLineNumber ? {
type: 'File',
fileURI: model.uri,
selectionStr: null,
range: null,
} : {
type: 'Selection',
fileURI: model.uri,
selectionStr: selectionStr,
range: selectionRange,
}
// add selection to staging
const threadHistoryService = accessor.get(IThreadHistoryService)
const currentStaging = threadHistoryService.state._currentStagingSelections
const currentStagingEltIdx = currentStaging?.findIndex(s =>
s.fileURI.fsPath === model.uri.fsPath
&& s.range?.startLineNumber === selection.range?.startLineNumber
&& s.range?.endLineNumber === selection.range?.endLineNumber
)
// if matches with existing selection, overwrite
if (currentStagingEltIdx !== undefined && currentStagingEltIdx !== -1) {
threadHistoryService.setStaging([
...currentStaging!.slice(0, currentStagingEltIdx),
selection,
...currentStaging!.slice(currentStagingEltIdx + 1, Infinity)
])
}
// if no match, add
else {
threadHistoryService.setStaging([...(currentStaging ?? []), selection])
}
// if matches with existing selection, overwrite
if (currentStagingEltIdx !== undefined && currentStagingEltIdx !== -1) {
threadHistoryService.setStaging([
...currentStaging!.slice(0, currentStagingEltIdx),
selection,
...currentStaging!.slice(currentStagingEltIdx + 1, Infinity)
])
}
// if no match, add
else {
threadHistoryService.setStaging([...(currentStaging ?? []), selection])
}
}