summaryrefslogtreecommitdiff
path: root/extension/react-app/src/components
diff options
context:
space:
mode:
authorNate Sesti <33237525+sestinj@users.noreply.github.com>2023-07-02 20:15:49 -0700
committerGitHub <noreply@github.com>2023-07-02 20:15:49 -0700
commitcf3d08ceaabecd80b20aaac24c5c166cd8e6472f (patch)
tree7559fe0d9500d5156fa99ee5614ac0fe6d4e88f3 /extension/react-app/src/components
parent0ffd2648d679916872c681036a68741a83d80c0e (diff)
parent0a812994703791023125177fe4820202374e45b0 (diff)
downloadsncontinue-cf3d08ceaabecd80b20aaac24c5c166cd8e6472f.tar.gz
sncontinue-cf3d08ceaabecd80b20aaac24c5c166cd8e6472f.tar.bz2
sncontinue-cf3d08ceaabecd80b20aaac24c5c166cd8e6472f.zip
Merge pull request #170 from continuedev/explicit-context
Explicit context
Diffstat (limited to 'extension/react-app/src/components')
-rw-r--r--extension/react-app/src/components/CodeBlock.tsx73
-rw-r--r--extension/react-app/src/components/ComboBox.tsx261
-rw-r--r--extension/react-app/src/components/ContinueButton.tsx1
-rw-r--r--extension/react-app/src/components/IterationContainer.tsx77
-rw-r--r--extension/react-app/src/components/PillButton.tsx66
-rw-r--r--extension/react-app/src/components/StepContainer.tsx8
6 files changed, 286 insertions, 200 deletions
diff --git a/extension/react-app/src/components/CodeBlock.tsx b/extension/react-app/src/components/CodeBlock.tsx
index 1624b986..17f5626b 100644
--- a/extension/react-app/src/components/CodeBlock.tsx
+++ b/extension/react-app/src/components/CodeBlock.tsx
@@ -61,7 +61,7 @@ function CopyButton(props: { textToCopy: string; visible: boolean }) {
);
}
-function CodeBlock(props: { children: React.ReactNode }) {
+function CodeBlock(props: { children: string; showCopy?: boolean }) {
const [result, setResult] = useState<AutoHighlightResult | undefined>(
undefined
);
@@ -78,39 +78,36 @@ function CodeBlock(props: { children: React.ReactNode }) {
setHighlightTimeout(
setTimeout(() => {
- const result = hljs.highlightAuto(
- (props.children as any).props.children[0],
- [
- "python",
- "javascript",
- "typescript",
- "bash",
- "html",
- "css",
- "json",
- "yaml",
- "markdown",
- "sql",
- "java",
- "c",
- "cpp",
- "csharp",
- "go",
- "kotlin",
- "php",
- "ruby",
- "rust",
- "scala",
- "swift",
- "dart",
- "haskell",
- "perl",
- "r",
- "julia",
- "objectivec",
- "ocaml",
- ]
- );
+ const result = hljs.highlightAuto(props.children, [
+ "python",
+ "javascript",
+ "typescript",
+ "bash",
+ "html",
+ "css",
+ "json",
+ "yaml",
+ "markdown",
+ "sql",
+ "java",
+ "c",
+ "cpp",
+ "csharp",
+ "go",
+ "kotlin",
+ "php",
+ "ruby",
+ "rust",
+ "scala",
+ "swift",
+ "dart",
+ "haskell",
+ "perl",
+ "r",
+ "julia",
+ "objectivec",
+ "ocaml",
+ ]);
setResult(result);
setHighlightTimeout(undefined);
}, 100)
@@ -129,13 +126,11 @@ function CodeBlock(props: { children: React.ReactNode }) {
>
<CopyButtonDiv>
<CopyButton
- visible={hovered}
- textToCopy={(props.children as any).props.children[0]}
+ visible={hovered && (props.showCopy || true)}
+ textToCopy={props.children}
/>
</CopyButtonDiv>
- <StyledCode language={result?.language}>
- {(props.children as any).props.children[0]}
- </StyledCode>
+ <StyledCode language={result?.language}>{props.children}</StyledCode>
</StyledPre>
);
}
diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index 3816cee8..f299c3a2 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback } from "react";
+import React, { useCallback, useEffect } from "react";
import { useCombobox } from "downshift";
import styled from "styled-components";
import {
@@ -7,8 +7,27 @@ import {
secondaryDark,
vscBackground,
} from ".";
+import CodeBlock from "./CodeBlock";
+import { RangeInFile } from "../../../src/client";
+import PillButton from "./PillButton";
const mainInputFontSize = 16;
+
+const ContextDropdown = styled.div`
+ position: absolute;
+ padding: 4px;
+ width: calc(100% - 16px - 8px);
+ background-color: ${secondaryDark};
+ color: white;
+ border-bottom-right-radius: ${defaultBorderRadius};
+ border-bottom-left-radius: ${defaultBorderRadius};
+ /* border: 1px solid white; */
+ border-top: none;
+ margin: 8px;
+ outline: 1px solid orange;
+ z-index: 5;
+`;
+
const MainTextInput = styled.textarea`
resize: none;
@@ -20,6 +39,7 @@ const MainTextInput = styled.textarea`
width: 100%;
background-color: ${vscBackground};
color: white;
+ z-index: 1;
&:focus {
border: 1px solid transparent;
@@ -49,6 +69,7 @@ const Ul = styled.ul<{
border-radius: ${defaultBorderRadius};
overflow: hidden;
border: 0.5px solid gray;
+ z-index: 2;
`;
const Li = styled.li<{
@@ -71,6 +92,8 @@ interface ComboBoxProps {
onInputValueChange: (inputValue: string) => void;
disabled?: boolean;
onEnter?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
+ highlightedCodeSections?: (RangeInFile & { contents: string })[];
+ deleteContextItem?: (idx: number) => void;
}
const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
@@ -78,6 +101,26 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
// The position of the current command you are typing now, so the one that will be appended to history once you press enter
const [positionInHistory, setPositionInHistory] = React.useState<number>(0);
const [items, setItems] = React.useState(props.items);
+ const [hoveringButton, setHoveringButton] = React.useState(false);
+ const [hoveringContextDropdown, setHoveringContextDropdown] =
+ React.useState(false);
+ const [highlightedCodeSections, setHighlightedCodeSections] = React.useState(
+ props.highlightedCodeSections || [
+ {
+ filepath: "test.ts",
+ range: {
+ start: { line: 0, character: 0 },
+ end: { line: 0, character: 0 },
+ },
+ contents: "import * as a from 'a';",
+ },
+ ]
+ );
+
+ useEffect(() => {
+ setHighlightedCodeSections(props.highlightedCodeSections || []);
+ }, [props.highlightedCodeSections]);
+
const {
isOpen,
getToggleButtonProps,
@@ -111,90 +154,144 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
};
return (
- <div className="flex px-2" ref={divRef} hidden={!isOpen}>
- <MainTextInput
- 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.
- (event.nativeEvent as any).preventDownshiftDefault = true;
- } else if (
- event.key === "Enter" &&
- (!isOpen || items.length === 0)
- ) {
- // Prevent Downshift's default 'Enter' behavior.
- (event.nativeEvent as any).preventDownshiftDefault = true;
- if (props.onEnter) props.onEnter(event);
- setInputValue("");
- const value = event.currentTarget.value;
- if (value !== "") {
- setPositionInHistory(history.length + 1);
- setHistory([...history, value]);
+ <>
+ <div className="flex px-2" ref={divRef} hidden={!isOpen}>
+ <MainTextInput
+ 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`;
+
+ // setShowContextDropdown(target.value.endsWith("@"));
+ },
+ onKeyDown: (event) => {
+ if (event.key === "Enter" && event.shiftKey) {
+ // Prevent Downshift's default 'Enter' behavior.
+ (event.nativeEvent as any).preventDownshiftDefault = true;
+ } else if (
+ event.key === "Enter" &&
+ (!isOpen || items.length === 0)
+ ) {
+ // Prevent Downshift's default 'Enter' behavior.
+ (event.nativeEvent as any).preventDownshiftDefault = true;
+ if (props.onEnter) props.onEnter(event);
+ setInputValue("");
+ const value = event.currentTarget.value;
+ if (value !== "") {
+ setPositionInHistory(history.length + 1);
+ setHistory([...history, value]);
+ }
+ } else if (event.key === "Tab" && items.length > 0) {
+ setInputValue(items[0].name);
+ event.preventDefault();
+ } 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" &&
+ event.currentTarget.value.split("\n").length > 1
+ ) {
+ if (positionInHistory < history.length - 1) {
+ setInputValue(history[positionInHistory + 1]);
+ }
+ setPositionInHistory((prev) =>
+ Math.min(prev + 1, history.length)
+ );
+ }
+ },
+ ref: ref as any,
+ })}
+ />
+ <Ul
+ {...getMenuProps({
+ ref: ulRef,
+ })}
+ showAbove={showAbove()}
+ ulHeightPixels={ulRef.current?.getBoundingClientRect().height || 0}
+ >
+ {isOpen &&
+ items.map((item, index) => (
+ <Li
+ key={`${item.name}${index}`}
+ {...getItemProps({ item, index })}
+ highlighted={highlightedIndex === index}
+ selected={selectedItem === item}
+ >
+ <span>
+ {item.name}: {item.description}
+ </span>
+ </Li>
+ ))}
+ </Ul>
+ </div>
+ <div className="px-2 flex gap-2 items-center flex-wrap">
+ {highlightedCodeSections.map((section, idx) => (
+ <PillButton
+ title={section.filepath}
+ onDelete={() => {
+ if (props.deleteContextItem) {
+ props.deleteContextItem(idx);
}
- } else if (event.key === "Tab" && items.length > 0) {
- setInputValue(items[0].name);
- event.preventDefault();
- } 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" &&
- event.currentTarget.value.split("\n").length > 1
- ) {
- if (positionInHistory < history.length - 1) {
- setInputValue(history[positionInHistory + 1]);
+ setHighlightedCodeSections((prev) => {
+ const newSections = [...prev];
+ newSections.splice(idx, 1);
+ return newSections;
+ });
+ }}
+ onHover={(val: boolean) => {
+ if (val) {
+ setHoveringButton(val);
+ } else {
+ setTimeout(() => {
+ setHoveringButton(val);
+ }, 100);
}
- setPositionInHistory((prev) =>
- Math.min(prev + 1, history.length)
- );
- }
- },
- ref: ref as any,
- })}
- />
- <Ul
- {...getMenuProps({
- ref: ulRef,
- })}
- showAbove={showAbove()}
- ulHeightPixels={ulRef.current?.getBoundingClientRect().height || 0}
+ }}
+ />
+ ))}
+
+ <span className="text-trueGray-400 ml-auto mr-4 text-xs">
+ Highlight code to include as context.{" "}
+ {highlightedCodeSections.length > 0 &&
+ "Otherwise using entire currently open file."}
+ </span>
+ </div>
+ <ContextDropdown
+ onMouseEnter={() => {
+ setHoveringContextDropdown(true);
+ }}
+ onMouseLeave={() => {
+ setHoveringContextDropdown(false);
+ }}
+ hidden={!hoveringContextDropdown && !hoveringButton}
>
- {isOpen &&
- items.map((item, index) => (
- <Li
- key={`${item.name}${index}`}
- {...getItemProps({ item, index })}
- highlighted={highlightedIndex === index}
- selected={selectedItem === item}
- >
- <span>
- {item.name}: {item.description}
- </span>
- </Li>
- ))}
- </Ul>
- </div>
+ {highlightedCodeSections.map((section, idx) => (
+ <>
+ <p>{section.filepath}</p>
+ <CodeBlock showCopy={false} key={idx}>
+ {section.contents}
+ </CodeBlock>
+ </>
+ ))}
+ </ContextDropdown>
+ </>
);
});
diff --git a/extension/react-app/src/components/ContinueButton.tsx b/extension/react-app/src/components/ContinueButton.tsx
index ef6719b7..5295799a 100644
--- a/extension/react-app/src/components/ContinueButton.tsx
+++ b/extension/react-app/src/components/ContinueButton.tsx
@@ -6,6 +6,7 @@ import { RootStore } from "../redux/store";
let StyledButton = styled(Button)`
margin: auto;
+ margin-top: 8px;
display: grid;
grid-template-columns: 30px 1fr;
align-items: center;
diff --git a/extension/react-app/src/components/IterationContainer.tsx b/extension/react-app/src/components/IterationContainer.tsx
deleted file mode 100644
index a0053519..00000000
--- a/extension/react-app/src/components/IterationContainer.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import { useState } from "react";
-import styled from "styled-components";
-import {
- defaultBorderRadius,
- MainContainerWithBorder,
- secondaryDark,
- vscBackground,
-} from ".";
-import { RangeInFile, FileEdit } from "../../../src/client";
-import CodeBlock from "./CodeBlock";
-import SubContainer from "./SubContainer";
-
-import { ChevronDown, ChevronRight } from "@styled-icons/heroicons-outline";
-
-export interface IterationContext {
- codeSelections: RangeInFile[];
- instruction: string;
- suggestedChanges: FileEdit[];
- status: "waiting" | "accepted" | "rejected";
- summary?: string;
- action: string;
- error?: string;
-}
-
-interface IterationContainerProps {
- iterationContext: IterationContext;
-}
-
-const IterationContainerDiv = styled.div<{ open: boolean }>`
- background-color: ${(props) => (props.open ? vscBackground : secondaryDark)};
- border-radius: ${defaultBorderRadius};
- padding: ${(props) => (props.open ? "2px" : "8px")};
-`;
-
-function IterationContainer(props: IterationContainerProps) {
- const [open, setOpen] = useState(false);
-
- return (
- <MainContainerWithBorder className="m-2 overflow-hidden">
- <IterationContainerDiv open={open}>
- <p
- className="m-2 cursor-pointer"
- onClick={() => setOpen((prev) => !prev)}
- >
- {open ? <ChevronDown size="1.4em" /> : <ChevronRight size="1.4em" />}
- {props.iterationContext.summary ||
- props.iterationContext.codeSelections
- .map((cs) => cs.filepath)
- .join("\n")}
- </p>
-
- {open && (
- <>
- <SubContainer title="Action">
- {props.iterationContext.action}
- </SubContainer>
- {props.iterationContext.error && (
- <SubContainer title="Error">
- <CodeBlock>{props.iterationContext.error}</CodeBlock>
- </SubContainer>
- )}
- {props.iterationContext.suggestedChanges.map((sc) => {
- return (
- <SubContainer title="Suggested Change">
- {sc.filepath}
- <CodeBlock>{sc.replacement}</CodeBlock>
- </SubContainer>
- );
- })}
- </>
- )}
- </IterationContainerDiv>
- </MainContainerWithBorder>
- );
-}
-
-export default IterationContainer;
diff --git a/extension/react-app/src/components/PillButton.tsx b/extension/react-app/src/components/PillButton.tsx
new file mode 100644
index 00000000..33451db5
--- /dev/null
+++ b/extension/react-app/src/components/PillButton.tsx
@@ -0,0 +1,66 @@
+import { useState } from "react";
+import styled from "styled-components";
+import { defaultBorderRadius } from ".";
+
+const Button = styled.button`
+ border: none;
+ color: white;
+ background-color: transparent;
+ border: 1px solid white;
+ border-radius: ${defaultBorderRadius};
+ padding: 3px 6px;
+
+ &:hover {
+ background-color: white;
+ color: black;
+ }
+`;
+
+interface PillButtonProps {
+ onHover?: (arg0: boolean) => void;
+ onDelete?: () => void;
+ title: string;
+}
+
+const PillButton = (props: PillButtonProps) => {
+ const [isHovered, setIsHovered] = useState(false);
+ return (
+ <Button
+ onMouseEnter={() => {
+ setIsHovered(true);
+ if (props.onHover) {
+ props.onHover(true);
+ }
+ }}
+ onMouseLeave={() => {
+ setIsHovered(false);
+ if (props.onHover) {
+ props.onHover(false);
+ }
+ }}
+ >
+ <div
+ style={{ display: "grid", gridTemplateColumns: "1fr auto", gap: "4px" }}
+ >
+ <span>{props.title}</span>
+ <span
+ style={{
+ cursor: "pointer",
+ color: "red",
+ borderLeft: "1px solid black",
+ paddingLeft: "4px",
+ }}
+ hidden={!isHovered}
+ onClick={() => {
+ props.onDelete?.();
+ props.onHover?.(false);
+ }}
+ >
+ X
+ </span>
+ </div>
+ </Button>
+ );
+};
+
+export default PillButton;
diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx
index 02c04548..cb83f20a 100644
--- a/extension/react-app/src/components/StepContainer.tsx
+++ b/extension/react-app/src/components/StepContainer.tsx
@@ -246,10 +246,14 @@ function StepContainer(props: StepContainerProps) {
className="overflow-x-scroll"
components={{
pre: ({ node, ...props }) => {
- return <CodeBlock children={props.children[0]} />;
+ return (
+ <CodeBlock
+ children={(props.children[0] as any).props.children[0]}
+ />
+ );
},
code: ({ node, ...props }) => {
- return <StyledCode children={props.children[0]} />;
+ return <StyledCode children={props.children[0] as any} />;
},
}}
>