diff options
Diffstat (limited to 'extension/react-app/src/components')
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} />;                  },                }}              > | 
