Skip to content

Commit b08c46c

Browse files
committed
feat(textarea): hide unnecessary information
1 parent a93fb5a commit b08c46c

File tree

7 files changed

+361
-106
lines changed

7 files changed

+361
-106
lines changed

webview-ui/src/components/chat/ChatLexicalTextArea.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,6 @@ export const ChatLexicalTextArea = forwardRef<LexicalEditor, ChatTextAreaProps>(
174174
const [validMentions, setValidMentions] = useState<MentionInfo[]>([])
175175

176176
const handleMentionUpdate = useCallback((mentions: MentionInfo[]) => {
177-
console.log({ mentions })
178177
setValidMentions(mentions)
179178
}, [])
180179

@@ -646,12 +645,10 @@ export const ChatLexicalTextArea = forwardRef<LexicalEditor, ChatTextAreaProps>(
646645
"min-h-[94px]",
647646
"box-border",
648647
"resize-none",
649-
"overflow-x-hidden",
650-
"overflow-y-auto",
648+
"overflow-x-hidden overflow-y-auto",
651649
"flex-none flex-grow",
652650
"z-[2]",
653-
"scrollbar-none",
654-
"scrollbar-hide",
651+
"scrollbar-none scrollbar-hide",
655652
isFocused
656653
? "border border-vscode-focusBorder outline outline-vscode-focusBorder"
657654
: isDraggingOver

webview-ui/src/components/chat/lexical/LexicalMentionPlugin.tsx

Lines changed: 8 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { getIconForFilePath, getIconUrlByName } from "vscode-material-icons"
1717

1818
import { $createMentionNode, $isMentionNode } from "./MentionNode"
1919
import { shouldShowContextMenu, ContextMenuOptionType } from "@/utils/context-mentions"
20-
import { removeLeadingNonAlphanumeric } from "@/utils/removeLeadingNonAlphanumeric"
20+
import { getDisplayNameForPath } from "@/utils/path-mentions"
2121

2222
export interface MentionInfo {
2323
path: string
@@ -43,33 +43,6 @@ export const LexicalMentionPlugin = forwardRef<LexicalMentionPluginRef, LexicalM
4343
const [editor] = useLexicalComposerContext()
4444
const [isShowingMentions, setIsShowingMentions] = useState(false)
4545

46-
// Helper function to get display name with conflict resolution
47-
const getDisplayName = useCallback((mention: string, allMentions: string[]) => {
48-
// Remove leading non-alphanumeric and trailing slash
49-
const path = removeLeadingNonAlphanumeric(mention).replace(/\/$/, "")
50-
const pathList = path.split("/")
51-
const filename = pathList.at(-1) || mention
52-
53-
// Check if there are other mentions with the same filename
54-
const sameFilenames = allMentions.filter((m) => {
55-
const otherPath = removeLeadingNonAlphanumeric(m).replace(/\/$/, "")
56-
const otherFilename = otherPath.split("/").at(-1) || m
57-
return otherFilename === filename && m !== mention
58-
})
59-
60-
if (sameFilenames.length === 0) {
61-
return filename // No conflicts, just show filename
62-
}
63-
64-
// There are conflicts, need to show directory to disambiguate
65-
if (pathList.length > 1) {
66-
// Show filename with first directory
67-
return `${pathList[pathList.length - 2]}/${filename}`
68-
}
69-
70-
return filename
71-
}, [])
72-
7346
// Helper function to get material icon for mention
7447
const getMaterialIconForMention = useCallback(
7548
(mention: string, type?: "file" | "folder") => {
@@ -105,7 +78,7 @@ export const LexicalMentionPlugin = forwardRef<LexicalMentionPluginRef, LexicalM
10578

10679
if ($isMentionNode(node) && node.getTrigger() === "@") {
10780
mentionNodes.push({
108-
path: node.getMentionName(),
81+
path: node.getValue(),
10982
data: node.getData(),
11083
})
11184
}
@@ -161,16 +134,16 @@ export const LexicalMentionPlugin = forwardRef<LexicalMentionPluginRef, LexicalM
161134
icon = linkIconSvg // You can change this to a different icon if needed
162135
} else if (storedType === ContextMenuOptionType.File) {
163136
type = "file"
164-
displayName = getDisplayName(node.path, mentionPaths)
137+
displayName = getDisplayNameForPath(node.path, mentionPaths)
165138
icon = getMaterialIconForMention(node.path, "file")
166139
} else if (storedType === ContextMenuOptionType.Folder) {
167140
type = "folder"
168-
displayName = getDisplayName(node.path, mentionPaths)
141+
displayName = getDisplayNameForPath(node.path, mentionPaths)
169142
icon = getMaterialIconForMention(node.path, "folder")
170143
} else {
171144
// Fall back to path-based detection for backward compatibility
172145
type = isFolder(node.path) ? "folder" : "file"
173-
displayName = getDisplayName(node.path, mentionPaths)
146+
displayName = getDisplayNameForPath(node.path, mentionPaths)
174147
icon = getMaterialIconForMention(node.path, type)
175148
}
176149

@@ -183,7 +156,7 @@ export const LexicalMentionPlugin = forwardRef<LexicalMentionPluginRef, LexicalM
183156
})
184157

185158
return mentions
186-
}, [editor, getDisplayName, getMaterialIconForMention, isFolder])
159+
}, [editor, getMaterialIconForMention, isFolder])
187160

