diff options
author | Nate Sesti <sestinj@gmail.com> | 2023-06-26 16:39:07 -0700 |
---|---|---|
committer | Nate Sesti <sestinj@gmail.com> | 2023-06-26 16:39:07 -0700 |
commit | 0b3e6fdf553a481bdd899a2608376dbb95c8e72e (patch) | |
tree | 615381124b54fb8a10523335c971b5a2d208b767 /extension | |
parent | deca77889cfc8eaa4065694f113525eda365e5d0 (diff) | |
parent | 0862b388dc0f07e45b6daeaec1f8c05925623692 (diff) | |
download | sncontinue-0b3e6fdf553a481bdd899a2608376dbb95c8e72e.tar.gz sncontinue-0b3e6fdf553a481bdd899a2608376dbb95c8e72e.tar.bz2 sncontinue-0b3e6fdf553a481bdd899a2608376dbb95c8e72e.zip |
Merge branch 'main' into styled-code
Diffstat (limited to 'extension')
36 files changed, 443 insertions, 734 deletions
diff --git a/extension/README.md b/extension/README.md index 63200a85..b43203bb 100644 --- a/extension/README.md +++ b/extension/README.md @@ -2,21 +2,18 @@ **[Continue](https://continue.dev/docs/) is the open-source library for accelerating software development with language models** -You can use Continue to use and create recipes that automate the most repetitive sequences of tasks in your workflows. - ## Getting Started -After install is complete, the Continue extension will automatically open. You can also open it by pressing `cmd+shift+p` on Mac / `ctrl+shift+p` on Windows and then selecting `Continue: Open Continue GUI` - -To test a few common recipes, open a blank python file and try the following: +After install is complete, the Continue extension will automatically open. You can also open it by pressing `cmd+k` on Mac / `ctrl+k` on Windows and then selecting `Continue: Open Continue GUI`. -- Ask "write a calculator class in python" -- /comment to write comments for the class -- /pytest to write Pytest unit tests in a separate file -- Ask in natural language for a new method +To learn how to use it, try to use Continue for the following after `calculator.py` opens: +- "Write me a calculator class" +- Ask for a new method (e.g. "exp", "mod", "sqrt") +- Type /comment to write comments for the entire class +- Ask about how the class works, how to write it in another language, etc. Give Continue a star on GitHub [here](https://github.com/continuedev/continue) ## Feedback -Have thoughts about Continue? Please [leave an issue](https://github.com/continuedev/continue/issues/new) or email us at hi@continue.dev :) +Have thoughts about Continue? Please [leave an issue](https://github.com/continuedev/continue/issues/new) or email us at hi@continue.dev :)
\ No newline at end of file diff --git a/extension/package-lock.json b/extension/package-lock.json index c5d97cfb..fcd4c755 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.55", + "version": "0.0.70", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.55", + "version": "0.0.70", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 036d13f0..b56c60f4 100644 --- a/extension/package.json +++ b/extension/package.json @@ -13,8 +13,8 @@ "license": "Apache-2.0", "displayName": "Continue", "pricing": "Free", - "description": "Refine code 10x faster", - "version": "0.0.55", + "description": "Accelerating software development with language models", + "version": "0.0.70", "publisher": "Continue", "engines": { "vscode": "^1.74.0" @@ -81,7 +81,7 @@ { "id": "continue", "title": "Continue ", - "icon": "react-app/dist/continue_arrow.png" + "icon": "react-app/dist/play_button.png" } ] }, diff --git a/extension/react-app/package-lock.json b/extension/react-app/package-lock.json index 18ef3ee6..fa624b3f 100644 --- a/extension/react-app/package-lock.json +++ b/extension/react-app/package-lock.json @@ -18,6 +18,7 @@ "react-markdown": "^8.0.5", "react-redux": "^8.0.5", "react-syntax-highlighter": "^15.5.0", + "react-switch": "^7.0.0", "styled-components": "^5.3.6", "vscode-webview": "^1.0.1-beta.1" }, @@ -2921,19 +2922,16 @@ } } }, - "node_modules/react-syntax-highlighter": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz", - "integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==", + "node_modules/react-switch": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/react-switch/-/react-switch-7.0.0.tgz", + "integrity": "sha512-KkDeW+cozZXI6knDPyUt3KBN1rmhoVYgAdCJqAh7st7tk8YE6N0iR89zjCWO8T8dUTeJGTR0KU+5CHCRMRffiA==", "dependencies": { - "@babel/runtime": "^7.3.1", - "highlight.js": "^10.4.1", - "lowlight": "^1.17.0", - "prismjs": "^1.27.0", - "refractor": "^3.6.0" + "prop-types": "^15.7.2" }, "peerDependencies": { - "react": ">= 0.14.0" + "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, "node_modules/read-cache": { @@ -5351,16 +5349,12 @@ "use-sync-external-store": "^1.0.0" } }, - "react-syntax-highlighter": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz", - "integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==", + "react-switch": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/react-switch/-/react-switch-7.0.0.tgz", + "integrity": "sha512-KkDeW+cozZXI6knDPyUt3KBN1rmhoVYgAdCJqAh7st7tk8YE6N0iR89zjCWO8T8dUTeJGTR0KU+5CHCRMRffiA==", "requires": { - "@babel/runtime": "^7.3.1", - "highlight.js": "^10.4.1", - "lowlight": "^1.17.0", - "prismjs": "^1.27.0", - "refractor": "^3.6.0" + "prop-types": "^15.7.2" } }, "read-cache": { diff --git a/extension/react-app/package.json b/extension/react-app/package.json index d43ec55e..5fe27916 100644 --- a/extension/react-app/package.json +++ b/extension/react-app/package.json @@ -19,6 +19,7 @@ "react-markdown": "^8.0.5", "react-redux": "^8.0.5", "react-syntax-highlighter": "^15.5.0", + "react-switch": "^7.0.0", "styled-components": "^5.3.6", "vscode-webview": "^1.0.1-beta.1" }, diff --git a/extension/react-app/public/continue.gif b/extension/react-app/public/continue.gif Binary files differnew file mode 100644 index 00000000..daed6663 --- /dev/null +++ b/extension/react-app/public/continue.gif diff --git a/extension/react-app/public/continue_arrow.png b/extension/react-app/public/continue_arrow.png Binary files differdeleted file mode 100644 index 3b16ddf9..00000000 --- a/extension/react-app/public/continue_arrow.png +++ /dev/null diff --git a/extension/react-app/public/play_button.png b/extension/react-app/public/play_button.png Binary files differnew file mode 100644 index 00000000..af379375 --- /dev/null +++ b/extension/react-app/public/play_button.png diff --git a/extension/react-app/src/components/CodeBlock.tsx b/extension/react-app/src/components/CodeBlock.tsx index a720a6ce..9438ab23 100644 --- a/extension/react-app/src/components/CodeBlock.tsx +++ b/extension/react-app/src/components/CodeBlock.tsx @@ -22,7 +22,7 @@ const StyledCopyButton = styled.button<{ visible: boolean }>` /* position: relative; */ float: right; border: none; - background-color: transparent; + background-color: ${secondaryDark}; cursor: pointer; padding: 0; /* margin: 4px; */ @@ -77,7 +77,10 @@ function CodeBlock(props: { language?: string; children: string }) { setHovered(false); }} > - <CopyButton visible={hovered} textToCopy={props.children} /> + <CopyButton + visible={hovered} + textToCopy={(props.children as any).props.children[0]} + /> <StyledCode>{props.children}</StyledCode> </StyledPre> ); diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index ddc9d5dc..3816cee8 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -116,6 +116,15 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { disabled={props.disabled} placeholder="Type '/' to see available slash commands." {...getInputProps({ + onChange: (e) => { + const target = e.target as HTMLTextAreaElement; + // Update the height of the textarea to match the content, up to a max of 200px. + target.style.height = "auto"; + target.style.height = `${Math.min( + target.scrollHeight, + 300 + ).toString()}px`; + }, onKeyDown: (event) => { if (event.key === "Enter" && event.shiftKey) { // Prevent Downshift's default 'Enter' behavior. @@ -136,14 +145,24 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { } else if (event.key === "Tab" && items.length > 0) { setInputValue(items[0].name); event.preventDefault(); - } else if (event.key === "ArrowUp") { + } else if ( + event.key === "ArrowUp" || + (event.key === "ArrowDown" && + event.currentTarget.value.split("\n").length > 1) + ) { + (event.nativeEvent as any).preventDownshiftDefault = true; + } else if ( + event.key === "ArrowUp" && + event.currentTarget.value.split("\n").length > 1 + ) { if (positionInHistory == 0) return; setInputValue(history[positionInHistory - 1]); setPositionInHistory((prev) => prev - 1); - } else if (event.key === "ArrowDown") { - if (positionInHistory >= history.length - 1) { - setInputValue(""); - } else { + } else if ( + event.key === "ArrowDown" && + event.currentTarget.value.split("\n").length > 1 + ) { + if (positionInHistory < history.length - 1) { setInputValue(history[positionInHistory + 1]); } setPositionInHistory((prev) => diff --git a/extension/react-app/src/components/ContinueButton.tsx b/extension/react-app/src/components/ContinueButton.tsx index c6117bf9..ef6719b7 100644 --- a/extension/react-app/src/components/ContinueButton.tsx +++ b/extension/react-app/src/components/ContinueButton.tsx @@ -1,6 +1,8 @@ import styled, { keyframes } from "styled-components"; import { Button } from "."; import { Play } from "@styled-icons/heroicons-outline"; +import { useSelector } from "react-redux"; +import { RootStore } from "../redux/store"; let StyledButton = styled(Button)` margin: auto; @@ -25,14 +27,21 @@ let StyledButton = styled(Button)` `; function ContinueButton(props: { onClick?: () => void; hidden?: boolean }) { + const vscMediaUrl = useSelector( + (state: RootStore) => state.config.vscMediaUrl + ); + return ( <StyledButton hidden={props.hidden} className="m-auto" onClick={props.onClick} > - <Play /> - {/* <img src={"/continue_arrow.png"} width="16px"></img> */} + {vscMediaUrl ? ( + <img src={`${vscMediaUrl}/play_button.png`} width="22px" /> + ) : ( + <Play /> + )} Continue </StyledButton> ); diff --git a/extension/react-app/src/components/DebugPanel.tsx b/extension/react-app/src/components/DebugPanel.tsx index 30f38779..94dbac9e 100644 --- a/extension/react-app/src/components/DebugPanel.tsx +++ b/extension/react-app/src/components/DebugPanel.tsx @@ -6,6 +6,7 @@ import { setApiUrl, setVscMachineId, setSessionId, + setVscMediaUrl, } from "../redux/slices/configSlice"; import { setHighlightedCode } from "../redux/slices/miscSlice"; import { updateFileSystem } from "../redux/slices/debugContexSlice"; @@ -37,6 +38,7 @@ function DebugPanel(props: DebugPanelProps) { dispatch(setApiUrl(event.data.apiUrl)); dispatch(setVscMachineId(event.data.vscMachineId)); dispatch(setSessionId(event.data.sessionId)); + dispatch(setVscMediaUrl(event.data.vscMediaUrl)); break; case "highlightedCode": dispatch(setHighlightedCode(event.data.rangeInFile)); diff --git a/extension/react-app/src/components/HeaderButtonWithText.tsx b/extension/react-app/src/components/HeaderButtonWithText.tsx index f9483f0f..30931f86 100644 --- a/extension/react-app/src/components/HeaderButtonWithText.tsx +++ b/extension/react-app/src/components/HeaderButtonWithText.tsx @@ -6,14 +6,20 @@ interface HeaderButtonWithTextProps { text: string; onClick?: (e: any) => void; children: React.ReactNode; + disabled?: boolean; } const HeaderButtonWithText = (props: HeaderButtonWithTextProps) => { const [hover, setHover] = useState(false); return ( <HeaderButton - style={{ padding: "3px" }} - onMouseEnter={() => setHover(true)} + disabled={props.disabled} + style={{ padding: "1px", paddingLeft: hover ? "4px" : "1px" }} + onMouseEnter={() => { + if (!props.disabled) { + setHover(true); + } + }} onMouseLeave={() => { setHover(false); }} diff --git a/extension/react-app/src/components/LoadingCover.tsx b/extension/react-app/src/components/LoadingCover.tsx new file mode 100644 index 00000000..a0f8f7a2 --- /dev/null +++ b/extension/react-app/src/components/LoadingCover.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import styled from "styled-components"; + +const StyledDiv = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100vh; + background: linear-gradient( + 101.79deg, + #12887a 0%, + #87245c 32%, + #e12637 63%, + #ffb215 100% + ); + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + z-index: 10; +`; + +const StyledImg = styled.img` + /* add your styles here */ +`; + +const StyledDiv2 = styled.div` + width: 50%; + height: 5px; + background: white; + margin-top: 20px; +`; + +interface LoadingCoverProps { + message: string; + hidden?: boolean; +} + +const LoadingCover = (props: LoadingCoverProps) => { + return ( + <StyledDiv style={{ display: props.hidden ? "none" : "inherit" }}> + <StyledImg src="continue.gif" alt="centered image" width="50%" /> + <StyledDiv2></StyledDiv2> + <p>{props.message}</p> + </StyledDiv> + ); +}; + +export default LoadingCover; diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index 1eb1d1fd..827d2d5f 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -49,9 +49,13 @@ const StepContainerDiv = styled.div<{ open: boolean }>` /* padding: 8px; */ `; -const HeaderDiv = styled.div<{ error: boolean }>` +const HeaderDiv = styled.div<{ error: boolean; loading: boolean }>` background-color: ${(props) => - props.error ? "#522" : vscBackgroundTransparent}; + props.error + ? "#522" + : props.loading + ? vscBackgroundTransparent + : vscBackground}; display: grid; grid-template-columns: 1fr auto auto; grid-gap: 8px; @@ -76,12 +80,22 @@ const StyledCode = styled.code` color: lightgray; `; +const gradient = keyframes` + 0% { + background-position: 0px 0; + } + 100% { + background-position: 100em 0; + } +`; + const GradientBorder = styled.div<{ borderWidth?: number; borderRadius?: string; borderColor?: string; isFirst: boolean; isLast: boolean; + loading: boolean; }>` border-radius: ${(props) => props.borderRadius || "0"}; padding-top: ${(props) => @@ -91,13 +105,18 @@ const GradientBorder = styled.div<{ background: ${(props) => props.borderColor ? props.borderColor - : `linear-gradient( + : `repeating-linear-gradient( 101.79deg, #12887a 0%, - #87245c 37.64%, - #e12637 65.98%, - #ffb215 110.45% + #87245c 16%, + #e12637 33%, + #ffb215 55%, + #e12637 67%, + #87245c 85%, + #12887a 99% )`}; + animation: ${(props) => (props.loading ? gradient : "")} 6s linear infinite; + background-size: 200% 200%; `; function StepContainer(props: StepContainerProps) { @@ -138,10 +157,15 @@ function StepContainer(props: StepContainerProps) { > <StepContainerDiv open={props.open}> <GradientBorder + loading={props.historyNode.active as boolean | false} isFirst={props.isFirst} isLast={props.isLast} borderColor={ - props.historyNode.observation?.error ? "#f00" : undefined + props.historyNode.observation?.error + ? "#f00" + : props.historyNode.active + ? undefined + : "white" } className="overflow-hidden cursor-pointer" onClick={(e) => { @@ -153,6 +177,7 @@ function StepContainer(props: StepContainerProps) { }} > <HeaderDiv + loading={props.historyNode.active as boolean | false} error={props.historyNode.observation?.error ? true : false} > <h4 className="m-2"> @@ -175,6 +200,7 @@ function StepContainer(props: StepContainerProps) { <> <HeaderButtonWithText + disabled={props.historyNode.active as boolean} onClick={(e) => { e.stopPropagation(); props.onDelete(); diff --git a/extension/react-app/src/components/TextDialog.tsx b/extension/react-app/src/components/TextDialog.tsx index e50a7686..2632e572 100644 --- a/extension/react-app/src/components/TextDialog.tsx +++ b/extension/react-app/src/components/TextDialog.tsx @@ -15,6 +15,7 @@ const DialogContainer = styled.div` top: 50%; left: 50%; transform: translate(-50%, -50%); + width: 75%; `; const Dialog = styled.div` @@ -76,7 +77,6 @@ const TextDialog = (props: { <Dialog> <P>Thanks for your feedback. We'll get back to you soon!</P> <TextArea - cols={50} rows={10} ref={textAreaRef} onKeyDown={(e) => { diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts index 824bb086..3d8e0a38 100644 --- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts +++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts @@ -13,6 +13,8 @@ abstract class AbstractContinueGUIClientProtocol { callback: (commands: { name: string; description: string }[]) => void ): void; + abstract changeDefaultModel(model: string): void; + abstract sendClear(): void; abstract retryAtIndex(index: number): void; diff --git a/extension/react-app/src/hooks/useContinueGUIProtocol.ts b/extension/react-app/src/hooks/useContinueGUIProtocol.ts index 59397742..f43a66ff 100644 --- a/extension/react-app/src/hooks/useContinueGUIProtocol.ts +++ b/extension/react-app/src/hooks/useContinueGUIProtocol.ts @@ -55,6 +55,10 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol { }); } + changeDefaultModel(model: string) { + this.messenger.send("change_default_model", { model }); + } + sendClear() { this.messenger.send("clear_history", {}); } diff --git a/extension/react-app/src/redux/slices/configSlice.ts b/extension/react-app/src/redux/slices/configSlice.ts index a6a641e6..1b107bed 100644 --- a/extension/react-app/src/redux/slices/configSlice.ts +++ b/extension/react-app/src/redux/slices/configSlice.ts @@ -37,9 +37,21 @@ export const configSlice = createSlice({ ...state, sessionId: action.payload, }), + setVscMediaUrl: ( + state: RootStore["config"], + action: { type: string; payload: string } + ) => ({ + ...state, + vscMediaUrl: action.payload, + }), }, }); -export const { setVscMachineId, setApiUrl, setWorkspacePath, setSessionId } = - configSlice.actions; +export const { + setVscMachineId, + setApiUrl, + setWorkspacePath, + setSessionId, + setVscMediaUrl, +} = configSlice.actions; export default configSlice.reducer; diff --git a/extension/react-app/src/redux/store.ts b/extension/react-app/src/redux/store.ts index f9eb0517..a5eef4ba 100644 --- a/extension/react-app/src/redux/store.ts +++ b/extension/react-app/src/redux/store.ts @@ -21,6 +21,7 @@ export interface RootStore { vscMachineId: string | undefined; sessionId: string | undefined; sessionStarted: number | undefined; + vscMediaUrl: string | undefined; }; chat: { messages: ChatMessage[]; diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx index 624d22d5..13b74423 100644 --- a/extension/react-app/src/tabs/gui.tsx +++ b/extension/react-app/src/tabs/gui.tsx @@ -1,11 +1,5 @@ import styled from "styled-components"; -import { - defaultBorderRadius, - vscBackground, - Loader, - MainTextInput, - HeaderButton, -} from "../components"; +import { defaultBorderRadius, Loader } from "../components"; import ContinueButton from "../components/ContinueButton"; import { useCallback, useEffect, useRef, useState } from "react"; import { History } from "../../../schema/History"; @@ -20,6 +14,11 @@ import { import ComboBox from "../components/ComboBox"; import TextDialog from "../components/TextDialog"; import HeaderButtonWithText from "../components/HeaderButtonWithText"; +import ReactSwitch from "react-switch"; +import { usePostHog } from "posthog-js/react"; +import { useSelector } from "react-redux"; +import { RootStore } from "../redux/store"; +import LoadingCover from "../components/LoadingCover"; const TopGUIDiv = styled.div` overflow: hidden; @@ -33,7 +32,7 @@ const UserInputQueueItem = styled.div` text-align: center; `; -const Footer = styled.footer` +const Footer = styled.footer<{ dataSwitchChecked: boolean }>` display: flex; flex-direction: row; gap: 8px; @@ -42,6 +41,8 @@ const Footer = styled.footer` align-items: center; margin-top: 8px; border-top: 0.1px solid gray; + background-color: ${(props) => + props.dataSwitchChecked ? "#12887a33" : "transparent"}; `; interface GUIProps { @@ -49,14 +50,22 @@ interface GUIProps { } function GUI(props: GUIProps) { + const posthog = usePostHog(); + const vscMachineId = useSelector( + (state: RootStore) => state.config.vscMachineId + ); + + const [usingFastModel, setUsingFastModel] = useState(false); const [waitingForSteps, setWaitingForSteps] = useState(false); const [userInputQueue, setUserInputQueue] = useState<string[]>([]); const [availableSlashCommands, setAvailableSlashCommands] = useState< { name: string; description: string }[] >([]); + const [dataSwitchChecked, setDataSwitchChecked] = useState(false); + const [showDataSharingInfo, setShowDataSharingInfo] = useState(false); const [stepsOpen, setStepsOpen] = useState<boolean[]>([]); const [history, setHistory] = useState<History | undefined>(); - // { + // { // timeline: [ // { // step: { @@ -144,6 +153,7 @@ function GUI(props: GUIProps) { // ], // }, // { + // active: false, // step: { // name: "SolveTracebackStep", // traceback: { @@ -204,24 +214,41 @@ function GUI(props: GUIProps) { }, [topGuiDivRef.current, scrollTimeout]); useEffect(() => { + const listener = (e: any) => { + // Cmd + J to toggle fast model + if (e.key === "i" && e.metaKey && e.shiftKey) { + setUsingFastModel((prev) => !prev); + } + }; + window.addEventListener("keydown", listener); + + return () => { + window.removeEventListener("keydown", listener); + }; + }, []); + + useEffect(() => { console.log("CLIENT ON STATE UPDATE: ", client, client?.onStateUpdate); client?.onStateUpdate((state) => { // Scroll only if user is at very bottom of the window. + setUsingFastModel(state.default_model === "gpt-3.5-turbo"); const shouldScrollToBottom = topGuiDivRef.current && topGuiDivRef.current?.offsetHeight - window.scrollY < 100; setWaitingForSteps(state.active); setHistory(state.history); setUserInputQueue(state.user_input_queue); - const nextStepsOpen = [...stepsOpen]; - for ( - let i = nextStepsOpen.length; - i < state.history.timeline.length; - i++ - ) { - nextStepsOpen.push(true); - } - setStepsOpen(nextStepsOpen); + setStepsOpen((prev) => { + const nextStepsOpen = [...prev]; + for ( + let i = nextStepsOpen.length; + i < state.history.timeline.length; + i++ + ) { + nextStepsOpen.push(true); + } + return nextStepsOpen; + }); if (shouldScrollToBottom) { scrollToBottom(); @@ -263,8 +290,9 @@ function GUI(props: GUIProps) { const onMainTextInput = () => { if (mainTextInputRef.current) { - if (!client) return; let input = mainTextInputRef.current.value; + mainTextInputRef.current.value = ""; + if (!client) return; if ( history?.timeline.length && @@ -301,6 +329,7 @@ function GUI(props: GUIProps) { // const iterations = useSelector(selectIterations); return ( <> + <LoadingCover hidden={true} message="Downloading local model..." /> <TextDialog showDialog={showFeedbackDialog} onEnter={(text) => { @@ -323,9 +352,7 @@ function GUI(props: GUIProps) { {typeof client === "undefined" && ( <> <Loader></Loader> - <p style={{ textAlign: "center" }}> - Trying to reconnect with server... - </p> + <p style={{ textAlign: "center" }}>Loading Continue server...</p> </> )} {history?.timeline.map((node: HistoryNode, index: number) => { @@ -340,7 +367,7 @@ function GUI(props: GUIProps) { setStepsOpen(nextStepsOpen); }} onToggleAll={() => { - setStepsOpen((prev) => prev.map(() => !prev[index])); + setStepsOpen((prev) => prev.map((_, index) => !prev[index])); }} key={index} onUserInput={(input: string) => { @@ -390,17 +417,87 @@ function GUI(props: GUIProps) { /> <ContinueButton onClick={onMainTextInput} /> </TopGUIDiv> - <Footer> + <div + style={{ + position: "fixed", + bottom: "50px", + backgroundColor: "white", + color: "black", + borderRadius: defaultBorderRadius, + padding: "16px", + margin: "16px", + }} + hidden={!showDataSharingInfo} + > + By turning on this switch, you signal that you would contribute this + software development data to a publicly accessible, open-source dataset + in the future. + <br /> + <br /> + <b> + {dataSwitchChecked + ? "No data is being collected. In the future, you would be contributing data" + : "No data is being collected. In the future, your data would not be shared"} + </b> + </div> + <Footer dataSwitchChecked={dataSwitchChecked}> + <div + style={{ + display: "flex", + gap: "4px", + marginRight: "auto", + alignItems: "center", + }} + onMouseEnter={() => { + setShowDataSharingInfo(true); + }} + onMouseLeave={() => { + setShowDataSharingInfo(false); + }} + > + <ReactSwitch + height={20} + handleDiameter={20} + width={40} + onChange={() => { + posthog?.capture("data_switch_toggled", { + vscMachineId: vscMachineId, + dataSwitchChecked: !dataSwitchChecked, + }); + setDataSwitchChecked((prev) => !prev); + }} + onColor="#12887a" + checked={dataSwitchChecked} + /> + <span style={{ cursor: "help", fontSize: "14px" }}> + Contribute Data + </span> + </div> + <HeaderButtonWithText + onClick={() => { + client?.changeDefaultModel( + usingFastModel ? "gpt-4" : "gpt-3.5-turbo" + ); + setUsingFastModel((prev) => !prev); + }} + text={usingFastModel ? "gpt-3.5-turbo" : "gpt-4"} + > + <div + style={{ fontSize: "18px", marginLeft: "2px", marginRight: "2px" }} + > + {usingFastModel ? "⚡" : "🧠"} + </div> + </HeaderButtonWithText> <HeaderButtonWithText onClick={() => { client?.sendClear(); }} - text="Clear History" + text="Clear All" > <Trash size="1.6em" /> </HeaderButtonWithText> <a href="https://continue.dev/docs" className="no-underline"> - <HeaderButtonWithText text="Continue Docs"> + <HeaderButtonWithText text="Docs"> <BookOpen size="1.6em" /> </HeaderButtonWithText> </a> diff --git a/extension/scripts/.gitignore b/extension/scripts/.gitignore index 7af27c08..fbb3bf9f 100644 --- a/extension/scripts/.gitignore +++ b/extension/scripts/.gitignore @@ -1,3 +1,5 @@ testdb env -stdout.txt
\ No newline at end of file +stdout.txt +.continue_env_installed +**.whl
\ No newline at end of file diff --git a/extension/scripts/continuedev-0.1.1-py3-none-any.whl b/extension/scripts/continuedev-0.1.1-py3-none-any.whl Binary files differdeleted file mode 100644 index 9d371e17..00000000 --- a/extension/scripts/continuedev-0.1.1-py3-none-any.whl +++ /dev/null diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts index 32726c86..0c92f095 100644 --- a/extension/src/activation/activate.ts +++ b/extension/src/activation/activate.ts @@ -2,30 +2,31 @@ import * as vscode from "vscode"; import { registerAllCommands } from "../commands"; import { registerAllCodeLensProviders } from "../lang-server/codeLens"; import { sendTelemetryEvent, TelemetryEvent } from "../telemetry"; -import { getExtensionUri } from "../util/vscode"; -import * as path from "path"; // import { openCapturedTerminal } from "../terminal/terminalEmulator"; import IdeProtocolClient from "../continueIdeClient"; import { getContinueServerUrl } from "../bridge"; -import { setupDebugPanel, ContinueGUIWebviewViewProvider } from "../debugPanel"; import { CapturedTerminal } from "../terminal/terminalEmulator"; +import { setupDebugPanel, ContinueGUIWebviewViewProvider } from "../debugPanel"; +import { startContinuePythonServer } from "./environmentSetup"; +// import { CapturedTerminal } from "../terminal/terminalEmulator"; export let extensionContext: vscode.ExtensionContext | undefined = undefined; export let ideProtocolClient: IdeProtocolClient; -export function activateExtension( +export async function activateExtension( context: vscode.ExtensionContext, showTutorial: boolean ) { - sendTelemetryEvent(TelemetryEvent.ExtensionActivated); + extensionContext = context; + sendTelemetryEvent(TelemetryEvent.ExtensionActivated); registerAllCodeLensProviders(context); registerAllCommands(context); // vscode.window.registerWebviewViewProvider("continue.continueGUIView", setupDebugPanel); - - let serverUrl = getContinueServerUrl(); + await startContinuePythonServer(); + const serverUrl = getContinueServerUrl(); ideProtocolClient = new IdeProtocolClient( `${serverUrl.replace("http", "ws")}/ide/ws`, @@ -49,41 +50,8 @@ export function activateExtension( })(); // All opened terminals should be replaced by our own terminal - vscode.window.onDidOpenTerminal((terminal) => { - if (terminal.name === "Continue") { - return; - } - const options = terminal.creationOptions; - const capturedTerminal = new CapturedTerminal({ - ...options, - name: "Continue", - }); - terminal.dispose(); - if (!ideProtocolClient.continueTerminal) { - ideProtocolClient.continueTerminal = capturedTerminal; - } - }); + // vscode.window.onDidOpenTerminal((terminal) => {}); // If any terminals are open to start, replace them - vscode.window.terminals.forEach((terminal) => { - if (terminal.name === "Continue") { - return; - } - const options = terminal.creationOptions; - const capturedTerminal = new CapturedTerminal( - { - ...options, - name: "Continue", - }, - (commandOutput: string) => { - ideProtocolClient.sendCommandOutput(commandOutput); - } - ); - terminal.dispose(); - if (!ideProtocolClient.continueTerminal) { - ideProtocolClient.continueTerminal = capturedTerminal; - } - }); - - extensionContext = context; + // vscode.window.terminals.forEach((terminal) => {} } diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 593b727e..25b6f643 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -7,6 +7,25 @@ import * as fs from "fs"; import rebuild from "@electron/rebuild"; import { getContinueServerUrl } from "../bridge"; import fetch from "node-fetch"; +import * as vscode from "vscode"; + +const MAX_RETRIES = 5; +async function retryThenFail( + fn: () => Promise<any>, + retries: number = MAX_RETRIES +): Promise<any> { + try { + return await fn(); + } catch (e) { + if (retries > 0) { + return await retryThenFail(fn, retries - 1); + } + vscode.window.showErrorMessage( + "Failed to set up Continue extension. Please email nate@continue.dev and we'll get this fixed ASAP!" + ); + throw e; + } +} async function runCommand(cmd: string): Promise<[string, string | undefined]> { console.log("Running command: ", cmd); @@ -77,55 +96,71 @@ function checkEnvExists() { ); } +function checkRequirementsInstalled() { + return fs.existsSync( + path.join(getExtensionUri().fsPath, "scripts", ".continue_env_installed") + ); +} + async function setupPythonEnv() { console.log("Setting up python env for Continue extension..."); - if (checkEnvExists()) return; - - // Assemble the command to create the env const [pythonCmd, pipCmd] = await getPythonPipCommands(); const [activateCmd, pipUpgradeCmd] = getActivateUpgradeCommands( pythonCmd, pipCmd ); - const createEnvCommand = [ - `cd ${path.join(getExtensionUri().fsPath, "scripts")}`, - `${pythonCmd} -m venv env`, - ].join("; "); - // Repeat until it is successfully created (sometimes it fails to generate the bin, need to try again) - while (true) { - const [, stderr] = await runCommand(createEnvCommand); - if (stderr) { - throw new Error(stderr); - } - if (checkEnvExists()) { - break; - } else { - // Remove the env and try again - const removeCommand = `rm -rf ${path.join( - getExtensionUri().fsPath, - "scripts", - "env" - )}`; - await runCommand(removeCommand); + if (checkEnvExists()) { + console.log("Python env already exists, skipping..."); + } else { + // Assemble the command to create the env + const createEnvCommand = [ + `cd ${path.join(getExtensionUri().fsPath, "scripts")}`, + `${pythonCmd} -m venv env`, + ].join("; "); + + // Repeat until it is successfully created (sometimes it fails to generate the bin, need to try again) + while (true) { + const [, stderr] = await runCommand(createEnvCommand); + if (stderr) { + throw new Error(stderr); + } + if (checkEnvExists()) { + break; + } else { + // Remove the env and try again + const removeCommand = `rm -rf ${path.join( + getExtensionUri().fsPath, + "scripts", + "env" + )}`; + await runCommand(removeCommand); + } } + console.log( + "Successfully set up python env at ", + getExtensionUri().fsPath + "/scripts/env" + ); } - console.log( - "Successfully set up python env at ", - getExtensionUri().fsPath + "/scripts/env" - ); - const installRequirementsCommand = [ - `cd ${path.join(getExtensionUri().fsPath, "scripts")}`, - activateCmd, - pipUpgradeCmd, - `${pipCmd} install -r requirements.txt`, - ].join(" && "); - const [, stderr] = await runCommand(installRequirementsCommand); - if (stderr) { - throw new Error(stderr); - } + await retryThenFail(async () => { + if (checkRequirementsInstalled()) { + console.log("Python requirements already installed, skipping..."); + } else { + const installRequirementsCommand = [ + `cd ${path.join(getExtensionUri().fsPath, "scripts")}`, + activateCmd, + pipUpgradeCmd, + `${pipCmd} install -r requirements.txt`, + "touch .continue_env_installed", + ].join(" ; "); + const [, stderr] = await runCommand(installRequirementsCommand); + if (stderr) { + throw new Error(stderr); + } + } + }); } function readEnvFile(path: string) { @@ -180,15 +215,11 @@ export async function startContinuePythonServer() { await setupPythonEnv(); // Check vscode settings - let serverUrl = getContinueServerUrl(); + const serverUrl = getContinueServerUrl(); if (serverUrl !== "http://localhost:8000") { return; } - console.log("Starting Continue python server..."); - - if (await checkServerRunning(serverUrl)) return; - let activateCmd = ". env/bin/activate"; let pythonCmd = "python3"; if (process.platform == "win32") { @@ -199,35 +230,41 @@ export async function startContinuePythonServer() { let command = `cd ${path.join( getExtensionUri().fsPath, "scripts" - )} && ${activateCmd} && cd .. && ${pythonCmd} -m scripts.run_continue_server`; + )} ; ${activateCmd} ; cd .. ; ${pythonCmd} -m scripts.run_continue_server`; + + return await retryThenFail(async () => { + console.log("Starting Continue python server..."); + + if (await checkServerRunning(serverUrl)) return; - return new Promise(async (resolve, reject) => { - try { - const child = spawn(command, { - shell: true, - }); - child.stdout.on("data", (data: any) => { - console.log(`stdout: ${data}`); - }); - child.stderr.on("data", (data: any) => { - console.log(`stderr: ${data}`); - if (data.includes("Uvicorn running on")) { - console.log("Successfully started Continue python server"); + return new Promise(async (resolve, reject) => { + try { + const child = spawn(command, { + shell: true, + }); + child.stdout.on("data", (data: any) => { + console.log(`stdout: ${data}`); + }); + child.stderr.on("data", (data: any) => { + console.log(`stderr: ${data}`); + if (data.includes("Uvicorn running on")) { + console.log("Successfully started Continue python server"); + resolve(null); + } + }); + child.on("error", (error: any) => { + console.log(`error: ${error.message}`); + }); + } catch (e) { + console.log("Failed to start Continue python server", e); + // If failed, check if it's because the server is already running (might have happened just after we checked above) + if (await checkServerRunning(serverUrl)) { resolve(null); + } else { + reject(); } - }); - child.on("error", (error: any) => { - console.log(`error: ${error.message}`); - }); - } catch (e) { - console.log("Failed to start Continue python server", e); - // If failed, check if it's because the server is already running (might have happened just after we checked above) - if (await checkServerRunning(serverUrl)) { - resolve(null); - } else { - reject(); } - } + }); }); } @@ -255,10 +292,10 @@ export async function downloadPython3() { throw new Error("python3 not found"); } else if (os === "linux") { command = - "sudo apt update && upgrade && sudo apt install python3 python3-pip"; + "sudo apt update ; upgrade ; sudo apt install python3 python3-pip"; } else if (os === "win32") { command = - "wget -O python_installer.exe https://www.python.org/ftp/python/3.11.3/python-3.11.3-amd64.exe && python_installer.exe /quiet InstallAllUsers=1 PrependPath=1 Include_test=0"; + "wget -O python_installer.exe https://www.python.org/ftp/python/3.11.3/python-3.11.3-amd64.exe ; python_installer.exe /quiet InstallAllUsers=1 PrependPath=1 Include_test=0"; pythonCmd = "python"; } diff --git a/extension/src/commands.ts b/extension/src/commands.ts index 5392a7a3..77273343 100644 --- a/extension/src/commands.ts +++ b/extension/src/commands.ts @@ -3,7 +3,6 @@ import { decorationManager, showAnswerInTextEditor, showGutterSpinner, - writeAndShowUnitTest, } from "./decorations"; import { acceptSuggestionCommand, @@ -12,19 +11,8 @@ import { suggestionUpCommand, } from "./suggestions"; import * as bridge from "./bridge"; -import { debugPanelWebview, setupDebugPanel } from "./debugPanel"; -// import { openCapturedTerminal } from "./terminal/terminalEmulator"; -import { getRightViewColumn } from "./util/vscode"; -import { - findSuspiciousCode, - runPythonScript, - writeUnitTestForFunction, -} from "./bridge"; +import { debugPanelWebview } from "./debugPanel"; import { sendTelemetryEvent, TelemetryEvent } from "./telemetry"; -import { getLanguageLibrary } from "./languages"; -import { SerializedDebugContext } from "./client"; -import { addFileSystemToDebugContext } from "./util/util"; -import { ideProtocolClient } from "./activation/activate"; // Copy everything over from extension.ts const commandsMap: { [command: string]: (...args: any) => any } = { @@ -62,79 +50,14 @@ const commandsMap: { [command: string]: (...args: any) => any } = { "continue.acceptSuggestion": acceptSuggestionCommand, "continue.rejectSuggestion": rejectSuggestionCommand, "continue.focusContinueInput": async () => { - if (!debugPanelWebview) { - vscode.commands.executeCommand("continue.continueGUIView.focus"); - } + vscode.commands.executeCommand("continue.continueGUIView.focus"); debugPanelWebview?.postMessage({ type: "focusContinueInput", }); }, - "continue.openCapturedTerminal": () => { - // Happens in webview resolution function - // openCapturedTerminal(); - }, - "continue.findSuspiciousCode": async ( - debugContext: SerializedDebugContext - ) => { - vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: "Finding suspicious code", - cancellable: false, - }, - async (progress, token) => { - let suspiciousCode = await findSuspiciousCode(debugContext); - debugContext.rangesInFiles = suspiciousCode; - let { filesystem } = addFileSystemToDebugContext(debugContext); - debugPanelWebview?.postMessage({ - type: "findSuspiciousCode", - codeLocations: suspiciousCode, - filesystem, - }); - } - ); - }, - "continue.debugTest": async (fileAndFunctionSpecifier: string) => { - sendTelemetryEvent(TelemetryEvent.AutoDebugThisTest); - let editor = vscode.window.activeTextEditor; - if (editor) editor.document.save(); - let { stdout } = await runPythonScript("run_unit_test.py", [ - fileAndFunctionSpecifier, - ]); - let traceback = getLanguageLibrary( - fileAndFunctionSpecifier.split("::")[0] - ).parseFirstStacktrace(stdout); - if (!traceback) { - vscode.window.showInformationMessage("The test passes!"); - return; - } - vscode.commands.executeCommand("continue.openContinueGUI").then(() => { - setTimeout(() => { - debugPanelWebview?.postMessage({ - type: "traceback", - value: traceback, - }); - }, 500); - }); - }, }; const textEditorCommandsMap: { [command: string]: (...args: any) => {} } = { - "continue.writeUnitTest": async (editor: vscode.TextEditor) => { - let position = editor.selection.active; - - let gutterSpinnerKey = showGutterSpinner(editor, position.line); - try { - let test = await writeUnitTestForFunction( - editor.document.fileName, - position - ); - writeAndShowUnitTest(editor.document.fileName, test); - } catch { - } finally { - decorationManager.deleteDecoration(gutterSpinnerKey); - } - }, "continue.writeDocstring": async (editor: vscode.TextEditor, _) => { sendTelemetryEvent(TelemetryEvent.GenerateDocstring); let gutterSpinnerKey = showGutterSpinner( @@ -201,20 +124,3 @@ async function answerQuestion( } ); } - -// async function suggestFixForAllWorkspaceProblems() { -// Something like this, just figure out the loops for diagnostics vs problems -// let problems = vscode.languages.getDiagnostics(); -// let codeSuggestions = await Promise.all(problems.map((problem) => { -// return bridge.suggestFixForProblem(problem[0].fsPath, problem[1]); -// })); -// for (const [uri, diagnostics] of problems) { -// for (let i = 0; i < diagnostics.length; i++) { -// let diagnostic = diagnostics[i]; -// let suggestedCode = codeSuggestions[i]; -// // If you're going to do this for a bunch of files at once, it will show the unsaved icon in the tab -// // BUT it would be better to have a single window to review all edits -// showSuggestion(uri.fsPath, diagnostic.range, suggestedCode) -// } -// } -// } diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index 3b5de93f..3a77e348 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -12,7 +12,6 @@ import { debugPanelWebview, setupDebugPanel } from "./debugPanel"; import { FileEditWithFullContents } from "../schema/FileEditWithFullContents"; import fs = require("fs"); import { WebsocketMessenger } from "./util/messenger"; -import { CapturedTerminal } from "./terminal/terminalEmulator"; import { decorationManager } from "./decorations"; class IdeProtocolClient { @@ -159,7 +158,7 @@ class IdeProtocolClient { editor.setDecorations(decorationType, [range]); // Listen for changes to cursor position and then remove the decoration (but keep for at least 2 seconds) - setTimeout(() => { + const allowRemoveHighlight = () => { const cursorDisposable = vscode.window.onDidChangeTextEditorSelection( (event) => { if (event.textEditor.document.uri.fsPath === rangeInFile.filepath) { @@ -168,7 +167,8 @@ class IdeProtocolClient { } } ); - }, 2000); + }; + setTimeout(allowRemoveHighlight, 2000); } } @@ -285,7 +285,7 @@ class IdeProtocolClient { edit.range.start.line, edit.range.start.character, edit.range.end.line, - edit.range.end.character + 1 + edit.range.end.character ); editor.edit((editBuilder) => { @@ -325,17 +325,14 @@ class IdeProtocolClient { return rangeInFiles; } - public continueTerminal: CapturedTerminal | undefined; - async runCommand(command: string) { - if (!this.continueTerminal || this.continueTerminal.isClosed()) { - this.continueTerminal = new CapturedTerminal({ - name: "Continue", - }); + if (vscode.window.terminals.length) { + vscode.window.terminals[0].sendText(command); + } else { + const terminal = vscode.window.createTerminal(); + terminal.show(); + terminal.sendText(command); } - - this.continueTerminal.show(); - return await this.continueTerminal.runCommand(command); } sendCommandOutput(output: string) { diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts index bb98eb46..b0db590a 100644 --- a/extension/src/debugPanel.ts +++ b/extension/src/debugPanel.ts @@ -1,21 +1,11 @@ import * as vscode from "vscode"; -import { - debugApi, - getContinueServerUrl, - runPythonScript, - unittestApi, -} from "./bridge"; -import { writeAndShowUnitTest } from "./decorations"; -import { showSuggestion } from "./suggestions"; -import { getLanguageLibrary } from "./languages"; +import { getContinueServerUrl } from "./bridge"; import { getExtensionUri, getNonce, openEditorAndRevealRange, } from "./util/vscode"; -import { sendTelemetryEvent, TelemetryEvent } from "./telemetry"; -import { RangeInFile, SerializedDebugContext } from "./client"; -import { addFileSystemToDebugContext } from "./util/util"; +import { RangeInFile } from "./client"; const WebSocket = require("ws"); class StreamManager { @@ -146,6 +136,9 @@ export function setupDebugPanel( let extensionUri = getExtensionUri(); let scriptUri: string; let styleMainUri: string; + let vscMediaUrl: string = debugPanelWebview + .asWebviewUri(vscode.Uri.joinPath(extensionUri, "react-app/dist")) + .toString(); const isProduction = true; // context?.extensionMode === vscode.ExtensionMode.Development; if (!isProduction) { @@ -236,6 +229,7 @@ export function setupDebugPanel( vscMachineId: vscode.env.machineId, apiUrl: getContinueServerUrl(), sessionId, + vscMediaUrl, }); // // Listen for changes to server URL in settings @@ -273,71 +267,6 @@ export function setupDebugPanel( connection.send(data.message); break; } - case "listTenThings": { - sendTelemetryEvent(TelemetryEvent.GenerateIdeas); - let resp = await debugApi.listtenDebugListPost({ - serializedDebugContext: data.debugContext, - }); - panel.webview.postMessage({ - type: "listTenThings", - value: resp.completion, - }); - break; - } - case "suggestFix": { - let completion: string; - let codeSelection = data.debugContext.rangesInFiles?.at(0); - if (codeSelection) { - completion = ( - await debugApi.inlineDebugInlinePost({ - inlineBody: { - filecontents: await vscode.workspace.fs - .readFile(vscode.Uri.file(codeSelection.filepath)) - .toString(), - startline: codeSelection.range.start.line, - endline: codeSelection.range.end.line, - traceback: data.debugContext.traceback, - }, - }) - ).completion; - } else if (data.debugContext.traceback) { - completion = ( - await debugApi.suggestionDebugSuggestionGet({ - traceback: data.debugContext.traceback, - }) - ).completion; - } else { - break; - } - panel.webview.postMessage({ - type: "suggestFix", - value: completion, - }); - break; - } - case "findSuspiciousCode": { - let traceback = getLanguageLibrary(".py").parseFirstStacktrace( - data.debugContext.traceback - ); - if (traceback === undefined) return; - vscode.commands.executeCommand( - "continue.findSuspiciousCode", - data.debugContext - ); - break; - } - case "queryEmbeddings": { - let { results } = await runPythonScript("index.py query", [ - data.query, - 2, - vscode.workspace.workspaceFolders?.[0].uri.fsPath, - ]); - panel.webview.postMessage({ - type: "queryEmbeddings", - results, - }); - break; - } case "openFile": { openEditorAndRevealRange(data.path, undefined, vscode.ViewColumn.One); break; @@ -351,20 +280,6 @@ export function setupDebugPanel( streamManager.closeStream(); break; } - case "explainCode": { - sendTelemetryEvent(TelemetryEvent.ExplainCode); - let debugContext: SerializedDebugContext = addFileSystemToDebugContext( - data.debugContext - ); - let resp = await debugApi.explainDebugExplainPost({ - serializedDebugContext: debugContext, - }); - panel.webview.postMessage({ - type: "explainCode", - value: resp.completion, - }); - break; - } case "withProgress": { // This message allows withProgress to be used in the webview if (data.done) { @@ -395,83 +310,6 @@ export function setupDebugPanel( ); break; } - case "makeEdit": { - sendTelemetryEvent(TelemetryEvent.SuggestFix); - let suggestedEdits = data.edits; - - if ( - typeof suggestedEdits === "undefined" || - suggestedEdits.length === 0 - ) { - vscode.window.showInformationMessage( - "Continue couldn't find a fix for this error." - ); - return; - } - - for (let i = 0; i < suggestedEdits.length; i++) { - let edit = suggestedEdits[i]; - await showSuggestion( - edit.filepath, - new vscode.Range( - edit.range.start.line, - edit.range.start.character, - edit.range.end.line, - edit.range.end.character - ), - edit.replacement - ); - } - break; - } - case "generateUnitTest": { - sendTelemetryEvent(TelemetryEvent.CreateTest); - vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: "Generating Unit Test...", - cancellable: false, - }, - async () => { - for (let i = 0; i < data.debugContext.rangesInFiles?.length; i++) { - let codeSelection = data.debugContext.rangesInFiles?.at(i); - if ( - codeSelection && - codeSelection.filepath && - codeSelection.range - ) { - try { - let filecontents = ( - await vscode.workspace.fs.readFile( - vscode.Uri.file(codeSelection.filepath) - ) - ).toString(); - let resp = - await unittestApi.failingtestUnittestFailingtestPost({ - failingTestBody: { - fp: { - filecontents, - lineno: codeSelection.range.end.line, - }, - description: data.debugContext.description || "", - }, - }); - - if (resp.completion) { - let decorationKey = await writeAndShowUnitTest( - codeSelection.filepath, - resp.completion - ); - break; - } - } catch {} - } - } - } - ); - - break; - } } }); diff --git a/extension/src/decorations.ts b/extension/src/decorations.ts index d2c94135..0587110c 100644 --- a/extension/src/decorations.ts +++ b/extension/src/decorations.ts @@ -1,7 +1,5 @@ import * as vscode from "vscode"; -import { getRightViewColumn, getTestFile } from "./util/vscode"; import * as path from "path"; -import { getLanguageLibrary } from "./languages"; export function showAnswerInTextEditor( filename: string, @@ -223,98 +221,3 @@ export function highlightCode( return key; } - -// Show unit test -const pythonImportDistinguisher = (line: string): boolean => { - if (line.startsWith("from") || line.startsWith("import")) { - return true; - } - return false; -}; -const javascriptImportDistinguisher = (line: string): boolean => { - if (line.startsWith("import")) { - return true; - } - return false; -}; -const importDistinguishersMap: { - [fileExtension: string]: (line: string) => boolean; -} = { - js: javascriptImportDistinguisher, - ts: javascriptImportDistinguisher, - py: pythonImportDistinguisher, -}; -function getImportsFromFileString( - fileString: string, - importDistinguisher: (line: string) => boolean -): Set<string> { - let importLines = new Set<string>(); - for (let line of fileString.split("\n")) { - if (importDistinguisher(line)) { - importLines.add(line); - } - } - return importLines; -} -function removeRedundantLinesFrom( - fileContents: string, - linesToRemove: Set<string> -): string { - let fileLines = fileContents.split("\n"); - fileLines = fileLines.filter((line: string) => { - return !linesToRemove.has(line); - }); - return fileLines.join("\n"); -} - -export async function writeAndShowUnitTest( - filename: string, - test: string -): Promise<DecorationKey> { - return new Promise((resolve, reject) => { - let testFilename = getTestFile(filename, true); - vscode.workspace.openTextDocument(testFilename).then((doc) => { - let fileContent = doc.getText(); - let fileEmpty = fileContent.trim() === ""; - let existingImportLines = getImportsFromFileString( - fileContent, - importDistinguishersMap[doc.fileName.split(".").at(-1) || ".py"] - ); - - // Remove redundant imports, make sure pytest is there - test = removeRedundantLinesFrom(test, existingImportLines); - test = - (fileEmpty - ? `${getLanguageLibrary(".py").writeImport( - testFilename, - filename - )}\nimport pytest\n\n` - : "\n\n") + - test.trim() + - "\n"; - - vscode.window - .showTextDocument(doc, getRightViewColumn()) - .then((editor) => { - let lastLine = editor.document.lineAt(editor.document.lineCount - 1); - let testRange = new vscode.Range( - lastLine.range.end, - new vscode.Position( - test.split("\n").length + lastLine.range.end.line, - 0 - ) - ); - editor - .edit((edit) => { - edit.insert(lastLine.range.end, test); - return true; - }) - .then((success) => { - if (!success) reject("Failed to insert test"); - let key = highlightCode(editor, testRange); - resolve(key); - }); - }); - }); - }); -} diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 88af0d19..de8f55e3 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -13,7 +13,7 @@ async function dynamicImportAndActivate( showTutorial: boolean ) { const { activateExtension } = await import("./activation/activate"); - activateExtension(context, showTutorial); + await activateExtension(context, showTutorial); } export function activate(context: vscode.ExtensionContext) { @@ -25,7 +25,6 @@ export function activate(context: vscode.ExtensionContext) { cancellable: false, }, async () => { - await startContinuePythonServer(); dynamicImportAndActivate(context, true); } ); diff --git a/extension/src/lang-server/codeLens.ts b/extension/src/lang-server/codeLens.ts index 2a362b62..26528d96 100644 --- a/extension/src/lang-server/codeLens.ts +++ b/extension/src/lang-server/codeLens.ts @@ -1,5 +1,4 @@ import * as vscode from "vscode"; -import { getLanguageLibrary } from "../languages"; import { editorToSuggestions } from "../suggestions"; class SuggestionsCodeLensProvider implements vscode.CodeLensProvider { @@ -52,40 +51,9 @@ class SuggestionsCodeLensProvider implements vscode.CodeLensProvider { } } -class PytestCodeLensProvider implements vscode.CodeLensProvider { - public provideCodeLenses( - document: vscode.TextDocument, - token: vscode.CancellationToken - ): vscode.CodeLens[] | Thenable<vscode.CodeLens[]> { - let codeLenses: vscode.CodeLens[] = []; - let lineno = 1; - let languageLibrary = getLanguageLibrary(document.fileName); - for (let line of document.getText().split("\n")) { - if ( - languageLibrary.lineIsFunctionDef(line) && - languageLibrary.parseFunctionDefForName(line).startsWith("test_") - ) { - let functionToTest = languageLibrary.parseFunctionDefForName(line); - let fileAndFunctionNameSpecifier = - document.fileName + "::" + functionToTest; - codeLenses.push( - new vscode.CodeLens(new vscode.Range(lineno, 0, lineno, 1), { - title: "Debug This Test", - command: "continue.debugTest", - arguments: [fileAndFunctionNameSpecifier], - }) - ); - } - lineno++; - } - - return codeLenses; - } -} - const allCodeLensProviders: { [langauge: string]: vscode.CodeLensProvider[] } = { - python: [new SuggestionsCodeLensProvider(), new PytestCodeLensProvider()], + python: [new SuggestionsCodeLensProvider()], }; export function registerAllCodeLensProviders(context: vscode.ExtensionContext) { diff --git a/extension/src/languages/index.d.ts b/extension/src/languages/index.d.ts deleted file mode 100644 index be7ddfbc..00000000 --- a/extension/src/languages/index.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -export interface LanguageLibrary { - language: string; - fileExtensions: string[]; - parseFirstStacktrace: (stdout: string) => string | undefined; - lineIsFunctionDef: (line: string) => boolean; - parseFunctionDefForName: (line: string) => string; - lineIsComment: (line: string) => boolean; - writeImport: ( - sourcePath: string, - pathToImport: string, - namesToImport?: string[] | undefined - ) => string; -} diff --git a/extension/src/languages/index.ts b/extension/src/languages/index.ts deleted file mode 100644 index 31d73a0b..00000000 --- a/extension/src/languages/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import pythonLanguageLibrary from "./python"; -import javascriptLanguageLibrary from "./javascript"; -import { LanguageLibrary } from "./index.d"; - -export const languageLibraries: LanguageLibrary[] = [ - pythonLanguageLibrary, - javascriptLanguageLibrary, -]; - -export function getLanguageLibrary(filepath: string): LanguageLibrary { - for (let languageLibrary of languageLibraries) { - for (let fileExtension of languageLibrary.fileExtensions) { - if (filepath.endsWith(fileExtension)) { - return languageLibrary; - } - } - } - throw new Error(`No language library found for file ${filepath}`); -} diff --git a/extension/src/languages/javascript/index.ts b/extension/src/languages/javascript/index.ts deleted file mode 100644 index 1c21a2fc..00000000 --- a/extension/src/languages/javascript/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { LanguageLibrary } from "../index.d"; -import { notImplemented } from "../notImplemented"; - -const NI = (propertyName: string) => notImplemented(propertyName, "javascript"); - -const javascriptLangaugeLibrary: LanguageLibrary = { - language: "javascript", - fileExtensions: [".js", ".jsx", ".ts", ".tsx"], - parseFirstStacktrace: NI("parseFirstStacktrace"), - lineIsFunctionDef: NI("lineIsFunctionDef"), - parseFunctionDefForName: NI("parseFunctionDefForName"), - lineIsComment: NI("lineIsComment"), - writeImport: NI("writeImport"), -}; - -export default javascriptLangaugeLibrary; diff --git a/extension/src/languages/notImplemented.ts b/extension/src/languages/notImplemented.ts deleted file mode 100644 index bbba2382..00000000 --- a/extension/src/languages/notImplemented.ts +++ /dev/null @@ -1,10 +0,0 @@ -export function notImplemented( - propertyName: string, - langauge: string -): (...args: any[]) => never { - return (...args: any[]) => { - throw new Error( - `Property ${propertyName} not implemented for language ${langauge}.` - ); - }; -} diff --git a/extension/src/languages/python/index.ts b/extension/src/languages/python/index.ts deleted file mode 100644 index 50282b45..00000000 --- a/extension/src/languages/python/index.ts +++ /dev/null @@ -1,74 +0,0 @@ -import path = require("path"); -import { LanguageLibrary } from "../index.d"; - -const tracebackStart = "Traceback (most recent call last):"; -const tracebackEnd = (buf: string): string | undefined => { - let lines = buf - .split("\n") - .filter((line: string) => line.trim() !== "~~^~~") - .filter((line: string) => line.trim() !== ""); - for (let i = 0; i < lines.length; i++) { - if ( - lines[i].startsWith(" File") && - i + 2 < lines.length && - lines[i + 2][0] !== " " - ) { - return lines.slice(0, i + 3).join("\n"); - } - } - return undefined; -}; - -function parseFirstStacktrace(stdout: string): string | undefined { - let startIdx = stdout.indexOf(tracebackStart); - if (startIdx < 0) return undefined; - stdout = stdout.substring(startIdx); - return tracebackEnd(stdout); -} - -function lineIsFunctionDef(line: string): boolean { - return line.startsWith("def "); -} - -function parseFunctionDefForName(line: string): string { - return line.split("def ")[1].split("(")[0]; -} - -function lineIsComment(line: string): boolean { - return line.trim().startsWith("#"); -} - -function writeImport( - sourcePath: string, - pathToImport: string, - namesToImport: string[] | undefined = undefined -): string { - let segs = path.relative(sourcePath, pathToImport).split(path.sep); - let importFrom = ""; - for (let seg of segs) { - if (seg === "..") { - importFrom = "." + importFrom; - } else { - if (!importFrom.endsWith(".")) { - importFrom += "."; - } - importFrom += seg.split(".").slice(0, -1).join("."); - } - } - - return `from ${importFrom} import ${ - namesToImport ? namesToImport.join(", ") : "*" - }`; -} - -const pythonLangaugeLibrary: LanguageLibrary = { - language: "python", - fileExtensions: [".py"], - parseFirstStacktrace, - lineIsFunctionDef, - parseFunctionDefForName, - lineIsComment, - writeImport, -}; - -export default pythonLangaugeLibrary; |