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 (
+
+ );
+};
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 && (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
-
-
-
-
-
-
-
-
-
-
-);
+ );
+};