diff --git a/src/components/Nvim/NvimTree.tsx b/src/components/Nvim/NvimTree.tsx index 21eb43a..ec25961 100644 --- a/src/components/Nvim/NvimTree.tsx +++ b/src/components/Nvim/NvimTree.tsx @@ -1 +1,179 @@ -export const NvimTree = () =>
tree
; +import { useApp } from "~/hooks/useApp"; +import { CHAR_HEIGHT, CHAR_WIDTH } from "../Kitty"; +import { type ReactNode, useEffect, useState } from "react"; +import { type InnerKittyProps } from "~/utils/types"; +import { type Nvim } from "."; + +type FileIcon = { + char: string; + color: string; +}; + +type File = { + name: string; +} & ( + | { type: "file" } + | { type: "directory"; files: Array; folded: boolean } +); + +const FILE_ICONS: Record = { + md: { + char: " ", + color: "#89bafa", + }, + asc: { + char: "󰷖 ", + color: "#f9e2af", + }, + UNKNOWN: { char: "󰈚 ", color: "#f599ae" }, +}; + +const sortFiles = (files: Array) => + files + .sort((a, b) => a.name.localeCompare(b.name)) + .sort((a, b) => + a.type === "directory" && b.type !== "directory" + ? -1 + : a.type !== "directory" && b.type === "directory" + ? 1 + : 0, + ); + +export const NvimTree = (props: InnerKittyProps) => { + const { rootManifest, activeKitty } = useApp(); + + const [selected, setSelected] = useState(0); + const [files, setFiles] = useState>( + sortFiles([ + { + type: "directory", + name: "projects", + files: rootManifest.projects, + folded: false, + }, + ...rootManifest.files.map((name) => ({ type: "file" as const, name })), + ]), + ); + + const tree: Array = []; + let y = 0; + let selectedFile: File; + for (const fileOrDir of files) { + if (y === selected) selectedFile = fileOrDir; + if (fileOrDir.type === "directory") { + const dy = y; + tree.push( +
  • setSelected(dy)} + onDoubleClick={() => { + fileOrDir.folded = !fileOrDir.folded; + setFiles([...files]); + }} + > + {fileOrDir.folded ? ( + <> + {" "} + + ) : ( + <>  + )} + {fileOrDir.name} +
  • , + ); + if (!fileOrDir.folded) { + for (let i = 0; i < fileOrDir.files.length; i++) { + y++; + if (y === selected) + selectedFile = { type: "file", name: fileOrDir.files[i] }; + + const icon = FILE_ICONS.UNKNOWN; + const fy = y; + tree.push( +
  • setSelected(fy)} + > + {" "} + + {i === fileOrDir.files.length - 1 ? "└ " : "│ "} + + {`${icon.char}`} + {fileOrDir.files[i]} +
  • , + ); + } + } + } else { + const parts = fileOrDir.name.split("."); + let extension = parts[parts.length - 1]; + if (!FILE_ICONS[extension]) extension = "UNKNOWN"; + + const icon = FILE_ICONS[extension]; + + const fy = y; + tree.push( +
  • setSelected(fy)} + > + {" "} + {`${icon.char}`} + {fileOrDir.name === "README.md" ? ( + README.md + ) : ( + {fileOrDir.name} + )} +
  • , + ); + } + y++; + } + + useEffect(() => { + const onScroll = (event: KeyboardEvent) => { + if (activeKitty !== props.id) return; + switch (event.key) { + case "ArrowUp": + setSelected((x) => Math.max(0, x - 1)); + break; + + case "ArrowDown": + setSelected((x) => Math.min(y - 1, x + 1)); + break; + + case "Enter": + if (selectedFile.type === "directory") { + selectedFile.folded = !selectedFile.folded; + } else { + console.log(`navigate to ${selectedFile.name}`); + } + + setFiles([...files]); + break; + } + }; + + window.addEventListener("keydown", onScroll); + + return () => { + window.removeEventListener("keydown", onScroll); + }; + }); + + return ( +
    +
      + {tree} +
    +
    + ); +}; diff --git a/src/components/Nvim/index.tsx b/src/components/Nvim/index.tsx index 05ab8b0..5952687 100644 --- a/src/components/Nvim/index.tsx +++ b/src/components/Nvim/index.tsx @@ -1,28 +1,41 @@ +import { useKitty } from "~/hooks/useKitty"; import { CHAR_HEIGHT } from "../Kitty"; import { NvimEditor } from "./NvimEditor"; import { NvimInput } from "./NvimInput"; import { NvimStatusBar } from "./NvimStatusBar"; import { NvimTree } from "./NvimTree"; -export const Nvim = () => ( -
    -
    - +export const Nvim = (_props: {}) => { + const kitty = useKitty(); + + return ( +
    + {kitty && ( + <> +
    + +
    +
    + +
    +
    + +
    +
    + +
    + + )}
    -
    - -
    -
    - -
    -
    - -
    -
    -); + ); +};