1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#![allow(unsafe_op_in_unsafe_fn)]
use windows_sys::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM};
use windows_sys::Win32::UI::WindowsAndMessaging::{
CallWindowProcW, DefWindowProcW, GetWindowLongPtrW, SetWindowLongPtrW, GWLP_WNDPROC,
WM_CHAR, WM_DESTROY, SendMessageW, SetPropW, GetPropW, RemovePropW,
};
use windows_sys::Win32::UI::Controls::{EM_GETSEL, EM_SETSEL, EM_REPLACESEL};
use crate::w;
// use crate::ui::theme::{SetPropW, GetPropW, RemovePropW}; // Removed bad import
// Property name to store the original WndProc
// Property name literal moved to usage to avoid const issues
// const PROP_OLD_PROC: *const u16 = w!("CompactRs_OldEditProc").as_ptr();
/// Subclass an Edit control to handle Ctrl+Backspace
pub unsafe fn subclass_edit(hwnd: HWND) {
let old_proc = GetWindowLongPtrW(hwnd, GWLP_WNDPROC);
if old_proc != 0 {
let prop_name = w!("CompactRs_OldEditProc");
SetPropW(hwnd, prop_name.as_ptr(), old_proc as *mut std::ffi::c_void);
SetWindowLongPtrW(hwnd, GWLP_WNDPROC, edit_subclass_proc as *const () as isize);
}
}
unsafe extern "system" fn edit_subclass_proc(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
let prop_name = w!("CompactRs_OldEditProc");
let old_proc = GetPropW(hwnd, prop_name.as_ptr()) as isize;
if msg == WM_DESTROY {
// Restore original proc (optional, but good hygiene)
if old_proc != 0 {
SetWindowLongPtrW(hwnd, GWLP_WNDPROC, old_proc);
RemovePropW(hwnd, prop_name.as_ptr());
}
return CallWindowProcW(Some(std::mem::transmute(old_proc)), hwnd, msg, wparam, lparam);
}
if msg == WM_CHAR {
// Ctrl+Backspace produces 0x7F (127)
if wparam == 0x7F {
// Handle Ctrl+Backspace
if handle_ctrl_backspace(hwnd) {
return 0; // Handled
}
}
}
if old_proc != 0 {
CallWindowProcW(Some(std::mem::transmute(old_proc)), hwnd, msg, wparam, lparam)
} else {
DefWindowProcW(hwnd, msg, wparam, lparam)
}
}
unsafe fn handle_ctrl_backspace(hwnd: HWND) -> bool {
let mut start: u32 = 0;
let mut end: u32 = 0;
// Get current selection
SendMessageW(hwnd, EM_GETSEL, &mut start as *mut _ as usize, &mut end as *mut _ as isize);
// If selection is empty (caret only), finding previous word
// If selection exists, standard backspace behavior deletes selection (default handler handles this).
// Ctrl+Backspace usually deletes selection if exists, OR deletes word left.
// Let's mimic standard: if selection, delete it. If not, delete word left.
// Actually, default Edit control DOES handle selection delete on Backspace/Ctrl+Backspace char?
// Standard Backspace (0x08) deletes selection.
// Ctrl+Backspace (0x7F) usually does nothing in raw Edit.
if start != end {
// Selection exists, just replace with empty.
let empty = w!("");
SendMessageW(hwnd, EM_REPLACESEL, 1, empty.as_ptr() as isize);
return true;
}
if start == 0 {
return false; // Nothing to delete
}
// We need text to find word boundary.
// This is expensive for large text, but for search/settings inputs it's fine.
let text = crate::ui::wrappers::get_window_text(hwnd);
let chars: Vec<u16> = text.encode_utf16().collect();
// Find split point
let mut pos = start as usize;
if pos > chars.len() { pos = chars.len(); } // Safety
let _slice = &chars[0..pos];
// Logic:
// 1. Skip trailing whitespace
// 2. Skip non-whitespace (word)
// 3. That's our new start.
let mut new_pos = pos;
// 1. Go back over potential whitespace (if any)?
// VSCode: "foo bar |" -> "foo bar |" (deletes space?) or "foo |"
// Standard: deletes word left of cursor.
// "abc def|" -> "abc |"
// "abc def|" -> "abc |"
// "abc |" -> "abc |" (deletes spaces)
// Implementation:
// Iterate backwards.
// If we are at space, consume spaces until non-space.
// Then consume non-spaces until space or start.
// Let's treat it as: Delete until next word boundary.
let is_space = |c: u16| c == 0x0020 || c == 0x0009 || c == 0x000A || c == 0x000D;
// If currently at valid char, consume valid chars.
// If currently at space, consume spaces.
// Wait, standard Ctrl+Back behavior:
// "Word|" -> "|"
// "Word |" -> "Word|"
// "Word |" -> "Word |" (Deletes 1 space?) Or all spaces?
// Notepad:
// "Test Test|" -> "Test |" (Deletes word)
// "Test |" -> "Test|" (Deletes spaces)
// "Test Test|" -> "Test |" (Deletes word)
// Let's refine:
// 1. If we are just after word chars, delete back to start of word.
// 2. If we are just after whitespace, delete back to end of word.
// Simple algo:
// If char before is space, delete contiguous spaces.
// If char before is non-space, delete contiguous non-spaces.
if new_pos > 0 && is_space(chars[new_pos-1]) {
// Consumption 1: Spaces
while new_pos > 0 && is_space(chars[new_pos-1]) {
new_pos -= 1;
}
} else {
// Consumption 2: Word
// Also handling punctuation? Win32 usually stops at punctuation?
// Let's assume non-whitespace is "Word".
while new_pos > 0 && !is_space(chars[new_pos-1]) {
// Optional: Stop at punctuation? "foo-bar" -> "foo-"?
// Let's simpler: consume until whitespace.
new_pos -= 1;
}
}
// Perform Delete
// Select range [new_pos, start]
SendMessageW(hwnd, EM_SETSEL, new_pos, start as isize);
let empty = w!("");
SendMessageW(hwnd, EM_REPLACESEL, 1, empty.as_ptr() as isize);
true
}