188161
const updateMentions = useCallback(() => {
189162
const currentMentions = extractMentionsFromEditor()
@@ -277,7 +250,7 @@ export const LexicalMentionPlugin = forwardRef<LexicalMentionPluginRef, LexicalM
277250

278251
// Create mention node with type information
279252
const data = type ? { type } : undefined
280-
const mentionNode = $createMentionNode(mentionText, trigger, undefined, data)
253+
const mentionNode = $createMentionNode(trigger, mentionText, null, undefined, data)
281254

282255
if (beforeText) {
283256
anchorNode.setTextContent(beforeText)
@@ -317,7 +290,7 @@ export const LexicalMentionPlugin = forwardRef<LexicalMentionPluginRef, LexicalM
317290

318291
if ($isMentionNode(node) && node.getTrigger() === "@") {
319292
// Check if this is the mention we want to remove
320-
if (node.getMentionName() === mentionInfo.path) {
293+
if (node.getValue() === mentionInfo.path) {
321294
// Remove the mention node and any trailing space
322295
const nextSibling = node.getNextSibling()
323296
if ($isTextNode(nextSibling) && nextSibling.getTextContent().startsWith(" ")) {

webview-ui/src/components/chat/lexical/LexicalPastePlugin.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ export const LexicalPastePlugin = ({
101101
const beforeText = currentText.slice(0, atIndex)
102102
const afterText = currentText.slice(currentOffset)
103103

104-
const mentionNode = $createMentionNode(text.trim(), "@", undefined, {
104+
const url = text.trim()
105+
const mentionNode = $createMentionNode("@", url, null, undefined, {
105106
type: ContextMenuOptionType.URL,
106107
})
107108

@@ -247,9 +248,13 @@ function parseTextIntoNodes(text: string): Array<TextNode> {
247248
}
248249

249250
const data = contextType ? { type: contextType } : undefined
250-
nodes.push($createMentionNode(item.mentionText, "@", undefined, data))
251+
nodes.push($createMentionNode("@", mentionText, null, undefined, data))
251252
} else if (item.type === "command") {
252-
nodes.push($createMentionNode(item.mentionText, "/", undefined, { type: ContextMenuOptionType.Command }))
253+
nodes.push(
254+
$createMentionNode("/", item.mentionText, null, undefined, {
255+
type: ContextMenuOptionType.Command,
256+
}),
257+
)
253258
}
254259

255260
lastIndex = item.index + item.match.length

webview-ui/src/components/chat/lexical/MentionNode.spec.ts

Lines changed: 98 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,66 +3,148 @@ import { $createMentionNode, $isMentionNode, MentionNode } from "./MentionNode"
33

44
describe("MentionNode", () => {
55
it("should create a mention node with correct properties", () => {
6-
const mentionNode = $createMentionNode("test-file.ts", "@")
6+
const mentionNode = $createMentionNode("@", "test-file.ts", "test-file.ts")
77

88
expect($isMentionNode(mentionNode)).toBe(true)
9-
expect(mentionNode.getMentionName()).toBe("test-file.ts")
9+
expect(mentionNode.getValue()).toBe("test-file.ts")
10+
expect(mentionNode.getLabel()).toBe("test-file.ts")
1011
expect(mentionNode.getTrigger()).toBe("@")
1112
expect(mentionNode.getTextContent()).toBe("@test-file.ts")
1213
})
1314

1415
it("should create a command mention node", () => {
15-
const mentionNode = $createMentionNode("code", "/")
16+
const mentionNode = $createMentionNode("/", "code", "code")
1617

1718
expect($isMentionNode(mentionNode)).toBe(true)
18-
expect(mentionNode.getMentionName()).toBe("code")
19+
expect(mentionNode.getValue()).toBe("code")
20+
expect(mentionNode.getLabel()).toBe("code")
1921
expect(mentionNode.getTrigger()).toBe("/")
2022
expect(mentionNode.getTextContent()).toBe("/code")
2123
})
2224

2325
it("should serialize and deserialize correctly", () => {
24-
const mentionNode = $createMentionNode("test-file.ts", "@", undefined, { id: "123" })
26+
const mentionNode = $createMentionNode("@", "test-file.ts", "test-file.ts", undefined, { id: "123" })
2527
const serialized = mentionNode.exportJSON()
2628

27-
expect(serialized.mentionName).toBe("test-file.ts")
29+
expect(serialized.value).toBe("test-file.ts")
30+
expect(serialized.label).toBe("test-file.ts")
2831
expect(serialized.trigger).toBe("@")
2932
expect(serialized.data).toEqual({ id: "123" })
3033
expect(serialized.type).toBe("mention")
3134

3235
const deserialized = MentionNode.importJSON(serialized)
33-
expect(deserialized.getMentionName()).toBe("test-file.ts")
36+
expect(deserialized.getValue()).toBe("test-file.ts")
37+
expect(deserialized.getLabel()).toBe("test-file.ts")
3438
expect(deserialized.getTrigger()).toBe("@")
3539
expect(deserialized.getData()).toEqual({ id: "123" })
3640
})
3741

3842
it("should clone correctly", () => {
39-
const original = $createMentionNode("test-file.ts", "@", undefined, { id: "123" })
43+
const original = $createMentionNode("@", "test-file.ts", "test-file.ts", undefined, { id: "123" })
4044
const cloned = MentionNode.clone(original)
4145

42-
expect(cloned.getMentionName()).toBe(original.getMentionName())
46+
expect(cloned.getValue()).toBe(original.getValue())
47+
expect(cloned.getLabel()).toBe(original.getLabel())
4348
expect(cloned.getTrigger()).toBe(original.getTrigger())
4449
expect(cloned.getData()).toEqual(original.getData())
4550
expect(cloned.getTextContent()).toBe(original.getTextContent())
4651
})
4752

48-
it("should update mention name correctly", () => {
49-
const mentionNode = $createMentionNode("old-file.ts", "@")
50-
const updated = mentionNode.setMentionName("new-file.ts")
53+
it("should update value and label correctly", () => {
54+
const mentionNode = $createMentionNode("@", "old-file.ts", "old-file.ts")
5155

52-
expect(updated.getMentionName()).toBe("new-file.ts")
53-
expect(updated.getTextContent()).toBe("@new-file.ts")
56+
const withNewValue = mentionNode.setValue("new-file.ts")
57+
expect(withNewValue.getValue()).toBe("new-file.ts")
58+
59+
const withNewLabel = withNewValue.setLabel("new-file.ts")
60+
expect(withNewLabel.getLabel()).toBe("new-file.ts")
61+
expect(withNewLabel.getTextContent()).toBe("@new-file.ts")
5462
})
5563

5664
it("should not allow text insertion before or after", () => {
57-
const mentionNode = $createMentionNode("test-file.ts", "@")
65+
const mentionNode = $createMentionNode("@", "test-file.ts", "test-file.ts")
5866

5967
expect(mentionNode.canInsertTextBefore()).toBe(false)
6068
expect(mentionNode.canInsertTextAfter()).toBe(false)
6169
})
6270

6371
it("should be a text entity", () => {
64-
const mentionNode = $createMentionNode("test-file.ts", "@")
72+
const mentionNode = $createMentionNode("@", "test-file.ts", "test-file.ts")
6573

6674
expect(mentionNode.isTextEntity()).toBe(true)
6775
})
76+
77+
it("should support value and label", () => {
78+
const mentionNode = $createMentionNode("@", "/path/to/filename.ts", "filename.ts", undefined, { type: "file" })
79+
80+
expect(mentionNode.getValue()).toBe("/path/to/filename.ts")
81+
expect(mentionNode.getLabel()).toBe("filename.ts")
82+
})
83+
84+
it("should format file mentions with @file: prefix", () => {
85+
const mentionNode = $createMentionNode("@", "/path/to/file.ts", "file.ts", undefined, { type: "file" })
86+
87+
expect(mentionNode.getFormattedDisplayText()).toBe("@file:file.ts")
88+
})
89+
90+
it("should format directory mentions with @dir: prefix", () => {
91+
const mentionNode = $createMentionNode("@", "/path/to/dir/", "/path/to/dir/", undefined, { type: "folder" })
92+
93+
expect(mentionNode.getFormattedDisplayText()).toBe("@dir:/path/to/dir/")
94+
})
95+
96+
it("should format git mentions with @git: prefix", () => {
97+
const mentionNode = $createMentionNode("@", "abc123def456", "abc123", undefined, { type: "git" })
98+
99+
expect(mentionNode.getFormattedDisplayText()).toBe("@git:abc123")
100+
})
101+
102+
it("should format url mentions with @url: prefix", () => {
103+
const mentionNode = $createMentionNode("@", "https://example.com", "example.com", undefined, { type: "url" })
104+
105+
expect(mentionNode.getFormattedDisplayText()).toBe("@url:example.com")
106+
})
107+
108+
it("should keep standard format for mentions without type", () => {
109+
const mentionNode = $createMentionNode("@", "test-file.ts", "test-file.ts")
110+
111+
expect(mentionNode.getFormattedDisplayText()).toBe("@test-file.ts")
112+
})
113+
114+
it("should keep @ prefix for special mentions (problems, terminal)", () => {
115+
const problemsNode = $createMentionNode("@", "problems", "problems", undefined, { type: "problems" })
116+
const terminalNode = $createMentionNode("@", "terminal", "terminal", undefined, { type: "terminal" })
117+
118+
expect(problemsNode.getFormattedDisplayText()).toBe("@problems")
119+
expect(terminalNode.getFormattedDisplayText()).toBe("@terminal")
120+
})
121+
122+
it("should serialize and deserialize with value and label", () => {
123+
const mentionNode = $createMentionNode("@", "/path/to/filename.ts", "filename.ts", undefined, { type: "file" })
124+
const serialized = mentionNode.exportJSON()
125+
126+
expect(serialized.value).toBe("/path/to/filename.ts")
127+
expect(serialized.label).toBe("filename.ts")
128+
129+
const deserialized = MentionNode.importJSON(serialized)
130+
expect(deserialized.getValue()).toBe("/path/to/filename.ts")
131+
expect(deserialized.getLabel()).toBe("filename.ts")
132+
})
133+
134+
it("should update value and label", () => {
135+
const mentionNode = $createMentionNode("@", "file.ts", "file.ts")
136+
137+
const withValue = mentionNode.setValue("/path/to/file.ts")
138+
expect(withValue.getValue()).toBe("/path/to/file.ts")
139+
140+
const withLabel = withValue.setLabel("file.ts")
141+
expect(withLabel.getLabel()).toBe("file.ts")
142+
})
143+
144+
it("should update display text when setting data with type", () => {
145+
const mentionNode = $createMentionNode("@", "/path/to/file.ts", "file.ts")
146+
147+
const withType = mentionNode.setData({ type: "file" })
148+
expect(withType.getFormattedDisplayText()).toBe("@file:file.ts")
149+
})
68150
})

0 commit comments

Comments
 (0)