Skip to content

Commit 1c8565c

Browse files
committed
Refactor the caret position utility functions into a dedicated caret.ts file for improved modularity
1 parent 26b9436 commit 1c8565c

4 files changed

Lines changed: 76 additions & 135 deletions

File tree

‎packages/ui/src/components/editor/panel/ChatInput/ChatInput.tsx‎

Lines changed: 4 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import { use_dropdown } from './hooks/use-dropdown'
88
import { DropdownMenu } from '../../common/DropdownMenu'
99
import { search_paths } from '@shared/utils/search-paths'
1010
import { get_display_text } from './utils/get-display-text'
11+
import {
12+
get_caret_position_from_div,
13+
set_caret_position_for_div
14+
} from './utils/caret'
1115

1216
export type EditFormat = 'whole' | 'truncated' | 'diff'
1317

@@ -41,72 +45,6 @@ export type ChatInputProps = {
4145
on_go_to_file?: (file_path: string) => void
4246
}
4347

44-
const get_caret_position_from_div = (element: HTMLElement): number => {
45-
const selection = window.getSelection()
46-
if (!selection || selection.rangeCount == 0) {
47-
return 0
48-
}
49-
const range = selection.getRangeAt(0)
50-
const pre_caret_range = range.cloneRange()
51-
pre_caret_range.selectNodeContents(element)
52-
pre_caret_range.setEnd(range.endContainer, range.endOffset)
53-
return pre_caret_range.toString().length
54-
}
55-
56-
const set_caret_position_for_div = (element: HTMLElement, position: number) => {
57-
const selection = window.getSelection()
58-
if (!selection) return
59-
const range = document.createRange()
60-
let char_count = 0
61-
let found = false
62-
63-
const find_text_node_and_offset = (node: Node) => {
64-
if (found) return
65-
if (node.nodeType == Node.TEXT_NODE) {
66-
const text_node = node as Text
67-
const next_char_count = char_count + text_node.length
68-
if (position >= char_count && position <= next_char_count) {
69-
range.setStart(node, position - char_count)
70-
range.collapse(true)
71-
found = true
72-
} else {
73-
char_count = next_char_count
74-
}
75-
} else {
76-
if (
77-
node.nodeType === Node.ELEMENT_NODE &&
78-
(node as HTMLElement).getAttribute('contenteditable') === 'false'
79-
) {
80-
const text_len = node.textContent?.length ?? 0
81-
const next_char_count = char_count + text_len
82-
if (position >= char_count && position <= next_char_count) {
83-
range.setStartAfter(node)
84-
range.collapse(true)
85-
found = true
86-
} else {
87-
char_count = next_char_count
88-
}
89-
return // Do not iterate over children
90-
}
91-
for (let i = 0; i < node.childNodes.length; i++) {
92-
find_text_node_and_offset(node.childNodes[i])
93-
if (found) break
94-
}
95-
}
96-
}
97-
98-
find_text_node_and_offset(element)
99-
if (found) {
100-
selection.removeAllRanges()
101-
selection.addRange(range)
102-
} else {
103-
range.selectNodeContents(element)
104-
range.collapse(false)
105-
selection.removeAllRanges()
106-
selection.addRange(range)
107-
}
108-
}
109-
11048
const map_display_pos_to_raw_pos = (
11149
display_pos: number,
11250
raw_text: string,

‎packages/ui/src/components/editor/panel/ChatInput/hooks/use-handlers.ts‎

Lines changed: 4 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,10 @@
11
import { useState } from 'react'
22
import { ChatInputProps } from '../ChatInput'
33
import { get_display_text } from '../utils/get-display-text'
4-
5-
const get_caret_position_from_div = (element: HTMLElement): number => {
6-
const selection = window.getSelection()
7-
if (!selection || selection.rangeCount === 0) {
8-
return 0
9-
}
10-
const range = selection.getRangeAt(0)
11-
const pre_caret_range = range.cloneRange()
12-
pre_caret_range.selectNodeContents(element)
13-
pre_caret_range.setEnd(range.endContainer, range.endOffset)
14-
return pre_caret_range.toString().length
15-
}
16-
17-
const set_caret_position_for_div = (element: HTMLElement, position: number) => {
18-
const selection = window.getSelection()
19-
if (!selection) return
20-
const range = document.createRange()
21-
let char_count = 0
22-
let found = false
23-
24-
function find_text_node_and_offset(node: Node) {
25-
if (found) return
26-
if (node.nodeType === Node.TEXT_NODE) {
27-
const text_node = node as Text
28-
const next_char_count = char_count + text_node.length
29-
if (position >= char_count && position <= next_char_count) {
30-
range.setStart(node, position - char_count)
31-
range.collapse(true)
32-
found = true
33-
} else {
34-
char_count = next_char_count
35-
}
36-
} else {
37-
if (
38-
node.nodeType === Node.ELEMENT_NODE &&
39-
(node as HTMLElement).getAttribute('contenteditable') === 'false'
40-
) {
41-
const text_len = node.textContent?.length ?? 0
42-
const next_char_count = char_count + text_len
43-
if (position >= char_count && position <= next_char_count) {
44-
range.setStartAfter(node)
45-
range.collapse(true)
46-
found = true
47-
} else {
48-
char_count = next_char_count
49-
}
50-
return // Do not iterate over children
51-
}
52-
for (let i = 0; i < node.childNodes.length; i++) {
53-
find_text_node_and_offset(node.childNodes[i])
54-
if (found) break
55-
}
56-
}
57-
}
58-
59-
find_text_node_and_offset(element)
60-
if (found) {
61-
selection.removeAllRanges()
62-
selection.addRange(range)
63-
} else {
64-
range.selectNodeContents(element)
65-
range.collapse(false)
66-
selection.removeAllRanges()
67-
selection.addRange(range)
68-
}
69-
}
4+
import {
5+
get_caret_position_from_div,
6+
set_caret_position_for_div
7+
} from '../utils/caret'
708

719
const reconstruct_raw_value_from_node = (node: Node): string => {
7210
if (node.nodeType === Node.TEXT_NODE) {
@@ -124,7 +62,6 @@ export const use_handlers = (props: ChatInputProps) => {
12462
props.on_change(new_value)
12563
set_history_index(-1)
12664

127-
// Clear saved value on any input change
12865
set_saved_value_before_at_sign(null)
12966

13067
const new_display_value = e.currentTarget.textContent ?? ''
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
export const get_caret_position_from_div = (element: HTMLElement): number => {
2+
const selection = window.getSelection()
3+
if (!selection || selection.rangeCount === 0) {
4+
return 0
5+
}
6+
const range = selection.getRangeAt(0)
7+
const pre_caret_range = range.cloneRange()
8+
pre_caret_range.selectNodeContents(element)
9+
pre_caret_range.setEnd(range.endContainer, range.endOffset)
10+
return pre_caret_range.toString().length
11+
}
12+
13+
export const set_caret_position_for_div = (
14+
element: HTMLElement,
15+
position: number
16+
) => {
17+
const selection = window.getSelection()
18+
if (!selection) return
19+
const range = document.createRange()
20+
let char_count = 0
21+
let found = false
22+
23+
const find_text_node_and_offset = (node: Node) => {
24+
if (found) return
25+
if (node.nodeType == Node.TEXT_NODE) {
26+
const text_node = node as Text
27+
const next_char_count = char_count + text_node.length
28+
if (position >= char_count && position <= next_char_count) {
29+
range.setStart(node, position - char_count)
30+
range.collapse(true)
31+
found = true
32+
} else {
33+
char_count = next_char_count
34+
}
35+
} else {
36+
if (
37+
node.nodeType == Node.ELEMENT_NODE &&
38+
(node as HTMLElement).getAttribute('contenteditable') == 'false'
39+
) {
40+
const text_len = node.textContent?.length ?? 0
41+
const next_char_count = char_count + text_len
42+
if (position >= char_count && position <= next_char_count) {
43+
range.setStartAfter(node)
44+
range.collapse(true)
45+
found = true
46+
} else {
47+
char_count = next_char_count
48+
}
49+
return // Do not iterate over children
50+
}
51+
for (let i = 0; i < node.childNodes.length; i++) {
52+
find_text_node_and_offset(node.childNodes[i])
53+
if (found) break
54+
}
55+
}
56+
}
57+
58+
find_text_node_and_offset(element)
59+
if (found) {
60+
selection.removeAllRanges()
61+
selection.addRange(range)
62+
} else {
63+
range.selectNodeContents(element)
64+
range.collapse(false)
65+
selection.removeAllRanges()
66+
selection.addRange(range)
67+
}
68+
}

‎packages/ui/src/components/editor/panel/ChatInput/utils/get-display-text.ts‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ export const get_display_text = (
22
text: string,
33
context_file_paths: string[]
44
): string => {
5-
// Replace file paths in backticks with just the filename (no backticks)
65
return text.replace(/`([^\s`]*\.[^\s`]+)`/g, (match, file_path) => {
76
if (context_file_paths.includes(file_path)) {
8-
// Extract just the filename from the path
97
const filename = file_path.split('/').pop() || file_path
108
return filename
119
}

0 commit comments

Comments
 (0)