mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
monaco loads... need to adjust height
This commit is contained in:
parent
a49a1324a5
commit
09d84044b5
10 changed files with 199 additions and 225 deletions
|
|
@ -5,6 +5,13 @@ esbuild.build({
|
|||
entryPoints: ['src/sidebar/index.tsx'],
|
||||
bundle: true,
|
||||
minify: true,
|
||||
loader: {
|
||||
'.ttf': 'file',
|
||||
'.woff': 'file',
|
||||
'.woff2': 'file',
|
||||
'.eot': 'file',
|
||||
'.svg': 'file',
|
||||
},
|
||||
sourcemap: true,
|
||||
outfile: 'dist/sidebar/index.js',
|
||||
format: 'iife', // apparently iife is safe for browsers (safer than cjs)
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@
|
|||
}
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "npm run compile",
|
||||
"vscode:prepublish": "echo \"running prepublish\"",
|
||||
"watch": "tsc -watch -p ./",
|
||||
"build": "rimraf dist && node build-tsx.js && node build-css.js",
|
||||
"pretest": "tsc -p ./ && eslint src --ext ts",
|
||||
|
|
|
|||
|
|
@ -65,7 +65,8 @@ type MessageFromSidebar = (
|
|||
type ChatThreads = {
|
||||
[id: string]: {
|
||||
id: string; // store the id here too
|
||||
createdAt: string;
|
||||
createdAt: string; // ISO string
|
||||
lastModified: string; // ISO string
|
||||
messages: ChatMessage[];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,8 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
// send contents to webview
|
||||
webview.postMessage({ type: 'files', files, } satisfies MessageToSidebar)
|
||||
|
||||
} else if (m.type === 'applyChanges') {
|
||||
}
|
||||
else if (m.type === 'applyChanges') {
|
||||
|
||||
const editor = vscode.window.activeTextEditor
|
||||
if (!editor) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import React, { FormEvent, useCallback, useEffect, useRef, useState } from "reac
|
|||
import { marked } from 'marked';
|
||||
import MarkdownRender from "./markdown/MarkdownRender";
|
||||
import BlockCode from "./markdown/BlockCode";
|
||||
import { SelectedFiles } from "./components/SelectedFiles";
|
||||
import { File, ChatMessage, CodeSelection } from "../common/shared_types";
|
||||
import * as vscode from 'vscode'
|
||||
import { awaitVSCodeResponse, getVSCodeAPI, onMessageFromVSCode, useOnVSCodeMessage } from "./getVscodeApi";
|
||||
|
|
@ -63,6 +62,55 @@ Please edit the selected code following these instructions:
|
|||
return str;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const getBasename = (pathStr: string) => {
|
||||
// "unixify" path
|
||||
pathStr = pathStr.replace(/[/\\]+/g, "/") // replace any / or \ or \\ with /
|
||||
const parts = pathStr.split("/") // split on /
|
||||
return parts[parts.length - 1]
|
||||
}
|
||||
|
||||
export const SelectedFiles = ({ files, setFiles, }: { files: vscode.Uri[], setFiles: null | ((files: vscode.Uri[]) => void) }) => {
|
||||
return (
|
||||
files.length !== 0 && (
|
||||
<div className="flex flex-wrap -mx-1 -mb-1">
|
||||
{files.map((filename, i) => (
|
||||
<button
|
||||
key={filename.path}
|
||||
disabled={!setFiles}
|
||||
className={`btn btn-secondary btn-sm border border-vscode-input-border rounded flex items-center space-x-2 mx-1 mb-1 disabled:cursor-default`}
|
||||
type="button"
|
||||
onClick={() => setFiles?.([...files.slice(0, i), ...files.slice(i + 1, Infinity)])}
|
||||
>
|
||||
<span>{getBasename(filename.fsPath)}</span>
|
||||
|
||||
{/* X button */}
|
||||
{!!setFiles && <span className="">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
className="size-4"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M6 18 18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</span>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => {
|
||||
|
||||
const role = chatMessage.role
|
||||
|
|
@ -76,16 +124,17 @@ const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => {
|
|||
if (role === 'user') {
|
||||
chatbubbleContents = <>
|
||||
<SelectedFiles files={chatMessage.files} setFiles={null} />
|
||||
{chatMessage.selection?.selectionStr && <BlockCode text={chatMessage.selection.selectionStr} hideToolbar />}
|
||||
{chatMessage.selection?.selectionStr && <BlockCode
|
||||
text={chatMessage.selection.selectionStr}
|
||||
buttonsOnHover={null}
|
||||
/>}
|
||||
{children}
|
||||
</>
|
||||
}
|
||||
else if (role === 'assistant') {
|
||||
|
||||
chatbubbleContents = <MarkdownRender string={children} /> // sectionsHTML
|
||||
}
|
||||
|
||||
|
||||
return <div className={`${role === 'user' ? 'text-right' : 'text-left'}`}>
|
||||
<div className={`inline-block p-2 rounded-lg space-y-2 ${role === 'user' ? 'bg-vscode-input-bg text-vscode-input-fg' : ''} max-w-full`}>
|
||||
{chatbubbleContents}
|
||||
|
|
@ -224,8 +273,8 @@ export const SidebarChat = () => {
|
|||
abortFnRef.current?.()
|
||||
|
||||
// if messageStream was not empty, add it to the history
|
||||
const llmContent = messageStream || '(canceled)'
|
||||
const newHistoryElt: ChatMessage = { role: 'assistant', displayContent: messageStream, content: llmContent }
|
||||
const llmContent = messageStream || '(null)'
|
||||
const newHistoryElt: ChatMessage = { role: 'assistant', content: llmContent, displayContent: messageStream, }
|
||||
addMessageToHistory(newHistoryElt)
|
||||
|
||||
setMessageStream('')
|
||||
|
|
@ -233,14 +282,9 @@ export const SidebarChat = () => {
|
|||
|
||||
}, [captureChatEvent, messageStream, addMessageToHistory])
|
||||
|
||||
//Clear code selection
|
||||
const clearSelection = () => {
|
||||
setSelection(null);
|
||||
};
|
||||
|
||||
|
||||
return <>
|
||||
<div className="overflow-y-auto overflow-x-hidden space-y-4">
|
||||
<div className="overflow-x-hidden space-y-4">
|
||||
{/* previous messages */}
|
||||
{currentThread !== null && currentThread.messages.map((message, i) =>
|
||||
<ChatBubble key={i} chatMessage={message} />
|
||||
|
|
@ -261,14 +305,15 @@ export const SidebarChat = () => {
|
|||
<SelectedFiles files={files} setFiles={setFiles} />
|
||||
{/* selected code */}
|
||||
{!!selection?.selectionStr && (
|
||||
<BlockCode className="rounded bg-vscode-sidebar-bg" text={selection.selectionStr} toolbar={(
|
||||
<button
|
||||
onClick={clearSelection}
|
||||
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
)} />
|
||||
<BlockCode text={selection.selectionStr}
|
||||
buttonsOnHover={(
|
||||
<button
|
||||
onClick={() => setSelection(null)}
|
||||
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
)} />
|
||||
)}
|
||||
</div>}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export const SidebarThreadSelector = ({ onClose }: { onClose: () => void }) => {
|
|||
const { allThreads, currentThread, switchToThread } = useThreads()
|
||||
|
||||
// sorted by most recent to least recent
|
||||
const sortedThreadIds = Object.keys(allThreads ?? {}).sort((threadId1, threadId2) => allThreads![threadId1].createdAt > allThreads![threadId2].createdAt ? -1 : 1)
|
||||
const sortedThreadIds = Object.keys(allThreads ?? {}).sort((threadId1, threadId2) => allThreads![threadId1].lastModified > allThreads![threadId2].lastModified ? 1 : -1)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-1">
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
import React from "react"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
const getBasename = (pathStr: string) => {
|
||||
// "unixify" path
|
||||
pathStr = pathStr.replace(/[/\\]+/g, "/") // replace any / or \ or \\ with /
|
||||
const parts = pathStr.split("/") // split on /
|
||||
return parts[parts.length - 1]
|
||||
}
|
||||
|
||||
export const SelectedFiles = ({ files, setFiles, }: { files: vscode.Uri[], setFiles: null | ((files: vscode.Uri[]) => void) }) => {
|
||||
return (
|
||||
files.length !== 0 && (
|
||||
<div className="flex flex-wrap -mx-1 -mb-1">
|
||||
{files.map((filename, i) => (
|
||||
<button
|
||||
key={filename.path}
|
||||
disabled={!setFiles}
|
||||
className={`btn btn-secondary btn-sm border border-vscode-input-border rounded flex items-center space-x-2 mx-1 mb-1 disabled:cursor-default`}
|
||||
type="button"
|
||||
onClick={() => setFiles?.([...files.slice(0, i), ...files.slice(i + 1, Infinity)])}
|
||||
>
|
||||
<span>{getBasename(filename.fsPath)}</span>
|
||||
|
||||
{/* X button */}
|
||||
{!!setFiles && <span className="">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
className="size-4"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M6 18 18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</span>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -14,11 +14,15 @@ type ConfigForThreadsValueType = {
|
|||
|
||||
const ThreadsContext = createContext<ConfigForThreadsValueType>(undefined as unknown as ConfigForThreadsValueType)
|
||||
|
||||
const createNewThread = () => ({
|
||||
id: new Date().getTime().toString(),
|
||||
createdAt: new Date().toISOString(),
|
||||
messages: [],
|
||||
})
|
||||
const createNewThread = () => {
|
||||
const now = new Date().toISOString()
|
||||
return {
|
||||
id: new Date().getTime().toString(),
|
||||
createdAt: now,
|
||||
lastModified: now,
|
||||
messages: [],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// const [stateRef, setState] = useInstantState(initVal)
|
||||
|
|
@ -67,6 +71,7 @@ export function ThreadsProvider({ children }: { children: ReactNode }) {
|
|||
...allThreads.current,
|
||||
[currentThread.id]: {
|
||||
...currentThread,
|
||||
lastModified: new Date().toISOString(),
|
||||
messages: [...currentThread.messages, message],
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,162 +1,70 @@
|
|||
import React, { ReactNode, useCallback, useEffect, useState } from "react"
|
||||
import { getVSCodeAPI } from "../getVscodeApi"
|
||||
|
||||
import SyntaxHighlighter from "react-syntax-highlighter";
|
||||
import { atomOneDarkReasonable } from "react-syntax-highlighter/dist/esm/styles/hljs";
|
||||
import MonacoEditor from '@monaco-editor/react'
|
||||
import { editor } from 'monaco-editor'
|
||||
|
||||
import * as monaco from 'monaco-editor';
|
||||
import { loader } from '@monaco-editor/react';
|
||||
|
||||
enum CopyButtonState {
|
||||
Copy = "Copy",
|
||||
Copied = "Copied!",
|
||||
Error = "Could not copy",
|
||||
}
|
||||
|
||||
const COPY_FEEDBACK_TIMEOUT = 1000 // amount of time to say 'Copied!'
|
||||
|
||||
loader.config({ monaco });
|
||||
|
||||
|
||||
// code block with toolbar (Apply, Copy, etc) at top
|
||||
const BlockCode = ({
|
||||
text,
|
||||
language,
|
||||
toolbar,
|
||||
hideToolbar = false,
|
||||
className,
|
||||
}: {
|
||||
text: string
|
||||
language?: string
|
||||
toolbar?: ReactNode
|
||||
hideToolbar?: boolean
|
||||
className?: string
|
||||
}) => {
|
||||
const [copyButtonState, setCopyButtonState] = useState(CopyButtonState.Copy)
|
||||
|
||||
const customStyle = {
|
||||
...atomOneDarkReasonable,
|
||||
'code[class*="language-"]': {
|
||||
...atomOneDarkReasonable['code[class*="language-"]'],
|
||||
background: "none",
|
||||
},
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (copyButtonState !== CopyButtonState.Copy) {
|
||||
setTimeout(() => {
|
||||
setCopyButtonState(CopyButtonState.Copy)
|
||||
}, COPY_FEEDBACK_TIMEOUT)
|
||||
}
|
||||
}, [copyButtonState])
|
||||
|
||||
const onCopy = useCallback(() => {
|
||||
navigator.clipboard.writeText(text).then(
|
||||
() => {
|
||||
setCopyButtonState(CopyButtonState.Copied)
|
||||
},
|
||||
() => {
|
||||
setCopyButtonState(CopyButtonState.Error)
|
||||
}
|
||||
)
|
||||
}, [text])
|
||||
|
||||
const defaultToolbar = (
|
||||
<>
|
||||
<button
|
||||
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
|
||||
onClick={onCopy}
|
||||
>
|
||||
{copyButtonState}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
|
||||
onClick={async () => {
|
||||
getVSCodeAPI().postMessage({ type: "applyChanges", code: text })
|
||||
}}
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
const BlockCode = ({ text, language, buttonsOnHover, }: { text: string, language?: string, buttonsOnHover?: ReactNode, }) => {
|
||||
|
||||
return (<>
|
||||
<div className={`relative group w-full bg-vscode-sidebar-bg overflow-hidden`}>
|
||||
|
||||
<MonacoEditor
|
||||
onMount={(editor, monaco) => {
|
||||
|
||||
const model = editor.getModel()
|
||||
model?.setEOL(monaco.editor.EndOfLineSequence.LF)
|
||||
|
||||
// model?.updateOptions({ tabSize: 4 }) // apparently this should get set on the model, not the editor ()
|
||||
|
||||
monaco?.editor.setTheme('dark')
|
||||
|
||||
|
||||
}}
|
||||
loading=''
|
||||
value={text}
|
||||
defaultValue={text}
|
||||
language={language}
|
||||
defaultLanguage={language}
|
||||
|
||||
// onChange={() => { onChangeText?.() }}
|
||||
height={'100%'} // 100% or the exact pixel height
|
||||
theme={'dark'}
|
||||
|
||||
|
||||
options={{
|
||||
matchBrackets: 'always',
|
||||
detectIndentation: false, // we always want a tab size of 4
|
||||
tabSize: 4,
|
||||
insertSpaces: true,
|
||||
|
||||
// fontSize: 15,
|
||||
wordWrapColumn: 10000, // we want this to be infinity
|
||||
// automaticLayout: true,
|
||||
wordWrap: 'bounded', // 'off'
|
||||
// wordBreak: 'keepAll',
|
||||
// automaticLayout: true,
|
||||
// lineDecorationsWidth: 0,
|
||||
lineNumbersMinChars: 4,
|
||||
lineNumbers: 'off',
|
||||
renderLineHighlight: 'none',
|
||||
minimap: { enabled: false },
|
||||
scrollBeyondLastColumn: 0,
|
||||
scrollBeyondLastLine: false,
|
||||
scrollbar: {
|
||||
alwaysConsumeMouseWheel: false, //height !== undefined
|
||||
// vertical: 'hidden',
|
||||
// horizontal: 'hidden'
|
||||
},
|
||||
|
||||
overviewRulerLanes: 0,
|
||||
readOnly: true,
|
||||
readOnlyMessage: undefined,
|
||||
quickSuggestions: false,
|
||||
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
<div className="relative group">
|
||||
|
||||
{!hideToolbar && (
|
||||
<div className="absolute top-0 right-0 opacity-0 group-hover:opacity-100 duration-500">
|
||||
<div className="flex space-x-2 p-2">{toolbar || defaultToolbar}</div>
|
||||
{!toolbar ? null : (
|
||||
<div className="absolute top-0 right-0 opacity-0 group-hover:opacity-100 duration-200">
|
||||
<div className="flex space-x-2 p-2">{buttonsOnHover === null ? null : buttonsOnHover}</div>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`overflow-x-auto rounded-sm text-vscode-editor-fg bg-vscode-editor-bg ${!hideToolbar ? "rounded-tl-none" : ""} ${className}`}
|
||||
>
|
||||
<SyntaxHighlighter
|
||||
language={language}
|
||||
style={customStyle}
|
||||
className={"rounded-sm"}
|
||||
>
|
||||
{text}
|
||||
</SyntaxHighlighter>
|
||||
|
||||
</div>
|
||||
<MonacoEditor
|
||||
onMount={(editor, monaco) => {
|
||||
const model = editor.getModel()
|
||||
model?.setEOL(monaco.editor.EndOfLineSequence.LF)
|
||||
monaco?.editor.setTheme('vs-dark')
|
||||
}}
|
||||
loading='loading'
|
||||
value={text}
|
||||
language={language}
|
||||
|
||||
// onChange={() => { onChangeText?.() }}
|
||||
height={'100%'} // 100% or the exact pixel height
|
||||
theme={'vs-dark'}
|
||||
|
||||
options={{
|
||||
matchBrackets: 'always',
|
||||
detectIndentation: false, // we always want a tab size of 4
|
||||
tabSize: 4,
|
||||
insertSpaces: true,
|
||||
|
||||
// fontSize: 15,
|
||||
wordWrapColumn: 10000, // we want this to be infinity
|
||||
// automaticLayout: true,
|
||||
wordWrap: 'bounded', // 'off'
|
||||
// wordBreak: 'keepAll',
|
||||
// automaticLayout: true,
|
||||
// lineDecorationsWidth: 0,
|
||||
lineNumbersMinChars: 4,
|
||||
lineNumbers: 'off',
|
||||
renderLineHighlight: 'none',
|
||||
minimap: { enabled: false },
|
||||
scrollBeyondLastColumn: 0,
|
||||
scrollBeyondLastLine: false,
|
||||
scrollbar: {
|
||||
alwaysConsumeMouseWheel: true, // height !== undefined
|
||||
// vertical: 'hidden',
|
||||
// horizontal: 'hidden'
|
||||
},
|
||||
|
||||
overviewRulerLanes: 0,
|
||||
readOnly: true,
|
||||
readOnlyMessage: undefined,
|
||||
quickSuggestions: false,
|
||||
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,57 @@
|
|||
import React, { JSX } from "react"
|
||||
import React, { JSX, useCallback, useEffect, useState } from "react"
|
||||
import { marked, MarkedToken, Token, TokensList } from "marked"
|
||||
import BlockCode from "./BlockCode"
|
||||
import { getVSCodeAPI } from "../getVscodeApi"
|
||||
|
||||
|
||||
enum CopyButtonState {
|
||||
Copy = "Copy",
|
||||
Copied = "Copied!",
|
||||
Error = "Could not copy",
|
||||
}
|
||||
|
||||
const COPY_FEEDBACK_TIMEOUT = 1000 // amount of time to say 'Copied!'
|
||||
|
||||
const CodeButtonsOnHover = ({ text }: { text: string }) => {
|
||||
const [copyButtonState, setCopyButtonState] = useState(CopyButtonState.Copy)
|
||||
|
||||
useEffect(() => {
|
||||
if (copyButtonState !== CopyButtonState.Copy) {
|
||||
setTimeout(() => {
|
||||
setCopyButtonState(CopyButtonState.Copy)
|
||||
}, COPY_FEEDBACK_TIMEOUT)
|
||||
}
|
||||
}, [copyButtonState])
|
||||
|
||||
const onCopy = useCallback(() => {
|
||||
navigator.clipboard.writeText(text).then(
|
||||
() => {
|
||||
setCopyButtonState(CopyButtonState.Copied)
|
||||
},
|
||||
() => {
|
||||
setCopyButtonState(CopyButtonState.Error)
|
||||
}
|
||||
)
|
||||
}, [text])
|
||||
|
||||
return <>
|
||||
<button
|
||||
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
|
||||
onClick={onCopy}
|
||||
>
|
||||
{copyButtonState}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
|
||||
onClick={async () => {
|
||||
getVSCodeAPI().postMessage({ type: "applyChanges", code: text })
|
||||
}}
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
const RenderToken = ({ token, nested = false }: { token: Token | string, nested?: boolean }): JSX.Element => {
|
||||
|
||||
|
|
@ -12,7 +63,11 @@ const RenderToken = ({ token, nested = false }: { token: Token | string, nested?
|
|||
}
|
||||
|
||||
if (t.type === "code") {
|
||||
return <BlockCode text={t.text} language={t.lang} />
|
||||
return <BlockCode
|
||||
text={t.text}
|
||||
language={t.lang}
|
||||
buttonsOnHover={<CodeButtonsOnHover text={t.text} />}
|
||||
/>
|
||||
}
|
||||
|
||||
if (t.type === "heading") {
|
||||
|
|
|
|||
Loading…
Reference in a new issue