fix(ui): fix ./ path prefix for SHIFT+ENTER
This commit is contained in:
@@ -204,13 +204,16 @@ export function usePromptPicker(options: PromptPickerOptions): PromptPickerContr
|
|||||||
}
|
}
|
||||||
|
|
||||||
const folderMention =
|
const folderMention =
|
||||||
relativePath === "." || relativePath === ""
|
relativePath === "." || relativePath === "" || relativePath === "./"
|
||||||
? "/"
|
? "./"
|
||||||
: relativePath.replace(/\/+$/, "") + "/"
|
: (relativePath.startsWith("./") ? relativePath.replace(/\/+$/, "") + "/" : relativePath.replace(/^\.\//, "").replace(/\/+$/, "") + "/")
|
||||||
|
|
||||||
const normalizedFolderPath = (() => {
|
const normalizedFolderPath = (() => {
|
||||||
const trimmed = relativePath.replace(/\/+$/, "")
|
const trimmed = relativePath.replace(/\/+$/, "")
|
||||||
return trimmed.length > 0 ? trimmed : "."
|
// If it's root "./", just return "./"
|
||||||
|
if (trimmed === "" || trimmed === ".") return "./"
|
||||||
|
// Otherwise remove any leading ./ and add ./ prefix
|
||||||
|
return "./" + trimmed.replace(/^\.\//, "")
|
||||||
})()
|
})()
|
||||||
|
|
||||||
const addPathOnlyAttachment = (value: string) => {
|
const addPathOnlyAttachment = (value: string) => {
|
||||||
@@ -237,12 +240,13 @@ export function usePromptPicker(options: PromptPickerOptions): PromptPickerContr
|
|||||||
|
|
||||||
if (action === "shiftEnter") {
|
if (action === "shiftEnter") {
|
||||||
// SHIFT+ENTER on directory: keep @path in prompt, add text attachment, remove @ when sending
|
// SHIFT+ENTER on directory: keep @path in prompt, add text attachment, remove @ when sending
|
||||||
addPathOnlyAttachment(folderMention)
|
// Always prefix with ./ for consistency
|
||||||
|
const normalizedFolderPathWithPrefix = normalizedFolderPath.startsWith("./") ? normalizedFolderPath : "./" + normalizedFolderPath
|
||||||
|
addPathOnlyAttachment(normalizedFolderPathWithPrefix)
|
||||||
replaceMentionToken(mentionText, { trailingSpace: true })
|
replaceMentionToken(mentionText, { trailingSpace: true })
|
||||||
} else {
|
} else {
|
||||||
// ENTER/click on directory: attach as a file part pointing at a file:// directory URL.
|
// ENTER/click on directory: attach as a file part pointing at a file:// directory URL.
|
||||||
const dirLabel =
|
const dirLabel = normalizedFolderPath === "./" ? "./" : normalizedFolderPath.split("/").pop() || normalizedFolderPath
|
||||||
normalizedFolderPath === "." ? "/" : normalizedFolderPath.split("/").pop() || normalizedFolderPath
|
|
||||||
const dirFilename = dirLabel.endsWith("/") ? dirLabel : `${dirLabel}/`
|
const dirFilename = dirLabel.endsWith("/") ? dirLabel : `${dirLabel}/`
|
||||||
|
|
||||||
const existingAttachments = getAttachments(options.instanceId(), options.sessionId())
|
const existingAttachments = getAttachments(options.instanceId(), options.sessionId())
|
||||||
@@ -275,10 +279,14 @@ export function usePromptPicker(options: PromptPickerOptions): PromptPickerContr
|
|||||||
|
|
||||||
if (action === "shiftEnter") {
|
if (action === "shiftEnter") {
|
||||||
// SHIFT+ENTER on file: keep @path in prompt, add text attachment, remove @ when sending
|
// SHIFT+ENTER on file: keep @path in prompt, add text attachment, remove @ when sending
|
||||||
addPathOnlyAttachment(normalizedPath)
|
// Always prefix with ./ for consistency
|
||||||
replaceMentionToken(`@${normalizedPath}`, { trailingSpace: true })
|
const normalizedPathWithPrefix = normalizedPath.startsWith("./") ? normalizedPath : "./" + normalizedPath
|
||||||
|
addPathOnlyAttachment(normalizedPathWithPrefix)
|
||||||
|
replaceMentionToken(`@${normalizedPathWithPrefix}`, { trailingSpace: true })
|
||||||
} else {
|
} else {
|
||||||
// ENTER/click on file: attach file (existing behavior).
|
// ENTER/click on file: attach file (existing behavior).
|
||||||
|
// Always prefix with ./ for consistency
|
||||||
|
const normalizedPathWithPrefix = normalizedPath.startsWith("./") ? normalizedPath : "./" + normalizedPath
|
||||||
const pathSegments = normalizedPath.split("/")
|
const pathSegments = normalizedPath.split("/")
|
||||||
const filename = (() => {
|
const filename = (() => {
|
||||||
const candidate = pathSegments[pathSegments.length - 1] || normalizedPath
|
const candidate = pathSegments[pathSegments.length - 1] || normalizedPath
|
||||||
@@ -287,12 +295,12 @@ export function usePromptPicker(options: PromptPickerOptions): PromptPickerContr
|
|||||||
|
|
||||||
const existingAttachments = getAttachments(options.instanceId(), options.sessionId())
|
const existingAttachments = getAttachments(options.instanceId(), options.sessionId())
|
||||||
const alreadyAttached = existingAttachments.some(
|
const alreadyAttached = existingAttachments.some(
|
||||||
(att) => att.source.type === "file" && att.source.path === normalizedPath,
|
(att) => att.source.type === "file" && att.source.path === normalizedPathWithPrefix,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!alreadyAttached) {
|
if (!alreadyAttached) {
|
||||||
const attachment = createFileAttachment(
|
const attachment = createFileAttachment(
|
||||||
normalizedPath,
|
normalizedPathWithPrefix,
|
||||||
filename,
|
filename,
|
||||||
"text/plain",
|
"text/plain",
|
||||||
undefined,
|
undefined,
|
||||||
@@ -301,7 +309,7 @@ export function usePromptPicker(options: PromptPickerOptions): PromptPickerContr
|
|||||||
addAttachment(options.instanceId(), options.sessionId(), attachment)
|
addAttachment(options.instanceId(), options.sessionId(), attachment)
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceMentionToken(`@${normalizedPath}`, { trailingSpace: true })
|
replaceMentionToken(`@${normalizedPathWithPrefix}`, { trailingSpace: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,9 +51,7 @@ function normalizeQuery(rawQuery: string) {
|
|||||||
if (!trimmed) {
|
if (!trimmed) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
if (trimmed === "." || trimmed === "./") {
|
// Don't normalize "." - it's used for workspace root
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return trimmed.replace(/^(\.\/)+/, "").replace(/^\/+/, "")
|
return trimmed.replace(/^(\.\/)+/, "").replace(/^\/+/, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,18 +348,22 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
|
|||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add root directory as first item when query is "/"
|
// Add root directory as first item only when query is EXACTLY "." or "./" (not "./docs/")
|
||||||
if (mode() === "mention" && props.searchQuery === "/") {
|
const isExactRootQuery = props.searchQuery === "." || props.searchQuery === "./"
|
||||||
|
if (mode() === "mention" && isExactRootQuery) {
|
||||||
const rootFile: FileItem = {
|
const rootFile: FileItem = {
|
||||||
path: "/",
|
path: ".",
|
||||||
relativePath: "/",
|
relativePath: ".",
|
||||||
isDirectory: true,
|
isDirectory: true,
|
||||||
isGitFile: false,
|
isGitFile: false,
|
||||||
}
|
}
|
||||||
items.push({ type: "file", file: rootFile })
|
items.push({ type: "file", file: rootFile })
|
||||||
}
|
}
|
||||||
|
|
||||||
filteredAgents().forEach((agent) => items.push({ type: "agent", agent }))
|
// Don't show agents for exact root path queries
|
||||||
|
if (!isExactRootQuery) {
|
||||||
|
filteredAgents().forEach((agent) => items.push({ type: "agent", agent }))
|
||||||
|
}
|
||||||
files().forEach((file) => items.push({ type: "file", file }))
|
files().forEach((file) => items.push({ type: "file", file }))
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
@@ -485,7 +487,7 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
|
|||||||
</For>
|
</For>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={mode() === "mention" && agentCount() > 0}>
|
<Show when={mode() === "mention" && agentCount() > 0 && !(props.searchQuery === "." || props.searchQuery === "./")}>
|
||||||
<div class="dropdown-section-header">
|
<div class="dropdown-section-header">
|
||||||
{t("unifiedPicker.sections.agents")}
|
{t("unifiedPicker.sections.agents")}
|
||||||
</div>
|
</div>
|
||||||
@@ -540,11 +542,11 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
|
|||||||
</For>
|
</For>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={mode() === "mention" && fileCount() > 0}>
|
<Show when={mode() === "mention" && (fileCount() > 0 || props.searchQuery === "." || props.searchQuery === "./")}>
|
||||||
<div class="dropdown-section-header">
|
<div class="dropdown-section-header">
|
||||||
{props.searchQuery === "/" ? t("unifiedPicker.sections.directories") : t("unifiedPicker.sections.files")}
|
{t("unifiedPicker.sections.files")}
|
||||||
</div>
|
</div>
|
||||||
<Show when={props.searchQuery === "/"}>
|
<Show when={props.searchQuery === "." || props.searchQuery === "./"}>
|
||||||
<div
|
<div
|
||||||
class={`dropdown-item py-1.5 ${
|
class={`dropdown-item py-1.5 ${
|
||||||
selectedIndex() === 0 ? "dropdown-item-highlight" : ""
|
selectedIndex() === 0 ? "dropdown-item-highlight" : ""
|
||||||
@@ -552,8 +554,8 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
|
|||||||
data-picker-selected={selectedIndex() === 0}
|
data-picker-selected={selectedIndex() === 0}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const rootFile: FileItem = {
|
const rootFile: FileItem = {
|
||||||
path: "/",
|
path: ".",
|
||||||
relativePath: "/",
|
relativePath: ".",
|
||||||
isDirectory: true,
|
isDirectory: true,
|
||||||
isGitFile: false,
|
isGitFile: false,
|
||||||
}
|
}
|
||||||
@@ -569,7 +571,7 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
|
|||||||
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
|
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="font-mono">/ (root)</span>
|
<span class="font-mono">. (workspace root)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -23,15 +23,52 @@ export function resolvePastedPlaceholders(prompt: string, attachments: Attachmen
|
|||||||
let result = prompt
|
let result = prompt
|
||||||
|
|
||||||
// For each path attachment (SHIFT+ENTER), find and replace @path with path in the prompt
|
// For each path attachment (SHIFT+ENTER), find and replace @path with path in the prompt
|
||||||
// We ALWAYS strip @ for SHIFT+ENTER paths, even if there's also a file attachment
|
|
||||||
for (const path of pathAttachments) {
|
for (const path of pathAttachments) {
|
||||||
// Try both with and without trailing slash
|
if (!path) continue
|
||||||
const variants = [path, path + "/"]
|
|
||||||
|
// The path should already have ./ prefix from usePromptPicker
|
||||||
|
// We need to find @path in prompt and replace with path
|
||||||
|
|
||||||
|
// For "./docs/" path, try to match @docs/, @./docs/, @docs, etc.
|
||||||
|
const basePath = path.replace(/^\.\//, "").replace(/\/+$/, "") // "docs"
|
||||||
|
const withSlash = basePath + "/" // "docs/"
|
||||||
|
|
||||||
|
const patterns = [
|
||||||
|
"@" + path, // @./docs/
|
||||||
|
"@" + basePath, // @docs
|
||||||
|
"@" + withSlash, // @docs/
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const pattern of patterns) {
|
||||||
|
if (result.includes(pattern)) {
|
||||||
|
result = result.replace(pattern, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const variant of variants) {
|
// Also strip @ for paths that have file attachments (ENTER case)
|
||||||
// Replace @path with path (exact match)
|
for (const filePath of fileAttachments) {
|
||||||
const searchPattern = "@" + variant
|
if (!filePath || filePath.length === 0) continue
|
||||||
result = result.split(searchPattern).join(variant)
|
|
||||||
|
// Special case: if attachment is "./" or ".", handle separately
|
||||||
|
if (filePath === "./" || filePath === ".") {
|
||||||
|
result = result.replace("@./", "./")
|
||||||
|
result = result.replace("@.", "./")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal path handling
|
||||||
|
const pathToFind = filePath.replace(/^\.\//, "")
|
||||||
|
const patterns = [
|
||||||
|
"@" + filePath,
|
||||||
|
"@./" + pathToFind,
|
||||||
|
"@" + pathToFind,
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const pattern of patterns) {
|
||||||
|
if (result.includes(pattern)) {
|
||||||
|
result = result.replace(pattern, filePath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user