Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/code/src/main/services/fs/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const fileEntry = z.object({

export const listRepoFilesOutput = z.array(fileEntry);
export const readRepoFileOutput = z.string().nullable();
export const fileExistsOutput = z.boolean();

export type ListRepoFilesInput = z.infer<typeof listRepoFilesInput>;
export type ReadRepoFileInput = z.infer<typeof readRepoFileInput>;
Expand Down
9 changes: 9 additions & 0 deletions apps/code/src/main/services/fs/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ export class FsService {
}
}

async fileExists(filePath: string): Promise<boolean> {
try {
const stat = await fs.promises.stat(path.resolve(filePath));
return stat.isFile();
} catch {
return false;
}
}

async readAbsoluteFile(filePath: string): Promise<string | null> {
try {
return await fs.promises.readFile(path.resolve(filePath), "utf-8");
Expand Down
6 changes: 6 additions & 0 deletions apps/code/src/main/trpc/routers/fs.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { container } from "../../di/container";
import { MAIN_TOKENS } from "../../di/tokens";
import {
fileExistsOutput,
listRepoFilesInput,
listRepoFilesOutput,
readAbsoluteFileInput,
Expand Down Expand Up @@ -33,6 +34,11 @@ export const fsRouter = router({
.output(readRepoFileOutput)
.query(({ input }) => getService().readAbsoluteFile(input.filePath)),

fileExists: publicProcedure
.input(readAbsoluteFileInput)
.output(fileExistsOutput)
.query(({ input }) => getService().fileExists(input.filePath)),

readFileAsBase64: publicProcedure
.input(readAbsoluteFileInput)
.output(readRepoFileOutput)
Expand Down
2 changes: 1 addition & 1 deletion apps/code/src/main/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export function createWindow(): void {
...(savedState.y !== undefined && { y: savedState.y }),
width: savedState.width,
height: savedState.height,
minWidth: 1200,
minWidth: 480,
minHeight: 600,
backgroundColor: "#0a0a0a",
...platformWindowConfig,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export function InlineEditableText({
overflow: "auto",
maxHeight: "120px",
wordBreak: "break-word",
cursor: "text",
userSelect: active ? "auto" : "none",
pointerEvents: active ? "auto" : "none",
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,11 @@ export function AttachmentsBar({ attachments, onRemove }: AttachmentsBarProps) {
if (attachments.length === 0) return null;

return (
<Flex gap="1" align="center" className="mb-2 flex-wrap">
<Flex
gap="1"
align="center"
className="flex-wrap border-[var(--gray-a4)] border-b pr-2 pb-1.5"
>
{attachments.map((att) =>
isImageFile(att.label) ? (
<ImageThumbnail
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function ModeAndBranchRow({
<Flex
align="center"
justify="between"
pl="1"
className="pr-2"
style={{ overflow: "hidden" }}
>
<Flex align="center" gap="2" flexShrink="0">
Expand Down Expand Up @@ -270,19 +270,23 @@ export const MessageEditor = forwardRef<EditorHandle, MessageEditorProps>(
direction="column"
gap="2"
onClick={handleContainerClick}
className={`rounded-md p-2 ${isBashMode ? "ring-1 ring-blue-9" : ""}`}
className={`rounded-md py-2 pr-0 pl-1.5 ${isBashMode ? "ring-1 ring-blue-9" : ""}`}
style={{ cursor: "text" }}
>
<AttachmentsBar attachments={attachments} onRemove={removeAttachment} />

<div
className="max-h-[200px] min-h-[50px] flex-1 overflow-y-auto text-[15px]"
className="cli-editor-scroll max-h-[200px] min-h-[50px] flex-1 overflow-y-auto text-[15px]"
style={{ position: "relative" }}
>
<EditorContent editor={editor} />
</div>

<Flex justify="between" align="center" pl="1">
<Flex
justify="between"
align="center"
className="border-[var(--gray-a4)] border-t pt-1.5 pr-2"
>
<Flex gap="2" align="center">
<EditorToolbar
disabled={disabled}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,21 @@
.cli-editor [data-node-view-wrapper] {
display: inline;
}

/* Editor scrollbar - slim, transparent track, hugs right edge */
.cli-editor-scroll::-webkit-scrollbar {
width: 6px;
}

.cli-editor-scroll::-webkit-scrollbar-track {
background: transparent;
}

.cli-editor-scroll::-webkit-scrollbar-thumb {
background: var(--gray-a5);
border-radius: 3px;
}

.cli-editor-scroll::-webkit-scrollbar-thumb:hover {
background: var(--gray-a7);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import type { AvailableCommand } from "@agentclientprotocol/sdk";
import { CODE_COMMANDS } from "@features/message-editor/commands";
import { getAvailableCommandsForTask } from "@features/sessions/stores/sessionStore";
import { fetchRepoFiles, searchFiles } from "@hooks/useRepoFiles";
import {
fetchRepoFiles,
pathToFileItem,
searchFiles,
} from "@hooks/useRepoFiles";
import { trpcClient } from "@renderer/trpc/client";
import { isAbsolutePath } from "@utils/path";
import Fuse, { type IFuseOptions } from "fuse.js";
import { useDraftStore } from "../stores/draftStore";
import type { CommandSuggestionItem, FileSuggestionItem } from "../types";
Expand Down Expand Up @@ -39,26 +45,62 @@ function searchCommands(
return results.map((result) => result.item);
}

async function getAbsolutePathSuggestion(
query: string,
): Promise<FileSuggestionItem | null> {
if (!isAbsolutePath(query)) return null;

try {
const exists = await trpcClient.fs.fileExists.query({ filePath: query });
if (!exists) return null;
} catch {
return null;
}

const fileItem = pathToFileItem(query);
return {
id: query,
label: fileItem.name,
description: fileItem.dir || undefined,
filename: fileItem.name,
path: query,
};
}

export async function getFileSuggestions(
sessionId: string,
query: string,
): Promise<FileSuggestionItem[]> {
const repoPath = useDraftStore.getState().contexts[sessionId]?.repoPath;
const absolutePathSuggestion = getAbsolutePathSuggestion(query);

if (!repoPath) {
return [];
const resolved = await absolutePathSuggestion;
return resolved ? [resolved] : [];
}

const { files, fzf } = await fetchRepoFiles(repoPath);
const matched = searchFiles(fzf, files, query);

return matched.map((file) => ({
id: file.path,
label: file.name,
description: file.dir || undefined,
filename: file.name,
path: file.path,
}));
const results: FileSuggestionItem[] = matched.map((file) => {
const parentDir = file.dir ? file.dir.split("/").pop() : undefined;
const label = parentDir ? `${parentDir}/${file.name}` : file.name;

return {
id: file.path,
label,
description: file.dir || undefined,
filename: file.name,
path: file.path,
};
});

const resolved = await absolutePathSuggestion;
if (resolved && !results.some((r) => r.id === resolved.id)) {
results.unshift(resolved);
}

return results;
}

export function getCommandSuggestions(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ function createSuggestion(
render: () => {
let component: ReactRenderer<SuggestionListRef> | null = null;
let popup: TippyInstance | null = null;
let dismissed = false;

return {
onStart: (props) => {
dismissed = false;
component = new ReactRenderer(SuggestionList, {
props: {
items: props.items,
Expand Down Expand Up @@ -67,9 +69,12 @@ function createSuggestion(
if (props.event.key === "Escape") {
props.event.stopPropagation();
popup?.hide();
dismissed = true;
return true;
}

if (dismissed) return false;

return component?.ref?.onKeyDown(props) ?? false;
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ function createSuggestion(
render: () => {
let component: ReactRenderer<SuggestionListRef> | null = null;
let popup: TippyInstance | null = null;
let dismissed = false;

return {
onStart: (props) => {
dismissed = false;
const items = props.items.length > 0 ? props.items : lastItems;
component = new ReactRenderer(SuggestionList, {
props: {
Expand Down Expand Up @@ -73,9 +75,12 @@ function createSuggestion(
if (props.event.key === "Escape") {
props.event.stopPropagation();
popup?.hide();
dismissed = true;
return true;
}

if (dismissed) return false;

return component?.ref?.onKeyDown(props) ?? false;
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ function DefaultChip({

const isCommand = type === "command";
const prefix = isCommand ? "/" : "@";
const isFile = type === "file";

return (
const chip = (
<span
className={`${isCommand ? "cli-slash-command" : "cli-file-mention"} ${chipClass}`}
contentEditable={false}
Expand All @@ -45,6 +46,12 @@ function DefaultChip({
{label}
</span>
);

if (isFile) {
return <Tooltip content={id}>{chip}</Tooltip>;
}

return chip;
}

function PastedTextChip({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,11 @@ export const SuggestionList = forwardRef<
return true;
}
if (event.key === "Enter" || event.key === "Tab") {
if (items[selectedIndex]) command(items[selectedIndex]);
return true;
if (items[selectedIndex]) {
command(items[selectedIndex]);
return true;
}
return false;
}
return false;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export interface UseTiptapEditorOptions {
}

const EDITOR_CLASS =
"cli-editor min-h-[1.5em] w-full break-words border-none bg-transparent text-[13px] text-[var(--gray-12)] outline-none [overflow-wrap:break-word] [white-space:pre-wrap] [word-break:break-word]";
"cli-editor min-h-[1.5em] w-full break-words border-none bg-transparent pr-2 text-[13px] text-[var(--gray-12)] outline-none [overflow-wrap:break-word] [white-space:pre-wrap] [word-break:break-word]";

async function pasteTextAsFile(
view: EditorView,
Expand Down Expand Up @@ -187,9 +187,10 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) {

if (isSubmitKey) {
if (!view.editable || submitDisabledRef.current) return false;
const suggestionPopup =
document.querySelector("[data-tippy-root]");
if (suggestionPopup) return false;
const visibleSuggestion = document.querySelector(
"[data-tippy-root] .tippy-box:not([data-state='hidden'])",
);
if (visibleSuggestion) return false;
event.preventDefault();
historyActions.reset();
submitRef.current();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export function GeneratingIndicator({
<Brain size={12} className="ph-pulse" />
<Text size="1">{activity}...</Text>
<Text size="1" color="gray">
(Esc to interrupt
(Esc to stop
</Text>
<Circle
size={4}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,13 @@ export function SettingsDialog() {
style={{
height: "100%",
width: "100%",
maxWidth: "800px",
}}
>
<Box p="6" style={{ position: "relative", zIndex: 1 }}>
<Box
p="6"
mx="auto"
style={{ position: "relative", zIndex: 1, maxWidth: "800px" }}
>
<Flex direction="column" gap="4">
<Text size="4" weight="medium">
{CATEGORY_TITLES[activeCategory]}
Expand Down
Loading
Loading