import React, { useEffect, useState } from "react"; import styled from "styled-components"; import { Button, buttonColor, defaultBorderRadius, secondaryDark } from "."; import { useSelector } from "react-redux"; import { selectDebugContext, selectAllRangesInFiles, selectRangesMask, } from "../redux/selectors/debugContextSelectors"; import "../highlight/dark.min.css"; import hljs from "highlight.js"; import { postVscMessage } from "../vscode"; import { RootStore } from "../redux/store"; import { useDispatch } from "react-redux"; import { addRangeInFile, deleteRangeInFileAt, toggleSelectionAt, updateFileSystem, } from "../redux/slices/debugContexSlice"; import { RangeInFile } from "../../../src/client"; import { readRangeInVirtualFileSystem } from "../util"; //#region Styled Components const MultiSelectContainer = styled.div` border-radius: ${defaultBorderRadius}; padding: 4px; display: flex; flex-direction: column; gap: 4px; `; const MultiSelectHeader = styled.div` display: flex; justify-content: space-between; align-items: left; border-bottom: 1px solid gray; padding-left: 4px; padding-right: 4px; & p { overflow-wrap: break-word; word-wrap: break-word; -ms-wrap-flow: break-word; overflow: hidden; } `; const MultiSelectOption = styled.div` border-radius: ${defaultBorderRadius}; padding-top: 4px; cursor: pointer; background-color: ${secondaryDark}; `; const DeleteSelectedRangeButton = styled(Button)` align-self: right; padding: 0px; margin-top: 0; aspect-ratio: 1/1; height: 28px; `; const ToggleHighlightButton = styled(Button)` display: grid; justify-content: center; align-items: center; grid-template-columns: 30px 1fr; margin-left: 20px; order: 1; width: fit-content; `; //#endregion //#region Path Formatting const filenameToLanguageMap: any = { py: "python", js: "javascript", ts: "typescript", html: "html", css: "css", java: "java", c: "c", cpp: "cpp", cs: "csharp", go: "go", rb: "ruby", rs: "rust", swift: "swift", php: "php", scala: "scala", kt: "kotlin", dart: "dart", hs: "haskell", lua: "lua", pl: "perl", r: "r", sql: "sql", vb: "vb", xml: "xml", yaml: "yaml", }; function filenameToLanguage(filename: string): string { const extension = filename.split(".").pop(); if (extension === undefined) { return ""; } return filenameToLanguageMap[extension] || ""; } function formatPathRelativeToWorkspace( path: string, workspacePath: string | undefined ) { if (workspacePath === undefined) { return path; } if (path.startsWith(workspacePath)) { return path.substring(workspacePath.length + 1); } else { return path; } } function formatFileRange( rangeInFile: RangeInFile, workspacePath: string | undefined ) { return `${formatPathRelativeToWorkspace( rangeInFile.filepath, workspacePath )} (lines ${rangeInFile.range.start.line + 1}-${ rangeInFile.range.end.line + 1 })`; // +1 because VS Code Ranges are 0-indexed } //#endregion function CodeMultiselect(props: {}) { // State const [highlightLocked, setHighlightLocked] = useState(true); // Redux const dispatch = useDispatch(); const workspacePath = useSelector( (state: RootStore) => state.config.workspacePath ); const debugContext = useSelector(selectDebugContext); const rangesInFiles = useSelector(selectAllRangesInFiles); const rangesInFilesMask = useSelector(selectRangesMask); useEffect(() => { let eventListener = (event: any) => { switch (event.data.type) { case "highlightedCode": if (!highlightLocked) { dispatch( addRangeInFile({ rangeInFile: event.data.rangeInFile, canUpdateLast: true, }) ); dispatch(updateFileSystem(event.data.filesystem)); } break; case "findSuspiciousCode": for (let c of event.data.codeLocations) { dispatch(addRangeInFile({ rangeInFile: c, canUpdateLast: false })); } dispatch(updateFileSystem(event.data.filesystem)); postVscMessage("listTenThings", { debugContext }); break; } }; window.addEventListener("message", eventListener); return () => window.removeEventListener("message", eventListener); }, [debugContext, highlightLocked]); useEffect(() => { hljs.highlightAll(); }, [rangesInFiles]); return ( <MultiSelectContainer> {rangesInFiles.map((range: RangeInFile, index: number) => { return ( <MultiSelectOption key={index} style={{ border: `1px solid ${ rangesInFilesMask[index] ? buttonColor : "gray" }`, }} onClick={() => { dispatch(toggleSelectionAt(index)); }} > <MultiSelectHeader> <p style={{ margin: "4px" }}> {formatFileRange(range, workspacePath)} </p> <DeleteSelectedRangeButton onClick={() => dispatch(deleteRangeInFileAt(index))} > x </DeleteSelectedRangeButton> </MultiSelectHeader> <pre> <code className={"language-" + filenameToLanguage(range.filepath)} > {readRangeInVirtualFileSystem(range, debugContext.filesystem)} </code> </pre> </MultiSelectOption> ); })} {rangesInFiles.length === 0 && ( <> <p>Highlight relevant code in the editor.</p> </> )} <ToggleHighlightButton onClick={() => { setHighlightLocked(!highlightLocked); }} > {highlightLocked ? ( <> <svg xmlns="http://www.w3.org/2000/svg" width="20px" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6" > <path strokeLinecap="round" strokeLinejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z" /> </svg>{" "} Enable Highlight </> ) : ( <> <svg xmlns="http://www.w3.org/2000/svg" width="20px" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6" > <path strokeLinecap="round" strokeLinejoin="round" d="M13.5 10.5V6.75a4.5 4.5 0 119 0v3.75M3.75 21.75h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H3.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z" /> </svg>{" "} Disable Highlight </> )} </ToggleHighlightButton> </MultiSelectContainer> ); } export default CodeMultiselect;