Skip to content

Commit 97113bc

Browse files
committed
better memory management
1 parent 610420a commit 97113bc

7 files changed

Lines changed: 287 additions & 129 deletions

File tree

src/buffer_controller.zig

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ var sync_attempt_count: u8 = 0;
3232
/// Map to remember which sync mode works best with each window class
3333
pub var window_class_to_mode: std.StringHashMap(u8) = undefined; // No initial init
3434

35+
var last_content_hash: u64 = 0;
36+
3537
pub fn init(allocator: std.mem.Allocator) !void {
3638
buffer_allocator = allocator;
3739
buffer_manager = buffer.BufferManager.init(allocator);
@@ -76,6 +78,14 @@ pub fn detectActiveTextField() void {
7678
buffer_manager.resetBuffer();
7779
buffer_manager.insertString(text) catch |err| {
7880
debug.debugPrint("Failed to sync buffer with text field: {}\n", .{err});
81+
// Recover by inserting a smaller portion if buffer is full
82+
if (err == error.BufferFull and text.len > 1024) {
83+
debug.debugPrint("Attempting to recover by inserting smaller portion\n", .{});
84+
buffer_manager.resetBuffer();
85+
buffer_manager.insertString(text[0..1024]) catch |truncate_err| {
86+
debug.debugPrint("Failed even with truncated text: {}\n", .{truncate_err});
87+
};
88+
}
7989
};
8090

8191
debug.debugPrint("Synced buffer with text field content: \"{s}\"\n", .{text});
@@ -177,6 +187,20 @@ fn tryKeySimulation(content: []const u8) bool {
177187
/// Print the current buffer state and process text for autocompletion
178188
pub fn printBufferState() void {
179189
const content = buffer_manager.getCurrentText();
190+
191+
// Calculate hash for current content
192+
var hasher = std.hash.Wyhash.init(0);
193+
hasher.update(content);
194+
const current_hash = hasher.final();
195+
196+
// If content hasn't changed (based on hash), skip expensive operations
197+
if (current_hash == last_content_hash) {
198+
return;
199+
}
200+
201+
// Update hash and continue with normal processing
202+
last_content_hash = current_hash;
203+
180204
debug.debugPrint("Buffer: \"", .{});
181205
printEscaped(content);
182206
debug.debugPrint("\" (len: {})\n", .{content.len});

src/core/buffer.zig

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ pub const TextBuffer = struct {
6161
return error.BufferFull;
6262
}
6363

64-
// Validate character
65-
if (char == 0 or (char < 32 and char != '\n' and char != '\r' and char != '\t')) {
66-
debug.debugPrint("Ignoring invalid character: 0x{X}\n", .{char});
67-
return;
64+
// Validate state before insertion
65+
if (self.gap_start >= self.content.len or self.gap_end > self.content.len) {
66+
debug.debugPrint("Buffer in invalid state: gap_start={d}, gap_end={d}, buffer_len={d}\n", .{ self.gap_start, self.gap_end, self.content.len });
67+
return error.BufferCorruption;
6868
}
6969

7070
// Insert at gap start
@@ -74,6 +74,13 @@ pub const TextBuffer = struct {
7474
self.length += 1;
7575
self.content_dirty = true;
7676

77+
// Validate state after insertion
78+
if (self.gap_start + self.gap_size != self.gap_end) {
79+
debug.debugPrint("Buffer inconsistency after insertion\n", .{});
80+
// Correct the inconsistency
81+
self.gap_end = self.gap_start + self.gap_size;
82+
}
83+
7784
// Update cursor position
7885
self.cursor.offset += 1;
7986
self.cursor.column += 1;

src/input/keyboard.zig

Lines changed: 125 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,125 @@ pub fn setupKeyboardHook() !win32.HHOOK {
2222
return hook.?;
2323
}
2424

25+
/// Process navigation keys for autocomplete UI
26+
fn processNavigationKeys(kbd: *win32.KBDLLHOOKSTRUCT) callconv(.C) win32.LRESULT {
27+
// Debug output for suggestion navigation
28+
debug.debugPrint("Suggestion navigation key: 0x{X}\n", .{kbd.vkCode});
29+
30+
// Handle navigation keys for autocomplete
31+
switch (kbd.vkCode) {
32+
win32.VK_UP => {
33+
// Move to previous suggestion
34+
manager.navigateToPreviousSuggestion();
35+
return 1; // Prevent default handling
36+
},
37+
win32.VK_DOWN => {
38+
// Move to next suggestion
39+
manager.navigateToNextSuggestion();
40+
return 1; // Prevent default handling
41+
},
42+
win32.VK_TAB, win32.VK_RIGHT => {
43+
debug.debugPrint("Accepting current suggestion\n", .{});
44+
// Accept current suggestion
45+
manager.acceptCurrentSuggestion();
46+
return 1; // Prevent default handling
47+
},
48+
win32.VK_RETURN => {
49+
debug.debugPrint("Accepting current suggestion with enter\n", .{});
50+
// Accept current suggestion
51+
manager.acceptCurrentSuggestion();
52+
53+
// Special handling for return key - after accepting suggestion,
54+
// we also need to properly handle the return key itself, so we don't
55+
// entirely consume it unless explicitly configured otherwise
56+
const config_consume_enter = true; // Make this configurable
57+
return if (config_consume_enter) 1 else 0;
58+
},
59+
else => return 0,
60+
}
61+
}
62+
63+
/// Process special key combinations like Ctrl+Backspace
64+
fn processSpecialKeys(kbd: *win32.KBDLLHOOKSTRUCT) callconv(.C) win32.LRESULT {
65+
// Special handling for Ctrl+Backspace (whole word deletion)
66+
if (kbd.vkCode == win32.VK_BACK and g_ctrl_pressed) {
67+
debug.debugPrint("Ctrl+Backspace detected - deleting whole word\n", .{});
68+
69+
// First, let the application handle the real Ctrl+Backspace
70+
// by passing it to the next hook
71+
_ = win32.CallNextHookEx(null, win32.HC_ACTION, win32.WM_KEYDOWN, @as(win32.LPARAM, @bitCast(@intFromPtr(kbd))));
72+
73+
// Add a small delay to let the OS process the keypress
74+
api.sleep(20);
75+
76+
// Now force detection of the text field to sync our buffer with the new content
77+
buffer_controller.detectActiveTextField();
78+
79+
// After syncing, update autocomplete suggestions
80+
const word = buffer_controller.getCurrentWord() catch "";
81+
manager.setCurrentWord(word);
82+
manager.getAutocompleteSuggestions() catch {};
83+
84+
// Print current buffer state to verify
85+
buffer_controller.printBufferState();
86+
87+
// Return 0 to allow the key to be processed (we've already called the next hook)
88+
return 0;
89+
}
90+
91+
return -1; // Indicates no special key was processed
92+
}
93+
94+
/// Process standard text editing keys (backspace, delete, etc.)
95+
fn processTextEditingKeys(kbd: *win32.KBDLLHOOKSTRUCT) void {
96+
debug.debugPrint("Key down: 0x{X}\n", .{kbd.vkCode});
97+
98+
// Exit application on ESC key
99+
if (kbd.vkCode == win32.VK_ESCAPE) {
100+
debug.debugPrint("ESC pressed - exit\n", .{});
101+
std.process.exit(0);
102+
}
103+
104+
// Special key handling for text editing
105+
if (kbd.vkCode == win32.VK_BACK) {
106+
// Backspace key
107+
buffer_controller.processBackspace() catch |err| {
108+
debug.debugPrint("Backspace error: {}\n", .{err});
109+
};
110+
111+
// Add a small delay to let the backspace take effect
112+
api.sleep(5);
113+
114+
// Detect the text field again to ensure sync
115+
buffer_controller.detectActiveTextField();
116+
117+
// Update suggestions based on new text state
118+
const word = buffer_controller.getCurrentWord() catch "";
119+
manager.setCurrentWord(word);
120+
manager.getAutocompleteSuggestions() catch {};
121+
} else if (kbd.vkCode == win32.VK_DELETE) {
122+
// Delete key
123+
buffer_controller.processDelete() catch |err| {
124+
debug.debugPrint("Delete error: {}\n", .{err});
125+
};
126+
} else if (kbd.vkCode == win32.VK_RETURN) {
127+
// Enter/Return key
128+
buffer_controller.processReturn() catch |err| {
129+
debug.debugPrint("Return error: {}\n", .{err});
130+
};
131+
} else if (kbd.vkCode == win32.VK_TAB) {
132+
// Tab key might indicate focus change - detect text field
133+
buffer_controller.detectActiveTextField();
134+
} else {
135+
// ASCII character input
136+
const char: u8 = @truncate(kbd.vkCode);
137+
buffer_controller.handleCharInput(char);
138+
}
139+
140+
// Debug: print current buffer state
141+
buffer_controller.printBufferState();
142+
}
143+
25144
/// Low-level keyboard hook callback function
26145
fn keyboardHookProc(nCode: c_int, wParam: win32.WPARAM, lParam: win32.LPARAM) callconv(.C) win32.LRESULT {
27146
// First, immediately check if we should pass this to the next hook
@@ -45,9 +164,6 @@ fn keyboardHookProc(nCode: c_int, wParam: win32.WPARAM, lParam: win32.LPARAM) ca
45164

46165
// Only process keydown events
47166
if (wParam == win32.WM_KEYDOWN or wParam == win32.WM_SYSKEYDOWN) {
48-
// Track if we consumed the key
49-
var key_consumed = false;
50-
51167
// Handle navigation keys when autocomplete is visible
52168
if (manager.isSuggestionUIVisible() and
53169
(kbd.vkCode == win32.VK_UP or
@@ -56,77 +172,13 @@ fn keyboardHookProc(nCode: c_int, wParam: win32.WPARAM, lParam: win32.LPARAM) ca
56172
kbd.vkCode == win32.VK_RIGHT or
57173
kbd.vkCode == win32.VK_RETURN))
58174
{
59-
// Debug output for suggestion navigation
60-
debug.debugPrint("Suggestion navigation key: 0x{X}\n", .{kbd.vkCode});
61-
62-
// Handle navigation keys for autocomplete
63-
switch (kbd.vkCode) {
64-
win32.VK_UP => {
65-
// Move to previous suggestion
66-
manager.navigateToPreviousSuggestion();
67-
return 1; // Prevent default handling
68-
},
69-
win32.VK_DOWN => {
70-
// Move to next suggestion
71-
manager.navigateToNextSuggestion();
72-
return 1; // Prevent default handling
73-
},
74-
win32.VK_TAB, win32.VK_RIGHT => {
75-
debug.debugPrint("Accepting current suggestion\n", .{});
76-
// Accept current suggestion
77-
manager.acceptCurrentSuggestion();
78-
return 1; // Prevent default handling
79-
},
80-
win32.VK_RETURN => {
81-
debug.debugPrint("Accepting current suggestion with enter\n", .{});
82-
// Accept current suggestion
83-
manager.acceptCurrentSuggestion();
84-
85-
// Special handling for return key - after accepting suggestion,
86-
// we also need to properly handle the return key itself, so we don't
87-
// entirely consume it unless explicitly configured otherwise
88-
const config_consume_enter = true; // Make this configurable
89-
if (!config_consume_enter) {
90-
// Let the app handle enter normally
91-
key_consumed = false;
92-
} else {
93-
key_consumed = true;
94-
}
95-
},
96-
else => {},
97-
}
98-
99-
// If we consumed the key, prevent default handling
100-
if (key_consumed) {
101-
return 1;
102-
}
175+
const result = processNavigationKeys(kbd);
176+
if (result >= 0) return result;
103177
}
104178

105-
// Special handling for Ctrl+Backspace (whole word deletion)
106-
if (kbd.vkCode == win32.VK_BACK and g_ctrl_pressed) {
107-
debug.debugPrint("Ctrl+Backspace detected - deleting whole word\n", .{});
108-
109-
// First, let the application handle the real Ctrl+Backspace
110-
// by passing it to the next hook
111-
_ = win32.CallNextHookEx(null, nCode, wParam, lParam);
112-
113-
// Add a small delay to let the OS process the keypress
114-
api.sleep(20);
115-
116-
// Now force detection of the text field to sync our buffer with the new content
117-
buffer_controller.detectActiveTextField();
118-
119-
// After syncing, update autocomplete suggestions
120-
const word = buffer_controller.getCurrentWord() catch "";
121-
manager.setCurrentWord(word);
122-
manager.getAutocompleteSuggestions() catch {};
123-
124-
// Print current buffer state to verify
125-
buffer_controller.printBufferState();
126-
127-
// Return 0 to allow the key to be processed (we've already called the next hook)
128-
return 0;
129-
}
179+
// Process special key combinations
180+
const special_result = processSpecialKeys(kbd);
181+
if (special_result >= 0) return special_result;
130182

131183
// Limit processing to printable characters and specific control keys
132184
const is_control_key =
@@ -169,52 +221,7 @@ fn keyboardHookProc(nCode: c_int, wParam: win32.WPARAM, lParam: win32.LPARAM) ca
169221
const is_printable = kbd.vkCode >= 0x20 and kbd.vkCode <= 0x7E;
170222

171223
if (is_control_key or is_printable) {
172-
debug.debugPrint("Key down: 0x{X}\n", .{kbd.vkCode});
173-
174-
// Exit application on ESC key
175-
if (kbd.vkCode == win32.VK_ESCAPE) {
176-
debug.debugPrint("ESC pressed - exit\n", .{});
177-
std.process.exit(0);
178-
}
179-
180-
// Special key handling for text editing
181-
if (kbd.vkCode == win32.VK_BACK) {
182-
// Backspace key
183-
buffer_controller.processBackspace() catch |err| {
184-
debug.debugPrint("Backspace error: {}\n", .{err});
185-
};
186-
187-
// Add a small delay to let the backspace take effect
188-
api.sleep(5);
189-
190-
// Detect the text field again to ensure sync
191-
buffer_controller.detectActiveTextField();
192-
193-
// Update suggestions based on new text state
194-
const word = buffer_controller.getCurrentWord() catch "";
195-
manager.setCurrentWord(word);
196-
manager.getAutocompleteSuggestions() catch {};
197-
} else if (kbd.vkCode == win32.VK_DELETE) {
198-
// Delete key
199-
buffer_controller.processDelete() catch |err| {
200-
debug.debugPrint("Delete error: {}\n", .{err});
201-
};
202-
} else if (kbd.vkCode == win32.VK_RETURN) {
203-
// Enter/Return key
204-
buffer_controller.processReturn() catch |err| {
205-
debug.debugPrint("Return error: {}\n", .{err});
206-
};
207-
} else if (kbd.vkCode == win32.VK_TAB) {
208-
// Tab key might indicate focus change - detect text field
209-
buffer_controller.detectActiveTextField();
210-
} else if (is_printable) {
211-
// ASCII character input
212-
const char: u8 = @truncate(kbd.vkCode);
213-
buffer_controller.handleCharInput(char);
214-
}
215-
216-
// Debug: print current buffer state
217-
buffer_controller.printBufferState();
224+
processTextEditingKeys(kbd);
218225
}
219226
}
220227
}

src/main.zig

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const buffer_controller = sysinput.buffer_controller;
77
const manager = sysinput.suggestion.manager;
88
const win32 = sysinput.win32.hook;
99
const debug = sysinput.core.debug;
10+
const edit_distance = sysinput.text.edit_distance;
1011

1112
/// General Purpose Allocator for dynamic memory
1213
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
@@ -72,4 +73,38 @@ test "basic buffer operations" {
7273
// Test clear
7374
test_buffer.resetBuffer();
7475
try std.testing.expectEqualStrings("", test_buffer.getCurrentText());
76+
77+
// Test handling special characters
78+
try test_buffer.insertString("Line1\nLine2\tTabbed");
79+
try std.testing.expectEqualStrings("Line1\nLine2\tTabbed", test_buffer.getCurrentText());
80+
81+
// Test word extraction
82+
const word = try test_buffer.getCurrentWord();
83+
try std.testing.expectEqualStrings("Tabbed", word);
84+
85+
// Test buffer limits
86+
const long_text = try allocator.alloc(u8, 4000);
87+
defer allocator.free(long_text);
88+
@memset(long_text, 'A');
89+
90+
test_buffer.resetBuffer();
91+
try test_buffer.insertString(long_text);
92+
try std.testing.expectEqual(long_text.len, test_buffer.getCurrentText().len);
93+
}
94+
95+
test "edit distance calculation" {
96+
// Test basic edit distance calculation
97+
try std.testing.expectEqual(@as(usize, 0), edit_distance.enhancedEditDistance("test", "test"));
98+
try std.testing.expectEqual(@as(usize, 1), edit_distance.enhancedEditDistance("test", "tent"));
99+
try std.testing.expectEqual(@as(usize, 2), edit_distance.enhancedEditDistance("test", "text"));
100+
101+
// Test with empty strings
102+
try std.testing.expectEqual(@as(usize, 4), edit_distance.enhancedEditDistance("test", ""));
103+
try std.testing.expectEqual(@as(usize, 4), edit_distance.enhancedEditDistance("", "test"));
104+
try std.testing.expectEqual(@as(usize, 0), edit_distance.enhancedEditDistance("", ""));
105+
106+
// Test similarity scoring
107+
const score1 = edit_distance.calculateSuggestionScore("te", "test");
108+
const score2 = edit_distance.calculateSuggestionScore("te", "tent");
109+
try std.testing.expect(score1 > score2); // "test" should be a better suggestion for "te" than "tent"
75110
}

0 commit comments

Comments
 (0)