diff options
Diffstat (limited to 'extension')
-rw-r--r-- | extension/package-lock.json | 4 | ||||
-rw-r--r-- | extension/package.json | 4 | ||||
-rw-r--r-- | extension/react-app/src/components/ComboBox.tsx | 3 | ||||
-rw-r--r-- | extension/react-app/src/components/DebugPanel.tsx | 85 | ||||
-rw-r--r-- | extension/react-app/src/components/HeaderButtonWithText.tsx | 30 | ||||
-rw-r--r-- | extension/react-app/src/components/StepContainer.tsx | 16 | ||||
-rw-r--r-- | extension/react-app/src/components/index.ts | 2 | ||||
-rw-r--r-- | extension/react-app/src/index.css | 4 | ||||
-rw-r--r-- | extension/react-app/src/tabs/gui.tsx | 206 | ||||
-rw-r--r-- | extension/scripts/continuedev-0.1.1-py3-none-any.whl | bin | 81620 -> 84291 bytes | |||
-rw-r--r-- | extension/src/activation/activate.ts | 38 | ||||
-rw-r--r-- | extension/src/continueIdeClient.ts | 12 | ||||
-rw-r--r-- | extension/src/terminal/terminalEmulator.ts | 140 | ||||
-rw-r--r-- | extension/src/util/lcs.ts | 30 |
14 files changed, 377 insertions, 197 deletions
diff --git a/extension/package-lock.json b/extension/package-lock.json index e41cd2c2..86c816e0 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.40", + "version": "0.0.47", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.40", + "version": "0.0.47", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 4b199420..56a522ac 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "Refine code 10x faster", - "version": "0.0.40", + "version": "0.0.47", "publisher": "Continue", "engines": { "vscode": "^1.74.0" @@ -96,7 +96,7 @@ { "type": "webview", "id": "continue.continueGUIView", - "name": ")", + "name": "GUI", "visibility": "visible" } ] diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index ace0605e..2b140567 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -113,6 +113,9 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { (event.nativeEvent as any).preventDownshiftDefault = true; if (props.onEnter) props.onEnter(event); setInputValue(""); + } else if (event.key === "Tab" && items.length > 0) { + setInputValue(items[0].name); + event.preventDefault(); } }, ref: ref as any, diff --git a/extension/react-app/src/components/DebugPanel.tsx b/extension/react-app/src/components/DebugPanel.tsx index 11ec2fe2..30f38779 100644 --- a/extension/react-app/src/components/DebugPanel.tsx +++ b/extension/react-app/src/components/DebugPanel.tsx @@ -17,39 +17,15 @@ interface DebugPanelProps { }[]; } -const GradientContainer = styled.div` - // Uncomment to get gradient border - /* background: linear-gradient( - 101.79deg, - #12887a 0%, - #87245c 37.64%, - #e12637 65.98%, - #ffb215 110.45% - ); */ - /* padding: 10px; */ - background-color: ${secondaryDark}; - margin: 0; - height: 100%; - /* border: 1px solid white; */ - border-radius: ${defaultBorderRadius}; -`; - -const MainDiv = styled.div` - height: 100%; - border-radius: ${defaultBorderRadius}; - scrollbar-base-color: transparent; - background-color: ${vscBackground}; -`; - const TabBar = styled.div<{ numTabs: number }>` display: grid; grid-template-columns: repeat(${(props) => props.numTabs}, 1fr); `; const TabsAndBodyDiv = styled.div` - display: grid; - grid-template-rows: auto 1fr; height: 100%; + border-radius: ${defaultBorderRadius}; + scrollbar-base-color: transparent; `; function DebugPanel(props: DebugPanelProps) { @@ -76,42 +52,43 @@ function DebugPanel(props: DebugPanelProps) { const [currentTab, setCurrentTab] = useState(0); return ( - <GradientContainer> - <MainDiv> - <TabsAndBodyDiv> - {props.tabs.length > 1 && ( - <TabBar numTabs={props.tabs.length}> - {props.tabs.map((tab, index) => { - return ( - <div - key={index} - className={`p-2 cursor-pointer text-center ${ - index === currentTab - ? "bg-secondary-dark" - : "bg-vsc-background" - }`} - onClick={() => setCurrentTab(index)} - > - {tab.title} - </div> - ); - })} - </TabBar> - )} + <TabsAndBodyDiv> + {props.tabs.length > 1 && ( + <TabBar numTabs={props.tabs.length}> {props.tabs.map((tab, index) => { return ( <div key={index} - hidden={index !== currentTab} - style={{ scrollbarGutter: "stable both-edges" }} + className={`p-2 cursor-pointer text-center ${ + index === currentTab + ? "bg-secondary-dark" + : "bg-vsc-background" + }`} + onClick={() => setCurrentTab(index)} > - {tab.element} + {tab.title} </div> ); })} - </TabsAndBodyDiv> - </MainDiv> - </GradientContainer> + </TabBar> + )} + {props.tabs.map((tab, index) => { + return ( + <div + key={index} + hidden={index !== currentTab} + style={{ + scrollbarGutter: "stable both-edges", + minHeight: "100%", + display: "grid", + gridTemplateRows: "1fr auto", + }} + > + {tab.element} + </div> + ); + })} + </TabsAndBodyDiv> ); } diff --git a/extension/react-app/src/components/HeaderButtonWithText.tsx b/extension/react-app/src/components/HeaderButtonWithText.tsx new file mode 100644 index 00000000..c4f22211 --- /dev/null +++ b/extension/react-app/src/components/HeaderButtonWithText.tsx @@ -0,0 +1,30 @@ +import React, { useState } from "react"; + +import { HeaderButton } from "."; + +interface HeaderButtonWithTextProps { + text: string; + onClick?: (e: any) => void; + children: React.ReactNode; +} + +const HeaderButtonWithText = (props: HeaderButtonWithTextProps) => { + const [hover, setHover] = useState(false); + return ( + <HeaderButton + style={{ padding: "3px" }} + onMouseEnter={() => setHover(true)} + onMouseLeave={() => { + setTimeout(() => { + setHover(false); + }, 100); + }} + onClick={props.onClick} + > + <span hidden={!hover}>{props.text}</span> + {props.children} + </HeaderButton> + ); +}; + +export default HeaderButtonWithText; diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index 48f970d7..480f517f 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -21,9 +21,7 @@ import { } from "@styled-icons/heroicons-outline"; import { HistoryNode } from "../../../schema/HistoryNode"; import ReactMarkdown from "react-markdown"; -import ContinueButton from "./ContinueButton"; -import InputAndButton from "./InputAndButton"; -import ToggleErrorDiv from "./ToggleErrorDiv"; +import HeaderButtonWithText from "./HeaderButtonWithText"; interface StepContainerProps { historyNode: HistoryNode; @@ -152,23 +150,25 @@ function StepContainer(props: StepContainerProps) { </HeaderButton> */} <> - <HeaderButton + <HeaderButtonWithText onClick={(e) => { e.stopPropagation(); props.onDelete(); }} + text="Delete" > <XMark size="1.6em" onClick={props.onDelete} /> - </HeaderButton> + </HeaderButtonWithText> {props.historyNode.observation?.error ? ( - <HeaderButton + <HeaderButtonWithText + text="Retry" onClick={(e) => { e.stopPropagation(); props.onRetry(); }} > <ArrowPath size="1.6em" onClick={props.onRetry} /> - </HeaderButton> + </HeaderButtonWithText> ) : ( <></> )} @@ -193,7 +193,7 @@ function StepContainer(props: StepContainerProps) { ) : ( <ReactMarkdown key={1} - className="overflow-scroll" + className="overflow-x-scroll" components={ { // pre: ({ node, ...props }) => { diff --git a/extension/react-app/src/components/index.ts b/extension/react-app/src/components/index.ts index 525989af..d99b4d96 100644 --- a/extension/react-app/src/components/index.ts +++ b/extension/react-app/src/components/index.ts @@ -48,7 +48,7 @@ export const Pre = styled.pre` max-height: 150px; overflow-y: scroll; margin: 0; - background-color: ${secondaryDark}; + background-color: ${vscBackground}; border: none; /* text wrapping */ diff --git a/extension/react-app/src/index.css b/extension/react-app/src/index.css index 20599d30..db8afab9 100644 --- a/extension/react-app/src/index.css +++ b/extension/react-app/src/index.css @@ -21,7 +21,8 @@ html, body, #root { - height: calc(100%); + height: 100%; + background-color: var(--vsc-background); } body { @@ -31,4 +32,5 @@ body { font-family: "Mona Sans", "Arial", sans-serif; padding: 0px; margin: 0px; + height: 100%; } diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx index 5316f42b..994cb896 100644 --- a/extension/react-app/src/tabs/gui.tsx +++ b/extension/react-app/src/tabs/gui.tsx @@ -14,25 +14,18 @@ import StepContainer from "../components/StepContainer"; import useContinueGUIProtocol from "../hooks/useWebsocket"; import { BookOpen, - ChatBubbleOvalLeft, ChatBubbleOvalLeftEllipsis, Trash, } from "@styled-icons/heroicons-outline"; import ComboBox from "../components/ComboBox"; import TextDialog from "../components/TextDialog"; +import HeaderButtonWithText from "../components/HeaderButtonWithText"; -const MainDiv = styled.div` - display: grid; - grid-template-rows: 1fr auto; +const TopGUIDiv = styled.div` + overflow: hidden; `; -let TopGUIDiv = styled.div` - display: grid; - grid-template-columns: 1fr; - background-color: ${vscBackground}; -`; - -let UserInputQueueItem = styled.div` +const UserInputQueueItem = styled.div` border-radius: ${defaultBorderRadius}; color: gray; padding: 8px; @@ -40,7 +33,7 @@ let UserInputQueueItem = styled.div` text-align: center; `; -const TopBar = styled.div` +const Footer = styled.footer` display: flex; flex-direction: row; gap: 8px; @@ -201,7 +194,7 @@ function GUI(props: GUIProps) { if (topGuiDivRef.current) { const timeout = setTimeout(() => { window.scrollTo({ - top: window.outerHeight, + top: topGuiDivRef.current!.offsetHeight, behavior: "smooth", }); }, 200); @@ -213,7 +206,9 @@ function GUI(props: GUIProps) { console.log("CLIENT ON STATE UPDATE: ", client, client?.onStateUpdate); client?.onStateUpdate((state) => { // Scroll only if user is at very bottom of the window. - const shouldScrollToBottom = window.outerHeight - window.scrollY < 200; + const shouldScrollToBottom = + topGuiDivRef.current && + topGuiDivRef.current?.offsetHeight - window.scrollY < 100; setWaitingForSteps(state.active); setHistory(state.history); setUserInputQueue(state.user_input_queue); @@ -303,103 +298,98 @@ function GUI(props: GUIProps) { setShowFeedbackDialog(false); }} ></TextDialog> - <MainDiv> - <TopGUIDiv - ref={topGuiDivRef} - onKeyDown={(e) => { - if (e.key === "Enter" && e.ctrlKey) { - onMainTextInput(); - } - }} - > - {typeof client === "undefined" && ( - <> - <Loader></Loader> - <p style={{ textAlign: "center" }}> - Trying to reconnect with server... - </p> - </> - )} - {history?.timeline.map((node: HistoryNode, index: number) => { - return ( - <StepContainer - key={index} - onUserInput={(input: string) => { - onStepUserInput(input, index); - }} - inFuture={index > history?.current_index} - historyNode={node} - onRefinement={(input: string) => { - client?.sendRefinementInput(input, index); - }} - onReverse={() => { - client?.reverseToIndex(index); - }} - onRetry={() => { - client?.retryAtIndex(index); - setWaitingForSteps(true); - }} - onDelete={() => { - client?.deleteAtIndex(index); - }} - /> - ); - })} - {waitingForSteps && <Loader></Loader>} - <div> - {userInputQueue.map((input) => { - return <UserInputQueueItem>{input}</UserInputQueueItem>; - })} - </div> - - <ComboBox - disabled={ - history?.timeline.length - ? history.timeline[history.current_index].step.name === - "Waiting for user confirmation" - : false - } - ref={mainTextInputRef} - onEnter={(e) => { - onMainTextInput(); - e.stopPropagation(); - e.preventDefault(); - }} - onInputValueChange={() => {}} - items={availableSlashCommands} - /> - <ContinueButton onClick={onMainTextInput} /> - - <TopBar> - <a href="https://continue.dev/docs" className="no-underline"> - <HeaderButton style={{ padding: "3px" }}> - Continue Docs - <BookOpen size="1.6em" /> - </HeaderButton> - </a> - <HeaderButton - style={{ padding: "3px" }} - onClick={() => { - // Set dialog open - setShowFeedbackDialog(true); + <TopGUIDiv + ref={topGuiDivRef} + onKeyDown={(e) => { + if (e.key === "Enter" && e.ctrlKey) { + onMainTextInput(); + } + }} + > + {typeof client === "undefined" && ( + <> + <Loader></Loader> + <p style={{ textAlign: "center" }}> + Trying to reconnect with server... + </p> + </> + )} + {history?.timeline.map((node: HistoryNode, index: number) => { + return ( + <StepContainer + key={index} + onUserInput={(input: string) => { + onStepUserInput(input, index); + }} + inFuture={index > history?.current_index} + historyNode={node} + onRefinement={(input: string) => { + client?.sendRefinementInput(input, index); + }} + onReverse={() => { + client?.reverseToIndex(index); }} - > - Feedback - <ChatBubbleOvalLeftEllipsis size="1.6em" /> - </HeaderButton> - <HeaderButton - onClick={() => { - client?.sendClear(); + onRetry={() => { + client?.retryAtIndex(index); + setWaitingForSteps(true); }} - style={{ padding: "3px" }} - > - Clear History - <Trash size="1.6em" /> - </HeaderButton> - </TopBar> - </TopGUIDiv> - </MainDiv> + onDelete={() => { + client?.deleteAtIndex(index); + }} + /> + ); + })} + {waitingForSteps && <Loader></Loader>} + + <div> + {userInputQueue.map((input) => { + return <UserInputQueueItem>{input}</UserInputQueueItem>; + })} + </div> + + <ComboBox + // disabled={ + // history?.timeline.length + // ? history.timeline[history.current_index].step.name === + // "Waiting for user confirmation" + // : false + // } + ref={mainTextInputRef} + onEnter={(e) => { + onMainTextInput(); + e.stopPropagation(); + e.preventDefault(); + }} + onInputValueChange={() => {}} + items={availableSlashCommands} + /> + <ContinueButton onClick={onMainTextInput} /> + </TopGUIDiv> + <Footer> + <HeaderButtonWithText + onClick={() => { + client?.sendClear(); + }} + text="Clear History" + > + <Trash size="1.6em" /> + </HeaderButtonWithText> + <a href="https://continue.dev/docs" className="no-underline"> + <HeaderButtonWithText text="Continue Docs"> + <BookOpen size="1.6em" /> + </HeaderButtonWithText> + </a> + <HeaderButtonWithText + onClick={() => { + // Set dialog open + setShowFeedbackDialog(true); + }} + text="Feedback" + > + <ChatBubbleOvalLeftEllipsis size="1.6em" /> + </HeaderButtonWithText> + </Footer> </> ); } 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 differindex 2f8f1550..b0b84230 100644 --- a/extension/scripts/continuedev-0.1.1-py3-none-any.whl +++ b/extension/scripts/continuedev-0.1.1-py3-none-any.whl diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts index 135a8ec7..32726c86 100644 --- a/extension/src/activation/activate.ts +++ b/extension/src/activation/activate.ts @@ -8,6 +8,7 @@ import * as path from "path"; import IdeProtocolClient from "../continueIdeClient"; import { getContinueServerUrl } from "../bridge"; import { setupDebugPanel, ContinueGUIWebviewViewProvider } from "../debugPanel"; +import { CapturedTerminal } from "../terminal/terminalEmulator"; export let extensionContext: vscode.ExtensionContext | undefined = undefined; @@ -47,5 +48,42 @@ 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; + } + }); + + // 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; } diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index ef9a91c8..9a93a4ef 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -323,16 +323,22 @@ class IdeProtocolClient { return rangeInFiles; } - private continueTerminal: CapturedTerminal | undefined; + public continueTerminal: CapturedTerminal | undefined; async runCommand(command: string) { - if (!this.continueTerminal) { - this.continueTerminal = new CapturedTerminal("Continue"); + if (!this.continueTerminal || this.continueTerminal.isClosed()) { + this.continueTerminal = new CapturedTerminal({ + name: "Continue", + }); } this.continueTerminal.show(); return await this.continueTerminal.runCommand(command); } + + sendCommandOutput(output: string) { + this.messenger?.send("commandOutput", { output }); + } } export default IdeProtocolClient; diff --git a/extension/src/terminal/terminalEmulator.ts b/extension/src/terminal/terminalEmulator.ts index b3031baf..8e49737e 100644 --- a/extension/src/terminal/terminalEmulator.ts +++ b/extension/src/terminal/terminalEmulator.ts @@ -3,6 +3,7 @@ import * as vscode from "vscode"; import os = require("os"); import stripAnsi from "strip-ansi"; +import { longestCommonSubsequence } from "../util/lcs"; function loadNativeModule<T>(id: string): T | null { try { @@ -62,21 +63,38 @@ export class CapturedTerminal { this.terminal.show(); } + isClosed(): boolean { + return this.terminal.exitStatus !== undefined; + } + private commandQueue: [string, (output: string) => void][] = []; private hasRunCommand: boolean = false; + private dataEndsInPrompt(strippedData: string): boolean { + const lines = strippedData.split("\n"); + const lastLine = lines[lines.length - 1]; + + return ( + lines.length > 0 && + (((lastLine.includes("bash-") || lastLine.includes(") $ ")) && + lastLine.includes("$")) || + (lastLine.includes("]> ") && lastLine.includes(") [")) || + (lastLine.includes(" (") && lastLine.includes(")>")) || + (typeof this.commandPromptString !== "undefined" && + (lastLine.includes(this.commandPromptString) || + this.commandPromptString.length - + longestCommonSubsequence(lastLine, this.commandPromptString) + .length < + 3))) + ); + } + private async waitForCommandToFinish() { return new Promise<string>((resolve, reject) => { this.onDataListeners.push((data: any) => { const strippedData = stripAnsi(data); this.dataBuffer += strippedData; - const lines = this.dataBuffer.split("\n"); - if ( - lines.length > 0 && - (lines[lines.length - 1].includes("bash-") || - lines[lines.length - 1].includes(") $ ")) && - lines[lines.length - 1].includes("$") - ) { + if (this.dataEndsInPrompt(strippedData)) { resolve(this.dataBuffer); this.dataBuffer = ""; this.onDataListeners = []; @@ -86,12 +104,6 @@ export class CapturedTerminal { } async runCommand(command: string): Promise<string> { - if (!this.hasRunCommand) { - this.hasRunCommand = true; - // Let the first bash- prompt appear and let python env be opened - await this.waitForCommandToFinish(); - } - if (this.commandQueue.length === 0) { return new Promise(async (resolve, reject) => { this.commandQueue.push([command, resolve]); @@ -99,8 +111,12 @@ export class CapturedTerminal { while (this.commandQueue.length > 0) { const [command, resolve] = this.commandQueue.shift()!; + // Refresh the command prompt string every time in case it changes + await this.refreshCommandPromptString(); + this.terminal.sendText(command); - resolve(await this.waitForCommandToFinish()); + const output = await this.waitForCommandToFinish(); + resolve(output); } }); } else { @@ -112,8 +128,48 @@ export class CapturedTerminal { private readonly writeEmitter: vscode.EventEmitter<string>; - constructor(terminalName: string) { - this.shellCmd = "bash"; // getDefaultShell(); + private splitByCommandsBuffer: string = ""; + private readonly onCommandOutput: ((output: string) => void) | undefined; + + splitByCommandsListener(data: string) { + // Split the output by commands so it can be sent to Continue Server + + const strippedData = stripAnsi(data); + this.splitByCommandsBuffer += data; + if (this.dataEndsInPrompt(strippedData)) { + if (this.onCommandOutput) { + this.onCommandOutput(stripAnsi(this.splitByCommandsBuffer)); + } + this.splitByCommandsBuffer = ""; + } + } + + private runningClearToGetPrompt: boolean = false; + private seenClear: boolean = false; + private commandPromptString: string | undefined = undefined; + private resolveMeWhenCommandPromptStringFound: + | ((_: unknown) => void) + | undefined = undefined; + + private async refreshCommandPromptString(): Promise<string | undefined> { + // Sends a message that will be received by the terminal to get the command prompt string, see the onData method below in constructor. + this.runningClearToGetPrompt = true; + this.terminal.sendText("echo"); + const promise = new Promise((resolve, reject) => { + this.resolveMeWhenCommandPromptStringFound = resolve; + }); + await promise; + return this.commandPromptString; + } + + constructor( + options: { name: string } & Partial<vscode.ExtensionTerminalOptions>, + onCommandOutput?: (output: string) => void + ) { + this.onCommandOutput = onCommandOutput; + + // this.shellCmd = "bash"; // getDefaultShell(); + this.shellCmd = getDefaultShell(); const env = { ...(process.env as any) }; if (os.platform() !== "win32") { @@ -123,7 +179,7 @@ export class CapturedTerminal { // Create the pseudo terminal this.ptyProcess = pty.spawn(this.shellCmd, [], { name: "xterm-256color", - cols: 160, // TODO: Get size of vscode terminal, and change with resize + cols: 250, // No way to get the size of VS Code terminal, or listen to resize, so make it just bigger than most conceivable VS Code widths rows: 26, cwd: getRootDir(), env, @@ -133,9 +189,57 @@ export class CapturedTerminal { this.writeEmitter = new vscode.EventEmitter<string>(); this.ptyProcess.onData((data: any) => { + if (this.runningClearToGetPrompt) { + if ( + stripAnsi(data) + .split("\n") + .flatMap((line) => line.split("\r")) + .find((line) => line.trim() === "echo") !== undefined + ) { + this.seenClear = true; + return; + } else if (this.seenClear) { + const strippedLines = stripAnsi(data) + .split("\r") + .filter( + (line) => + line.trim().length > 0 && + line.trim() !== "%" && + line.trim() !== "⏎" + ); + const lastLine = strippedLines[strippedLines.length - 1] || ""; + const lines = lastLine + .split("\n") + .filter( + (line) => + line.trim().length > 0 && + line.trim() !== "%" && + line.trim() !== "⏎" + ); + const commandPromptString = (lines[lines.length - 1] || "").trim(); + if ( + commandPromptString.length > 0 && + !commandPromptString.includes("echo") + ) { + this.runningClearToGetPrompt = false; + this.seenClear = false; + this.commandPromptString = commandPromptString; + console.log( + "Found command prompt string: " + this.commandPromptString + ); + if (this.resolveMeWhenCommandPromptStringFound) { + this.resolveMeWhenCommandPromptStringFound(undefined); + } + } + return; + } + } + // Pass data through to terminal + data = data.replace("⏎", ""); this.writeEmitter.fire(data); + this.splitByCommandsListener(data); for (let listener of this.onDataListeners) { listener(data); } @@ -154,7 +258,7 @@ export class CapturedTerminal { // Create and clear the terminal this.terminal = vscode.window.createTerminal({ - name: terminalName, + ...options, pty: newPty, }); this.terminal.show(); diff --git a/extension/src/util/lcs.ts b/extension/src/util/lcs.ts new file mode 100644 index 00000000..17ea63f9 --- /dev/null +++ b/extension/src/util/lcs.ts @@ -0,0 +1,30 @@ +export function longestCommonSubsequence(a: string, b: string) { + const lengths: number[][] = []; + for (let i = 0; i <= a.length; i++) { + lengths[i] = []; + for (let j = 0; j <= b.length; j++) { + if (i === 0 || j === 0) { + lengths[i][j] = 0; + } else if (a[i - 1] === b[j - 1]) { + lengths[i][j] = lengths[i - 1][j - 1] + 1; + } else { + lengths[i][j] = Math.max(lengths[i - 1][j], lengths[i][j - 1]); + } + } + } + let result = ""; + let x = a.length; + let y = b.length; + while (x !== 0 && y !== 0) { + if (lengths[x][y] === lengths[x - 1][y]) { + x--; + } else if (lengths[x][y] === lengths[x][y - 1]) { + y--; + } else { + result = a[x - 1] + result; + x--; + y--; + } + } + return result; +} |