mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-16 05:27:50 +02:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40f9945ea7 | ||
|
|
f4d4a80ca3 | ||
|
|
0c90b66f91 | ||
|
|
1f2149eb6f | ||
|
|
ddfd8d7f45 | ||
|
|
66ffb6895c | ||
|
|
b3b7d0596d | ||
|
|
b93f1badd1 | ||
|
|
30756bb819 | ||
|
|
d244a697bf | ||
|
|
827c8dd614 | ||
|
|
b66c6c4932 | ||
|
|
e72f49952d | ||
|
|
a97f468a02 | ||
|
|
c332211997 | ||
|
|
98931d99b0 | ||
|
|
5585b773bf | ||
|
|
ffe8cef1a8 | ||
|
|
c0b549fee8 | ||
|
|
99639f1373 | ||
|
|
5ba3d10471 | ||
|
|
39683e0c34 | ||
|
|
2c395a1ef9 | ||
|
|
01a70e06c4 | ||
|
|
0614f05335 | ||
|
|
7b9b8834a8 | ||
|
|
f4bf9cf1c9 | ||
|
|
d8af7e2c88 | ||
|
|
f45345c7a7 | ||
|
|
c78592174d | ||
|
|
3f16aab664 | ||
|
|
9f45daf300 | ||
|
|
a0709acdde | ||
|
|
b9d7a661ce | ||
|
|
c2447abd30 | ||
|
|
36810862ed | ||
|
|
01104bac65 | ||
|
|
99ff621b21 | ||
|
|
633d833907 | ||
|
|
e9de88221f | ||
|
|
50a69cb093 | ||
|
|
a1e02c8858 | ||
|
|
e64fbe145e | ||
|
|
121eec48b5 | ||
|
|
9aa938c6cd | ||
|
|
4ad6d30ab8 | ||
|
|
74f532bd07 |
@@ -9,6 +9,21 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||
Recent major new features
|
||||
---------------------------
|
||||
|
||||
Mousing [0.46]
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
kitty already had excellent mouse support, but now it is taking it to the next
|
||||
level. The kitty scrollback buffer grew support for :opt:`smooth scrolling
|
||||
<pixel_scroll>` and :opt:`momentum based scrolling <momentum_scroll>`
|
||||
for a natural, smooth and kinetic scrolling experience.
|
||||
|
||||
Additionally, you can now :opt:`drag kitty tabs around <tab_bar_drag_threshold>` with the mouse
|
||||
to re-order them, move them to another kitty OS Window or even detach them into
|
||||
their own OS Window.
|
||||
|
||||
Finally, a long requested feature, the ability to resize kitty windows (aka
|
||||
splits) with the mouse was implemented.
|
||||
|
||||
Choose files, fast [0.45]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -150,6 +165,31 @@ consumption to do the same tasks.
|
||||
Detailed list of changes
|
||||
-------------------------------------
|
||||
|
||||
0.46.1 [2026-03-16]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- diff kitten: Highlight moved lines using a different background color (:opt:`kitten-diff.mark_moved_lines`) (:iss:`3241`)
|
||||
|
||||
- Fix a regression that broke ``kitten update-self`` (:iss:`9642`)
|
||||
|
||||
- macOS: Clear bell alert badge on dock icon on mouse/keyboard activity (:iss:`9640`)
|
||||
|
||||
- Fix a regression that broke accept anyway shortcut in the paste confirmation dialog (:pull:`9640`)
|
||||
|
||||
- Fix kitty hanging on startup on Intel macs (:iss:`9643`)
|
||||
|
||||
- X11: Fix a regression that caused some high res scroll devices to be treated as line based scroll devices (:iss:`9649`)
|
||||
|
||||
- Wayland: Fix momentum scrolling not working on compositors that send a stop frame with no axis information (:iss:`9653`)
|
||||
|
||||
- Linux: Fix regression that broke drag and drop from GTK applications (:iss:`9656`)
|
||||
|
||||
- macOS: Fix using Fn key for start dictation not working (:iss:`9661`)
|
||||
|
||||
- Don't use neighboring tab colors for tab bar margins in translucent windows (:iss:`9663`)
|
||||
|
||||
- macOS: Fix OS window focus not restored when switching spaces (:iss:`9665`)
|
||||
|
||||
0.46.0 [2026-03-11]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -161,7 +201,7 @@ Detailed list of changes
|
||||
|
||||
- macOS: Implement support for Apple dictation to input text in kitty (:iss:`3732`)
|
||||
|
||||
- Allow dragging tabs in the tab bar to re-order, move to another OS Window or
|
||||
- Allow dragging tabs (opt:`tab_bar_drag_threshold`) in the tab bar to re-order, move to another OS Window or
|
||||
detach (:pull:`9296`)
|
||||
|
||||
- Allow dragging window borders to resize kitty windows in all the different
|
||||
@@ -170,7 +210,7 @@ Detailed list of changes
|
||||
- Allow showing :opt:`configurable window titles <window_title_bar>` for individual kitty
|
||||
windows via a window title bar (:pull:`9450`)
|
||||
|
||||
- A command palette to browse and trigger all mapped and unmapped actions
|
||||
- A command palette (:sc:`command_palette`) to browse and trigger all mapped and unmapped actions
|
||||
(:pull:`9545`)
|
||||
|
||||
- choose-files kitten: Fix JXL image preview not working (:iss:`9323`)
|
||||
|
||||
@@ -14,9 +14,9 @@ Image and document viewers
|
||||
Powered by kitty's :doc:`graphics-protocol` there exist many tools for viewing
|
||||
images and other types of documents directly in your terminal, even over SSH.
|
||||
|
||||
.. _tool_bookorat:
|
||||
.. _tool_bookokrat:
|
||||
|
||||
`bookorat <https://github.com/bugzmanov/bookokrat>`
|
||||
`bookokrat <https://github.com/bugzmanov/bookokrat>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
A terminal PDF/EPUB viewer
|
||||
|
||||
|
||||
@@ -311,6 +311,24 @@ static NSDictionary<NSString*,NSNumber*> *global_shortcuts = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(NSNotification *)notification {
|
||||
(void)notification;
|
||||
// When the application becomes active after switching spaces (e.g., swiping
|
||||
// back from a fullscreen app on another space), macOS may not send
|
||||
// windowDidBecomeKey: for the already-key window. This leaves GLFW thinking
|
||||
// no window has focus (since windowDidResignKey: was sent when leaving).
|
||||
// Ensure GLFW's focus state is updated to match the actual key window.
|
||||
NSWindow *keyWindow = [NSApp keyWindow];
|
||||
if (keyWindow && !_glfw.focusedWindowId) {
|
||||
for (_GLFWwindow *window = _glfw.windowListHead; window; window = window->next) {
|
||||
if (window->ns.object == keyWindow) {
|
||||
if (_glfw.focusedWindowId != window->id) _glfwInputWindowFocus(window, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
|
||||
{
|
||||
(void)sender;
|
||||
@@ -839,6 +857,24 @@ is_apple_jis_layout_function_key(NSEvent *event) {
|
||||
return [event keyCode] == 0x66 /* kVK_JIS_Eisu */ || [event keyCode] == 0x68 /* kVK_JIS_Kana */;
|
||||
}
|
||||
|
||||
static bool
|
||||
has_apple_fn_global_shortcut(void) {
|
||||
NSDictionary *hitoolbox_settings = [[NSUserDefaults standardUserDefaults] persistentDomainForName:@"com.apple.HIToolbox"];
|
||||
id obj = [hitoolbox_settings objectForKey:@"AppleFnUsageType"];
|
||||
if (![obj isKindOfClass:[NSNumber class]]) return false;
|
||||
// Non-zero AppleFnUsageType means macOS has reserved Fn/Globe for a
|
||||
// system action such as input source switching, emoji picker, or dictation.
|
||||
return [obj integerValue] != 0;
|
||||
}
|
||||
|
||||
static bool
|
||||
is_apple_fn_global_shortcut(NSEvent *event) {
|
||||
if ([event keyCode] != 0x3f /* kVK_Function */) return false;
|
||||
NSEventModifierFlags mods = USEFUL_MODS([event modifierFlags]);
|
||||
if (mods != 0 && mods != NSEventModifierFlagFunction) return false;
|
||||
return has_apple_fn_global_shortcut();
|
||||
}
|
||||
|
||||
GLFWAPI GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) {
|
||||
GLFWapplicationshouldhandlereopenfun previous = handle_reopen_callback;
|
||||
handle_reopen_callback = callback;
|
||||
@@ -949,6 +985,10 @@ int _glfwPlatformInit(bool *supports_window_occlusion)
|
||||
debug_key("-------------- flags changed -----------------\n");
|
||||
debug_key("%s\n", [[event description] UTF8String]);
|
||||
last_keydown_shortcut_event.virtual_key_code = 0xffff;
|
||||
if (!_glfw.ignoreOSKeyboardProcessing && !_glfw.keyboard_grabbed && is_apple_fn_global_shortcut(event)) {
|
||||
debug_key("flagsChanged triggered global fn shortcut ignoring\n");
|
||||
return event;
|
||||
}
|
||||
// switching to the next input source is only confirmed when all modifier keys are released
|
||||
if (last_keydown_shortcut_event.input_source_switch_modifiers) {
|
||||
if (!([event modifierFlags] & last_keydown_shortcut_event.input_source_switch_modifiers))
|
||||
|
||||
@@ -99,6 +99,22 @@ polymorphic_string_as_utf8(id string) {
|
||||
return [characters UTF8String];
|
||||
}
|
||||
|
||||
static bool
|
||||
forward_dictation_selector_to_app(SEL selector, id sender) {
|
||||
static SEL start_dictation_selector = NULL, stop_dictation_selector = NULL;
|
||||
if (start_dictation_selector == NULL) {
|
||||
start_dictation_selector = NSSelectorFromString(@"startDictation:");
|
||||
stop_dictation_selector = NSSelectorFromString(@"stopDictation:");
|
||||
}
|
||||
if (selector != start_dictation_selector && selector != stop_dictation_selector) return false;
|
||||
if ([NSApp respondsToSelector:selector]) {
|
||||
debug_key("Forwarding %s to NSApp\n", [NSStringFromSelector(selector) UTF8String]);
|
||||
[NSApp performSelector:selector withObject:sender];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
vk_code_to_functional_key_code(uint8_t key_code) { // {{{
|
||||
switch(key_code) {
|
||||
@@ -842,6 +858,7 @@ static void update_titlebar_button_visibility_after_fullscreen_transition(_GLFWw
|
||||
// With the default macOS keybindings, pressing certain key combinations
|
||||
// (e.g. Ctrl+/, Ctrl+Cmd+Down/Left/Right) will produce a beep sound.
|
||||
debug_key("\n\tTextInputCtx: doCommandBySelector: (%s)\n", [NSStringFromSelector(selector) UTF8String]);
|
||||
if (forward_dictation_selector_to_app(selector, nil)) return;
|
||||
}
|
||||
@end // }}}
|
||||
|
||||
@@ -1930,6 +1947,17 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
||||
- (void)doCommandBySelector:(SEL)selector
|
||||
{
|
||||
debug_key("\n\tdoCommandBySelector: (%s)\n", [NSStringFromSelector(selector) UTF8String]);
|
||||
if (forward_dictation_selector_to_app(selector, self)) return;
|
||||
}
|
||||
|
||||
- (void)startDictation:(id)sender
|
||||
{
|
||||
forward_dictation_selector_to_app(_cmd, sender);
|
||||
}
|
||||
|
||||
- (void)stopDictation:(id)sender
|
||||
{
|
||||
forward_dictation_selector_to_app(_cmd, sender);
|
||||
}
|
||||
|
||||
- (BOOL)isAccessibilityElement
|
||||
|
||||
10
glfw/wl_init.c
vendored
10
glfw/wl_init.c
vendored
@@ -206,27 +206,35 @@ pointer_handle_frame(void *data UNUSED, struct wl_pointer *pointer UNUSED) {
|
||||
_GLFWwindow* window = _glfw.wl.pointerFocus;
|
||||
if (!window) return;
|
||||
GLFWScrollEvent ev = {.keyboard_modifiers=_glfw.wl.xkb.states.modifiers};
|
||||
bool found = false;
|
||||
|
||||
if (info.discrete.y_axis_type != AXIS_EVENT_UNKNOWN) {
|
||||
ev.unscaled.y = info.discrete.y;
|
||||
if (info.discrete.y_axis_type == AXIS_EVENT_VALUE120) ev.offset_type = GLFW_SCROLL_OFFEST_V120;
|
||||
found = true;
|
||||
} else if (info.continuous.y_axis_type != AXIS_EVENT_UNKNOWN) {
|
||||
ev.offset_type = GLFW_SCROLL_OFFEST_HIGHRES;
|
||||
ev.unscaled.y = info.continuous.y;
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (info.discrete.x_axis_type != AXIS_EVENT_UNKNOWN) {
|
||||
ev.unscaled.x = info.discrete.x;
|
||||
if (info.discrete.x_axis_type == AXIS_EVENT_VALUE120) ev.offset_type = GLFW_SCROLL_OFFEST_V120;
|
||||
found = true;
|
||||
} else if (info.continuous.x_axis_type != AXIS_EVENT_UNKNOWN) {
|
||||
ev.offset_type = GLFW_SCROLL_OFFEST_HIGHRES;
|
||||
ev.unscaled.x = info.continuous.x;
|
||||
found = true;
|
||||
}
|
||||
bool stopped = info.y_stop_received || info.x_stop_received;
|
||||
if (!found && stopped) ev.offset_type = window->wl.prev_frame_offset_type;
|
||||
ev.unscaled.x *= -1;
|
||||
const double scale = ev.offset_type == GLFW_SCROLL_OFFEST_HIGHRES ? _glfwWaylandWindowScale(window) : 1;
|
||||
ev.x_offset = scale * ev.unscaled.x; ev.y_offset = scale * ev.unscaled.y;
|
||||
glfw_handle_scroll_event_for_momentum(
|
||||
window, &ev, info.y_stop_received || info.x_stop_received, info.source_type == WL_POINTER_AXIS_SOURCE_FINGER);
|
||||
window, &ev, stopped, info.source_type == WL_POINTER_AXIS_SOURCE_FINGER);
|
||||
window->wl.prev_frame_offset_type = ev.offset_type;
|
||||
/* clear pointer_curr_axis_info for next frame */
|
||||
memset(&info, 0, sizeof(info));
|
||||
}
|
||||
|
||||
1
glfw/wl_platform.h
vendored
1
glfw/wl_platform.h
vendored
@@ -212,6 +212,7 @@ typedef struct _GLFWwindowWayland
|
||||
uint32_t source_type;
|
||||
monotonic_t x_start_time, x_stop_time, y_stop_time, y_start_time;
|
||||
} pointer_curr_axis_info;
|
||||
GLFWOffsetType prev_frame_offset_type;
|
||||
|
||||
_GLFWcursor* currentCursor;
|
||||
double cursorPosX, cursorPosY, allCursorPosX, allCursorPosY;
|
||||
|
||||
10
glfw/wl_window.c
vendored
10
glfw/wl_window.c
vendored
@@ -1588,6 +1588,14 @@ void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title)
|
||||
|
||||
void
|
||||
_glfwPlatformSetWindowIcon(_GLFWwindow* window, int count, const GLFWimage* images) {
|
||||
if (is_layer_shell(window)) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Cannot set window icon on layer shell surfaces");
|
||||
return;
|
||||
}
|
||||
if (!window->wl.xdg.toplevel) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Ignoring attempt to set window icon on window without a toplevel");
|
||||
return;
|
||||
}
|
||||
if (!_glfw.wl.xdg_toplevel_icon_manager_v1) {
|
||||
static bool warned_once = false;
|
||||
if (!warned_once) {
|
||||
@@ -2577,7 +2585,7 @@ _glfwPlatformReadAvailableDropData(GLFWwindow *w, GLFWDropEvent *ev, char *buffe
|
||||
for (size_t o = 0; o < offer->dd_count; o++) {
|
||||
if (offer->requested_drop_data[o].fd == fd) {
|
||||
ssize_t ret;
|
||||
do { ret = read(fd, buffer, sz); } while (ret < 0 && errno == EINTR);
|
||||
do { ret = read(fd, buffer, sz); } while (ret < 0 && (errno == EINTR || errno == EAGAIN));
|
||||
if (ret <= 0) removeWatch(&_glfw.wl.eventLoopData, offer->requested_drop_data[o].watch_id);
|
||||
return ret < 0 ? -errno : ret;
|
||||
}
|
||||
|
||||
6
glfw/x11_init.c
vendored
6
glfw/x11_init.c
vendored
@@ -216,8 +216,12 @@ read_xi_scroll_devices(void) {
|
||||
if (_glfw.x11.xi.num_scroll_devices >= arraysz(_glfw.x11.xi.scroll_devices)) continue;
|
||||
d = &_glfw.x11.xi.scroll_devices[_glfw.x11.xi.num_scroll_devices++];
|
||||
*d = (XIScrollDevice){
|
||||
.is_highres=is_highres, .is_finger_based=is_finger_based, .deviceid=device->deviceid, .sourceid=scroll->sourceid,
|
||||
.is_finger_based=is_finger_based, .deviceid=device->deviceid, .sourceid=scroll->sourceid,
|
||||
};
|
||||
if (is_highres) {
|
||||
d->type_detected = true;
|
||||
d->offset_type = GLFW_SCROLL_OFFEST_HIGHRES;
|
||||
}
|
||||
memcpy(d->name, device->name, MIN(sizeof(d->name)-1, strlen(device->name)));
|
||||
}
|
||||
if (d->num_valuators >= arraysz(d->valuators)) continue;
|
||||
|
||||
4
glfw/x11_platform.h
vendored
4
glfw/x11_platform.h
vendored
@@ -239,12 +239,14 @@ typedef struct XIScrollValuator {
|
||||
} XIScrollValuator;
|
||||
|
||||
typedef struct XIScrollDevice {
|
||||
bool is_highres;
|
||||
bool is_finger_based;
|
||||
bool type_detected;
|
||||
int deviceid, sourceid;
|
||||
XIScrollValuator valuators[8];
|
||||
unsigned num_valuators;
|
||||
char name[32];
|
||||
unsigned num_events;
|
||||
GLFWOffsetType offset_type;
|
||||
} XIScrollDevice;
|
||||
|
||||
typedef struct XdndSelectionRequest {
|
||||
|
||||
38
glfw/x11_window.c
vendored
38
glfw/x11_window.c
vendored
@@ -29,6 +29,7 @@
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "internal.h"
|
||||
#include "math.h"
|
||||
#include "backend_utils.h"
|
||||
#include "linux_notify.h"
|
||||
#include "../kitty/monotonic.h"
|
||||
@@ -1414,6 +1415,11 @@ handle_mouse_move_event(_GLFWwindow *window, const int x, const int y) {
|
||||
window->x11.lastCursorPosY = y;
|
||||
}
|
||||
|
||||
static bool
|
||||
number_has_fractional_part(double x) {
|
||||
return fabs(x - round(x)) >= 1e-6;
|
||||
}
|
||||
|
||||
static void
|
||||
handle_xi_motion_event(_GLFWwindow *window, XIDeviceEvent *de) {
|
||||
XIScrollDevice *d = NULL;
|
||||
@@ -1441,17 +1447,35 @@ handle_xi_motion_event(_GLFWwindow *window, XIDeviceEvent *de) {
|
||||
scroll_valuator_found = true;
|
||||
double delta = value - v->value;
|
||||
v->value = value;
|
||||
if (v->is_vertical) delta *= -1;
|
||||
delta *= -1;
|
||||
double *off = v->is_vertical ? &yOffset : &xOffset;
|
||||
*off = delta;
|
||||
if (!d->is_highres) {
|
||||
if (v->increment == 120.) type = GLFW_SCROLL_OFFEST_V120;
|
||||
else {
|
||||
type = GLFW_SCROLL_OFFSET_LINES;
|
||||
if (v->increment != 0) *off /= v->increment;
|
||||
d->num_events++;
|
||||
if (!d->type_detected) {
|
||||
if (v->increment == 120.) {
|
||||
d->type_detected = true;
|
||||
d->offset_type = GLFW_SCROLL_OFFEST_V120;
|
||||
} else {
|
||||
bool delta_is_fractional = number_has_fractional_part(delta);
|
||||
if (delta_is_fractional) {
|
||||
if (fabs(delta * 120 - round(delta * 120)) < 0.01) {
|
||||
d->type_detected = d->num_events > 2;
|
||||
d->offset_type = GLFW_SCROLL_OFFEST_V120;
|
||||
} else {
|
||||
d->type_detected = true;
|
||||
d->offset_type = GLFW_SCROLL_OFFEST_HIGHRES;
|
||||
}
|
||||
} else {
|
||||
d->type_detected = d->num_events > 2;
|
||||
d->offset_type = GLFW_SCROLL_OFFSET_LINES;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (d->offset_type == GLFW_SCROLL_OFFSET_LINES) {
|
||||
if (v->increment != 0) *off /= v->increment;
|
||||
}
|
||||
}
|
||||
type = d->offset_type;
|
||||
if (xOffset != 0 || yOffset != 0) {
|
||||
// Get keyboard modifiers
|
||||
int mods = translateState(de->mods.effective);
|
||||
@@ -1465,7 +1489,7 @@ handle_xi_motion_event(_GLFWwindow *window, XIDeviceEvent *de) {
|
||||
};
|
||||
|
||||
// For high-resolution, finger-based scrolling, use timer-based momentum scrolling
|
||||
if (d->is_highres && d->is_finger_based && type == GLFW_SCROLL_OFFEST_HIGHRES) {
|
||||
if (d->is_finger_based && type == GLFW_SCROLL_OFFEST_HIGHRES) {
|
||||
// Reset the timer on each scroll event
|
||||
x11_cancel_momentum_scroll_timer();
|
||||
|
||||
|
||||
2
go.mod
2
go.mod
@@ -24,7 +24,7 @@ require (
|
||||
github.com/zeebo/xxh3 v1.1.0
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b
|
||||
golang.org/x/image v0.36.0
|
||||
golang.org/x/sys v0.41.0
|
||||
golang.org/x/sys v0.42.0
|
||||
golang.org/x/text v0.34.0
|
||||
howett.net/plist v1.0.1
|
||||
)
|
||||
|
||||
4
go.sum
4
go.sum
@@ -74,8 +74,8 @@ golang.org/x/image v0.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc=
|
||||
golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -790,7 +790,7 @@ func (h *Handler) triggerSelected() {
|
||||
|
||||
func main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) {
|
||||
if tty.IsTerminal(os.Stdin.Fd()) {
|
||||
return 1, fmt.Errorf("This kitten must only be run via a mapping in kitty.conf")
|
||||
return 1, fmt.Errorf("This kitten must only be run via the command_palette action mapped to a shortcut in kitty.conf")
|
||||
}
|
||||
output := tui.KittenOutputSerializer()
|
||||
lp, err := loop.New()
|
||||
|
||||
@@ -65,6 +65,10 @@ Can be specified multiple times to use multiple patterns. For example::
|
||||
''',
|
||||
)
|
||||
|
||||
opt('mark_moved_lines', 'yes', option_type='to_bool', long_text='''
|
||||
Highlight lines that are moved, that is removed from the left and added to the right
|
||||
differently, using the :opt:`moved_bg` color.''')
|
||||
|
||||
opt('word_diff_mode', 'words', choices=('words', 'central'),
|
||||
long_text='''
|
||||
The algorithm to use for highlighting which parts of changed lines differ.
|
||||
@@ -152,6 +156,12 @@ opt('dark_highlight_added_bg', '#31503d', option_type='to_color')
|
||||
opt('added_margin_bg', '#cdffd8', option_type='to_color')
|
||||
opt('dark_added_margin_bg', '#31503d', option_type='to_color')
|
||||
|
||||
opt('moved_bg', '#fffde7', option_type='to_color', long_text='Moved text backgrounds (same text that was removed in one place and added in another)')
|
||||
opt('dark_moved_bg', '#003333', option_type='to_color')
|
||||
|
||||
opt('moved_margin_bg', '#fff3b0', option_type='to_color')
|
||||
opt('dark_moved_margin_bg', '#00495b', option_type='to_color')
|
||||
|
||||
opt('filler_bg', '#fafbfc', option_type='to_color', long_text='Filler (empty) line background')
|
||||
opt('dark_filler_bg', '#262c36', option_type='to_color')
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"sync"
|
||||
|
||||
parallel "github.com/kovidgoyal/go-parallel"
|
||||
"github.com/kovidgoyal/kitty/tools/simdstring"
|
||||
"github.com/kovidgoyal/kitty/tools/utils"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/images"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/shlex"
|
||||
@@ -336,6 +337,7 @@ func (self *Hunk) finalize(left_lines, right_lines []string) error {
|
||||
type Patch struct {
|
||||
all_hunks []*Hunk
|
||||
largest_line_number, added_count, removed_count int
|
||||
left_moved_lines, right_moved_lines *utils.Set[int]
|
||||
}
|
||||
|
||||
func (self *Patch) Len() int { return len(self.all_hunks) }
|
||||
@@ -451,6 +453,54 @@ func (self *Patch) compute_centers(left_lines, right_lines []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use SIMD to efficiently find non-blank lines: a line is non-blank if it
|
||||
// contains at least one character that is not a space or tab.
|
||||
func is_non_blank(text string) bool {
|
||||
return simdstring.NotIndexByte2String(text, ' ', '\t') >= 0
|
||||
}
|
||||
|
||||
func (self *Patch) detect_moved_lines(left_lines, right_lines []string) {
|
||||
// Build maps from line text to lists of line numbers for removed and added lines.
|
||||
removed := make(map[string][]int, len(left_lines)) // text -> left line numbers
|
||||
added := make(map[string][]int, len(right_lines)) // text -> right line numbers
|
||||
for _, hunk := range self.all_hunks {
|
||||
for _, chunk := range hunk.chunks {
|
||||
if !chunk.is_context {
|
||||
for i := 0; i < chunk.left_count; i++ {
|
||||
lnum := chunk.left_start + i
|
||||
text := left_lines[lnum]
|
||||
if is_non_blank(text) {
|
||||
removed[text] = append(removed[text], lnum)
|
||||
}
|
||||
}
|
||||
for i := 0; i < chunk.right_count; i++ {
|
||||
rnum := chunk.right_start + i
|
||||
text := right_lines[rnum]
|
||||
if is_non_blank(text) {
|
||||
added[text] = append(added[text], rnum)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Lines that appear in both removed and added sets are moved lines. When a
|
||||
// line appears multiple times on each side, only min(left_count,
|
||||
// right_count) occurrences are marked as moved.
|
||||
self.left_moved_lines = utils.NewSet[int]()
|
||||
self.right_moved_lines = utils.NewSet[int]()
|
||||
for text, lnums := range removed {
|
||||
if rnums, ok := added[text]; ok {
|
||||
count := min(len(lnums), len(rnums))
|
||||
for _, lnum := range lnums[:count] {
|
||||
self.left_moved_lines.Add(lnum)
|
||||
}
|
||||
for _, rnum := range rnums[:count] {
|
||||
self.right_moved_lines.Add(rnum)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parse_patch(raw string, left_lines, right_lines []string) (ans *Patch, err error) {
|
||||
ans = &Patch{all_hunks: make([]*Hunk, 0, 32)}
|
||||
var current_hunk *Hunk
|
||||
@@ -486,6 +536,9 @@ func parse_patch(raw string, left_lines, right_lines []string) (ans *Patch, err
|
||||
ans.largest_line_number = ans.all_hunks[len(ans.all_hunks)-1].largest_line_number
|
||||
}
|
||||
err = ans.compute_centers(left_lines, right_lines)
|
||||
if err == nil && conf.Mark_moved_lines {
|
||||
ans.detect_moved_lines(left_lines, right_lines)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ type HalfScreenLine struct {
|
||||
marked_up_margin_text string
|
||||
marked_up_text string
|
||||
is_filler bool
|
||||
is_moved bool
|
||||
cached_wcswidth int
|
||||
}
|
||||
|
||||
@@ -81,6 +82,9 @@ func (self *LogicalLine) render_screen_line(n int, lp *loop.Loop, margin_size, c
|
||||
if sl.left.is_filler {
|
||||
left_margin = format_as_sgr.margin_filler + left_margin
|
||||
left_text = format_as_sgr.filler + left_text
|
||||
} else if sl.left.is_moved {
|
||||
left_margin = format_as_sgr.moved_margin + left_margin
|
||||
left_text = format_as_sgr.moved + left_text
|
||||
} else {
|
||||
switch self.line_type {
|
||||
case CHANGE_LINE, IMAGE_LINE:
|
||||
@@ -104,6 +108,9 @@ func (self *LogicalLine) render_screen_line(n int, lp *loop.Loop, margin_size, c
|
||||
if sl.right.is_filler {
|
||||
right_margin = format_as_sgr.margin_filler + right_margin
|
||||
right_text = format_as_sgr.filler + right_text
|
||||
} else if sl.right.is_moved {
|
||||
right_margin = format_as_sgr.moved_margin + right_margin
|
||||
right_text = format_as_sgr.moved + right_text
|
||||
} else {
|
||||
switch self.line_type {
|
||||
case CHANGE_LINE, IMAGE_LINE:
|
||||
@@ -156,7 +163,7 @@ func place_in(text string, sz int) string {
|
||||
}
|
||||
|
||||
var format_as_sgr struct {
|
||||
title, margin, added, removed, added_margin, removed_margin, filler, margin_filler, hunk_margin, hunk, selection, search string
|
||||
title, margin, added, removed, added_margin, removed_margin, filler, margin_filler, hunk_margin, hunk, selection, search, moved, moved_margin string
|
||||
}
|
||||
|
||||
var statusline_format, added_count_format, removed_count_format, message_format func(...any) string
|
||||
@@ -175,6 +182,8 @@ type ResolvedColors struct {
|
||||
Margin_bg style.RGBA
|
||||
Margin_fg style.RGBA
|
||||
Margin_filler_bg style.NullableColor
|
||||
Moved_bg style.RGBA
|
||||
Moved_margin_bg style.RGBA
|
||||
Removed_bg style.RGBA
|
||||
Removed_margin_bg style.RGBA
|
||||
Search_bg style.RGBA
|
||||
@@ -202,6 +211,8 @@ func create_formatters() {
|
||||
rc.Margin_bg = conf.Dark_margin_bg
|
||||
rc.Margin_fg = conf.Dark_margin_fg
|
||||
rc.Margin_filler_bg = conf.Dark_margin_filler_bg
|
||||
rc.Moved_bg = conf.Dark_moved_bg
|
||||
rc.Moved_margin_bg = conf.Dark_moved_margin_bg
|
||||
rc.Removed_bg = conf.Dark_removed_bg
|
||||
rc.Removed_margin_bg = conf.Dark_removed_margin_bg
|
||||
rc.Search_bg = conf.Dark_search_bg
|
||||
@@ -223,6 +234,8 @@ func create_formatters() {
|
||||
rc.Margin_bg = conf.Margin_bg
|
||||
rc.Margin_fg = conf.Margin_fg
|
||||
rc.Margin_filler_bg = conf.Margin_filler_bg
|
||||
rc.Moved_bg = conf.Moved_bg
|
||||
rc.Moved_margin_bg = conf.Moved_margin_bg
|
||||
rc.Removed_bg = conf.Removed_bg
|
||||
rc.Removed_margin_bg = conf.Removed_margin_bg
|
||||
rc.Search_bg = conf.Search_bg
|
||||
@@ -248,6 +261,8 @@ func create_formatters() {
|
||||
format_as_sgr.added_margin = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Margin_fg.AsRGBSharp(), rc.Added_margin_bg.AsRGBSharp()))
|
||||
format_as_sgr.removed = only_open("bg=" + rc.Removed_bg.AsRGBSharp())
|
||||
format_as_sgr.removed_margin = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Margin_fg.AsRGBSharp(), rc.Removed_margin_bg.AsRGBSharp()))
|
||||
format_as_sgr.moved = only_open("bg=" + rc.Moved_bg.AsRGBSharp())
|
||||
format_as_sgr.moved_margin = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Margin_fg.AsRGBSharp(), rc.Moved_margin_bg.AsRGBSharp()))
|
||||
format_as_sgr.title = only_open(fmt.Sprintf("fg=%s bg=%s bold", rc.Title_fg.AsRGBSharp(), rc.Title_bg.AsRGBSharp()))
|
||||
format_as_sgr.margin = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Margin_fg.AsRGBSharp(), rc.Margin_bg.AsRGBSharp()))
|
||||
format_as_sgr.hunk = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Margin_fg.AsRGBSharp(), rc.Hunk_bg.AsRGBSharp()))
|
||||
@@ -533,6 +548,8 @@ type DiffData struct {
|
||||
available_cols, margin_size int
|
||||
|
||||
left_lines, right_lines []string
|
||||
left_moved_lines *utils.Set[int]
|
||||
right_moved_lines *utils.Set[int]
|
||||
}
|
||||
|
||||
func hunk_title(hunk *Hunk) string {
|
||||
@@ -567,7 +584,7 @@ func splitlines(text string, width int) []string {
|
||||
return style.WrapTextAsLines(text, width, style.WrapOptions{})
|
||||
}
|
||||
|
||||
func render_half_line(line_number int, line, ltype string, available_cols int, center Center, ans []HalfScreenLine) []HalfScreenLine {
|
||||
func render_half_line(line_number int, line, ltype string, available_cols int, center Center, is_moved bool, ans []HalfScreenLine) []HalfScreenLine {
|
||||
var regions []Region
|
||||
if ltype == "remove" {
|
||||
regions = center.left_regions
|
||||
@@ -583,7 +600,7 @@ func render_half_line(line_number int, line, ltype string, available_cols int, c
|
||||
}
|
||||
lnum := strconv.Itoa(line_number + 1)
|
||||
for _, sc := range splitlines(line, available_cols) {
|
||||
ans = append(ans, HalfScreenLine{marked_up_margin_text: lnum, marked_up_text: sc})
|
||||
ans = append(ans, HalfScreenLine{marked_up_margin_text: lnum, marked_up_text: sc, is_moved: is_moved})
|
||||
lnum = ""
|
||||
}
|
||||
return ans
|
||||
@@ -601,13 +618,15 @@ func lines_for_diff_chunk(data *DiffData, _ int, chunk *Chunk, _ int, ans []*Log
|
||||
}
|
||||
if i < chunk.left_count {
|
||||
left_lnum = chunk.left_start + i
|
||||
ll = render_half_line(left_lnum, data.left_lines[left_lnum], "remove", data.available_cols, center, ll)
|
||||
left_is_moved := data.left_moved_lines != nil && data.left_moved_lines.Has(left_lnum)
|
||||
ll = render_half_line(left_lnum, data.left_lines[left_lnum], "remove", data.available_cols, center, left_is_moved, ll)
|
||||
left_lnum++
|
||||
}
|
||||
|
||||
if i < chunk.right_count {
|
||||
right_lnum = chunk.right_start + i
|
||||
rl = render_half_line(right_lnum, data.right_lines[right_lnum], "add", data.available_cols, center, rl)
|
||||
right_is_moved := data.right_moved_lines != nil && data.right_moved_lines.Has(right_lnum)
|
||||
rl = render_half_line(right_lnum, data.right_lines[right_lnum], "add", data.available_cols, center, right_is_moved, rl)
|
||||
right_lnum++
|
||||
}
|
||||
|
||||
@@ -663,7 +682,10 @@ func lines_for_diff(left_path string, right_path string, patch *Patch, columns,
|
||||
return append(ans, &ht), nil
|
||||
}
|
||||
available_cols := columns/2 - margin_size
|
||||
data := DiffData{left_path: left_path, right_path: right_path, available_cols: available_cols, margin_size: margin_size}
|
||||
data := DiffData{
|
||||
left_path: left_path, right_path: right_path, available_cols: available_cols, margin_size: margin_size,
|
||||
left_moved_lines: patch.left_moved_lines, right_moved_lines: patch.right_moved_lines,
|
||||
}
|
||||
if left_path != "" {
|
||||
data.left_lines, err = highlighted_lines_for_path(left_path)
|
||||
if err != nil {
|
||||
@@ -720,7 +742,7 @@ func all_lines(path string, columns, margin_size int, is_add bool, ans []*Logica
|
||||
}
|
||||
for line_number, line := range lines {
|
||||
hlines := make([]HalfScreenLine, 0, 8)
|
||||
hlines = render_half_line(line_number, line, ltype, available_cols, Center{}, hlines)
|
||||
hlines = render_half_line(line_number, line, ltype, available_cols, Center{}, false, hlines)
|
||||
l := ll
|
||||
if is_add {
|
||||
l.right_reference.linenum = line_number + 1
|
||||
|
||||
@@ -67,3 +67,4 @@ void get_cocoa_key_equivalent(uint32_t, int, char *key, size_t key_sz, int*);
|
||||
void set_cocoa_pending_action(CocoaPendingAction action, const char*);
|
||||
void cocoa_report_live_notifications(const char* ident);
|
||||
void cocoa_set_dock_badge(const char *label);
|
||||
void cocoa_clear_dock_badge_if_set(void);
|
||||
|
||||
@@ -1341,15 +1341,23 @@ cocoa_show_progress_bar_on_dock_icon(PyObject *self UNUSED, PyObject *args) {
|
||||
|
||||
// Dock badge {{{
|
||||
|
||||
static bool dock_badge_is_set = false;
|
||||
|
||||
void
|
||||
cocoa_set_dock_badge(const char *label) {
|
||||
@autoreleasepool {
|
||||
NSDockTile *dockTile = [NSApp dockTile];
|
||||
[dockTile setBadgeLabel:label ? @(label) : nil];
|
||||
[dockTile display];
|
||||
dock_badge_is_set = (label != NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
cocoa_clear_dock_badge_if_set(void) {
|
||||
if (dock_badge_is_set) cocoa_set_dock_badge(NULL);
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
|
||||
@@ -22,7 +22,7 @@ class Version(NamedTuple):
|
||||
|
||||
appname: str = 'kitty'
|
||||
kitty_face = '🐱'
|
||||
version: Version = Version(0, 46, 0)
|
||||
version: Version = Version(0, 46, 1)
|
||||
str_version: str = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos: bool = 'darwin' in _plat
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import ctypes
|
||||
import os
|
||||
import sys
|
||||
from collections.abc import Callable, Generator
|
||||
@@ -218,6 +217,7 @@ def set_font_family(opts: Options | None = None, override_font_size: float | Non
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import ctypes
|
||||
CBufType = ctypes.Array[ctypes.c_ubyte]
|
||||
else:
|
||||
CBufType = None
|
||||
|
||||
28
kitty/glfw.c
28
kitty/glfw.c
@@ -518,6 +518,9 @@ update_modifier_state_on_modifier_key_event(GLFWkeyevent *ev, int key_modifier,
|
||||
static void
|
||||
key_callback(GLFWwindow *w, GLFWkeyevent *ev) {
|
||||
if (!set_callback_window(w)) return;
|
||||
#ifdef __APPLE__
|
||||
cocoa_clear_dock_badge_if_set();
|
||||
#endif
|
||||
#ifndef __APPLE__
|
||||
bool is_left;
|
||||
int key_modifier = key_to_modifier(ev->key, &is_left);
|
||||
@@ -554,6 +557,9 @@ cursor_enter_callback(GLFWwindow *w, int entered) {
|
||||
static void
|
||||
mouse_button_callback(GLFWwindow *w, int button, int action, int mods) {
|
||||
if (!set_callback_window(w)) return;
|
||||
#ifdef __APPLE__
|
||||
cocoa_clear_dock_badge_if_set();
|
||||
#endif
|
||||
monotonic_t now = monotonic();
|
||||
cursor_active_callback(now);
|
||||
mods_at_last_key_or_button_event = mods;
|
||||
@@ -676,6 +682,23 @@ is_droppable_mime(const char *mime) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t
|
||||
remove_duplicate_mimes(const char **mimes, size_t count) {
|
||||
// Use simple O(n²) scan since lists are typically small
|
||||
size_t new_count = 0;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
bool is_duplicate = false;
|
||||
for (size_t j = 0; j < new_count; j++) {
|
||||
if (strcmp(mimes[i], mimes[j]) == 0) { is_duplicate = true; break; }
|
||||
}
|
||||
if (!is_duplicate) {
|
||||
if (new_count != i) SWAP(mimes[i], mimes[new_count]);
|
||||
new_count++;
|
||||
}
|
||||
}
|
||||
return new_count;
|
||||
}
|
||||
|
||||
static void
|
||||
update_allowed_mimes_for_drop(GLFWDropEvent *ev) {
|
||||
if (ev->mimes && ev->num_mimes) {
|
||||
@@ -728,6 +751,7 @@ read_drop_data(GLFWwindow *window, GLFWDropEvent *ev) {
|
||||
PyObject *data = chunk;
|
||||
RAII_PyObject(existing, PyDict_GetItemString(global_state.drop_dest.data, ev->mimes[0]));
|
||||
if (existing) {
|
||||
existing = Py_NewRef(existing); // because PyBytes_Concat steals a reference
|
||||
PyBytes_Concat(&existing, chunk);
|
||||
data = existing;
|
||||
}
|
||||
@@ -775,6 +799,7 @@ on_drop(GLFWwindow *window, GLFWDropEvent *ev) {
|
||||
break;
|
||||
}
|
||||
update_allowed_mimes_for_drop(ev);
|
||||
ev->num_mimes = remove_duplicate_mimes(ev->mimes, ev->num_mimes);
|
||||
global_state.drop_dest.num_left = ev->num_mimes;
|
||||
if (!global_state.drop_dest.num_left || !(global_state.drop_dest.data = PyDict_New())) {
|
||||
ev->finish_drop(window, GLFW_DRAG_OPERATION_GENERIC);
|
||||
@@ -1064,6 +1089,7 @@ set_os_window_icon(PyObject UNUSED *self, PyObject *args) {
|
||||
if(!PyArg_ParseTuple(args, "K|O", &id, &what)) return NULL;
|
||||
OSWindow *os_window = os_window_for_id(id);
|
||||
if (!os_window) { PyErr_Format(PyExc_KeyError, "No OS Window with id: %llu", id); return NULL; }
|
||||
if (os_window->is_layer_shell && global_state.is_wayland) Py_RETURN_NONE;
|
||||
if (!what || what == Py_None) {
|
||||
glfwSetWindowIcon(os_window->handle, 0, NULL);
|
||||
Py_RETURN_NONE;
|
||||
@@ -1704,7 +1730,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) {
|
||||
glfwCocoaSetWindowResizeCallback(glfw_window, cocoa_os_window_resized);
|
||||
#endif
|
||||
send_prerendered_sprites_for_window(w);
|
||||
if (logo.pixels && logo.width && logo.height) glfwSetWindowIcon(glfw_window, 1, &logo);
|
||||
if (logo.pixels && logo.width && logo.height && (!lsc || !global_state.is_wayland)) glfwSetWindowIcon(glfw_window, 1, &logo);
|
||||
set_glfw_mouse_pointer_shape_in_window(glfw_window, OPT(default_pointer_shape));
|
||||
update_os_window_viewport(w, false);
|
||||
glfwSetWindowPosCallback(glfw_window, window_pos_callback);
|
||||
|
||||
@@ -1388,7 +1388,7 @@ pixel_scroll_enabled_for_screen(const Screen *screen) {
|
||||
|
||||
void
|
||||
scroll_event(const GLFWScrollEvent *ev) {
|
||||
debug("\x1b[36mScroll\x1b[m %s x: %f y: %f momentum: %s modifiers: %s\n", scroll_offset_type(ev->offset_type), ev->x_offset, ev->y_offset, scroll_phase(ev->momentum_type), format_mods(ev->keyboard_modifiers));
|
||||
debug("\x1b[36mScroll\x1b[m type=%s x: %f y: %f momentum: %s modifiers: %s\n", scroll_offset_type(ev->offset_type), ev->x_offset, ev->y_offset, scroll_phase(ev->momentum_type), format_mods(ev->keyboard_modifiers));
|
||||
static id_type window_for_momentum_scroll = 0;
|
||||
static bool main_screen_for_momentum_scroll = false;
|
||||
// allow scroll events even if window is not currently focused (in
|
||||
@@ -1461,14 +1461,14 @@ scroll_event(const GLFWScrollEvent *ev) {
|
||||
write_escape_code_to_child(screen, ESC_CSI, mouse_event_buf);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (screen->linebuf == screen->main_linebuf) {
|
||||
screen_history_scroll(screen, abs(s), upwards);
|
||||
if (screen->selections.in_progress) update_drag(w);
|
||||
} else {
|
||||
if (screen->linebuf == screen->main_linebuf) {
|
||||
screen_history_scroll(screen, abs(s), upwards);
|
||||
if (screen->selections.in_progress) update_drag(w);
|
||||
}
|
||||
else fake_scroll(w, abs(s), upwards);
|
||||
}
|
||||
else fake_scroll(w, abs(s), upwards);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ev->x_offset != 0.0) {
|
||||
|
||||
@@ -1795,7 +1795,9 @@ opt('tab_bar_margin_color', 'none',
|
||||
long_text='''
|
||||
Color for the tab bar margin area. Defaults to using the terminal background
|
||||
color for margins above and below the tab bar. For side margins the default
|
||||
color is chosen to match the background color of the neighboring tab.
|
||||
color is chosen to match the background color of the neighboring tab, unless
|
||||
the window is translucent, in which case the default background is used as it
|
||||
looks better.
|
||||
''')
|
||||
|
||||
opt(
|
||||
|
||||
@@ -19,6 +19,7 @@ from .fast_data_types import (
|
||||
Color,
|
||||
Region,
|
||||
Screen,
|
||||
background_opacity_of,
|
||||
cell_size_for_window,
|
||||
get_boss,
|
||||
get_options,
|
||||
@@ -687,7 +688,7 @@ class TabBar:
|
||||
if opts.tab_bar_margin_height.outer:
|
||||
blank_rects.append(Border(0, tab_bar.bottom, vw, vh, bg))
|
||||
if opts.tab_bar_margin_height.inner:
|
||||
blank_rects.append(Border(0, central.bottom, vw, vh, bg))
|
||||
blank_rects.append(Border(0, central.bottom, vw, tab_bar.top, bg))
|
||||
else: # top
|
||||
if opts.tab_bar_margin_height.outer:
|
||||
blank_rects.append(Border(0, 0, vw, tab_bar.top, bg))
|
||||
@@ -695,13 +696,14 @@ class TabBar:
|
||||
blank_rects.append(Border(0, tab_bar.bottom, vw, central.top, bg))
|
||||
g = self.window_geometry
|
||||
left_bg = right_bg = bg
|
||||
if opts.tab_bar_margin_color is None or opts.tab_bar_margin_width == 0:
|
||||
if opts.tab_bar_margin_color is None and (
|
||||
opacity := background_opacity_of(self.os_window_id)) is not None and opacity >= 1:
|
||||
left_bg = BorderColor.tab_bar_left_edge_color
|
||||
right_bg = BorderColor.tab_bar_right_edge_color
|
||||
if g.left > 0:
|
||||
blank_rects.append(Border(0, g.top, g.left, g.bottom, left_bg))
|
||||
if g.right - 1 < vw:
|
||||
blank_rects.append(Border(g.right - 1, g.top, vw, g.bottom, right_bg))
|
||||
if g.right < vw:
|
||||
blank_rects.append(Border(g.right, g.top, vw, g.bottom, right_bg))
|
||||
self.blank_rects = tuple(blank_rects)
|
||||
|
||||
def layout(self) -> None:
|
||||
|
||||
@@ -1251,7 +1251,7 @@ class Window:
|
||||
try:
|
||||
parts = tuple(map(int, raw_data.split(';')))[1:]
|
||||
except Exception:
|
||||
log_error(f'Ignoring malmormed OSC 9;4 progress report: {raw_data!r}')
|
||||
log_error(f'Ignoring malformed OSC 9;4 progress report: {raw_data!r}')
|
||||
return
|
||||
self.progress.update(*parts[:2])
|
||||
if (tab := self.tabref()) is not None:
|
||||
@@ -2018,7 +2018,7 @@ class Window:
|
||||
def handle_dangerous_paste_confirmation(self, unsanitized: bytes, sanitized: bytes, choice: str) -> None:
|
||||
if choice == 's':
|
||||
self.paste_text(sanitized)
|
||||
elif choice == 'p':
|
||||
elif choice == 'a':
|
||||
self.paste_text(unsanitized)
|
||||
|
||||
def handle_large_paste_confirmation(self, btext: bytes, confirmed: bool) -> None:
|
||||
|
||||
@@ -2,10 +2,14 @@
|
||||
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import textwrap
|
||||
import unittest
|
||||
from functools import partial
|
||||
|
||||
@@ -35,6 +39,103 @@ class TestBuild(BaseTest):
|
||||
for name in 'cell border bgimage tint graphics'.split():
|
||||
Program(name)
|
||||
|
||||
def test_macos_dictation_forwarding(self) -> None:
|
||||
from kitty.constants import glfw_path, is_macos
|
||||
if not is_macos or not shutil.which('clang'):
|
||||
self.skipTest('Dictation smoke test is macOS only and requires clang')
|
||||
cocoa_module = glfw_path('cocoa')
|
||||
probe = textwrap.dedent('''\
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <dlfcn.h>
|
||||
#import <objc/runtime.h>
|
||||
#import <objc/message.h>
|
||||
|
||||
static int start_calls = 0;
|
||||
static int stop_calls = 0;
|
||||
static id last_sender = nil;
|
||||
|
||||
static void fake_start_dictation(id self, SEL _cmd, id sender) {
|
||||
(void)self; (void)_cmd;
|
||||
start_calls++;
|
||||
last_sender = sender;
|
||||
}
|
||||
|
||||
static void fake_stop_dictation(id self, SEL _cmd, id sender) {
|
||||
(void)self; (void)_cmd;
|
||||
stop_calls++;
|
||||
last_sender = sender;
|
||||
}
|
||||
|
||||
static void require_true(BOOL condition, const char *message) {
|
||||
if (!condition) {
|
||||
fprintf(stderr, "FAIL: %s\\n", message);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
@autoreleasepool {
|
||||
[NSApplication sharedApplication];
|
||||
void *handle = dlopen(@@COCOA_MODULE@@, RTLD_NOW | RTLD_GLOBAL);
|
||||
require_true(handle != NULL, dlerror());
|
||||
|
||||
SEL start = NSSelectorFromString(@"startDictation:");
|
||||
SEL stop = NSSelectorFromString(@"stopDictation:");
|
||||
Method start_method = class_getInstanceMethod([NSApplication class], start);
|
||||
Method stop_method = class_getInstanceMethod([NSApplication class], stop);
|
||||
require_true(start_method != NULL, "NSApplication startDictation: missing");
|
||||
require_true(stop_method != NULL, "NSApplication stopDictation: missing");
|
||||
method_setImplementation(start_method, (IMP)fake_start_dictation);
|
||||
method_setImplementation(stop_method, (IMP)fake_stop_dictation);
|
||||
|
||||
Class view_cls = NSClassFromString(@"GLFWContentView");
|
||||
Class context_cls = NSClassFromString(@"GLFWTextInputContext");
|
||||
require_true(view_cls != Nil, "GLFWContentView class not loaded");
|
||||
require_true(context_cls != Nil, "GLFWTextInputContext class not loaded");
|
||||
|
||||
SEL init_with_glfw_window = NSSelectorFromString(@"initWithGlfwWindow:");
|
||||
id view = ((id (*)(id, SEL, void *)) objc_msgSend)([view_cls alloc], init_with_glfw_window, NULL);
|
||||
require_true(view != nil, "GLFWContentView initWithGlfwWindow: failed");
|
||||
require_true([view respondsToSelector:start], "GLFWContentView does not expose startDictation:");
|
||||
require_true([view respondsToSelector:stop], "GLFWContentView does not expose stopDictation:");
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
[view performSelector:start withObject:@"menu sender"];
|
||||
#pragma clang diagnostic pop
|
||||
require_true(start_calls == 1, "startDictation: action was not forwarded to NSApplication");
|
||||
require_true([(id)last_sender isEqual:@"menu sender"], "startDictation: forwarded wrong sender");
|
||||
|
||||
[view doCommandBySelector:start];
|
||||
require_true(start_calls == 2, "doCommandBySelector:startDictation: was swallowed");
|
||||
require_true(last_sender == view, "doCommandBySelector:startDictation: should forward self as sender");
|
||||
|
||||
id context = [view inputContext];
|
||||
require_true(context != nil, "GLFWContentView inputContext missing");
|
||||
require_true([context isKindOfClass:context_cls], "GLFWContentView inputContext has wrong class");
|
||||
[context doCommandBySelector:stop];
|
||||
require_true(stop_calls == 1, "GLFWTextInputContext did not forward stopDictation:");
|
||||
require_true(last_sender == nil, "GLFWTextInputContext should forward nil sender");
|
||||
|
||||
printf("dictation forwarding probe passed\\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
''').replace('@@COCOA_MODULE@@', json.dumps(cocoa_module))
|
||||
with tempfile.TemporaryDirectory() as tdir:
|
||||
src = os.path.join(tdir, 'dictation_probe.m')
|
||||
exe = os.path.join(tdir, 'dictation_probe')
|
||||
with open(src, 'w') as f:
|
||||
f.write(probe)
|
||||
cp = subprocess.run(
|
||||
['clang', '-framework', 'AppKit', src, '-o', exe],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
|
||||
)
|
||||
self.assertEqual(cp.returncode, 0, cp.stdout)
|
||||
cp = subprocess.run([exe], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
|
||||
self.assertEqual(cp.returncode, 0, cp.stdout)
|
||||
self.assertIn('dictation forwarding probe passed', cp.stdout)
|
||||
|
||||
def test_glfw_modules(self) -> None:
|
||||
from kitty.constants import glfw_path, is_macos
|
||||
linux_backends = ['x11']
|
||||
|
||||
12
setup.py
12
setup.py
@@ -949,7 +949,7 @@ def add_builtin_fonts(args: Options) -> None:
|
||||
break
|
||||
else:
|
||||
lines = subprocess.check_output([
|
||||
'fc-match', '--format', '%{file}\n%{postscriptname}', f'term:postscriptname={psname}', 'file', 'postscriptname']).decode().splitlines()
|
||||
'fc-list', '--format', '%{file}\n%{postscriptname}', f':postscriptname={psname}']).decode().splitlines()
|
||||
if len(lines) != 2:
|
||||
raise SystemExit(f'fc-match returned unexpected output: {lines}')
|
||||
if lines[1] != psname:
|
||||
@@ -1286,11 +1286,15 @@ def build_static_kittens(
|
||||
cmd = go + ['build', '-v']
|
||||
vcs_rev = args.vcs_rev or get_vcs_rev()
|
||||
ld_flags: List[str] = []
|
||||
binary_data_flags = [f"-X kitty.VCSRevision={vcs_rev}"]
|
||||
with open('go.mod') as f:
|
||||
m = re.search(r'^module\s+(\S+)', f.read(), flags=re.M)
|
||||
assert m is not None
|
||||
modpath = m.group(1).strip()
|
||||
binary_data_flags = [f"-X {modpath}.VCSRevision={vcs_rev}"]
|
||||
if for_freeze:
|
||||
binary_data_flags.append("-X kitty.IsFrozenBuild=true")
|
||||
binary_data_flags.append(f"-X {modpath}.IsFrozenBuild=true")
|
||||
if for_platform:
|
||||
binary_data_flags.append("-X kitty.IsStandaloneBuild=true")
|
||||
binary_data_flags.append(f"-X {modpath}.IsStandaloneBuild=true")
|
||||
if not args.debug:
|
||||
ld_flags.append('-s')
|
||||
ld_flags.append('-w')
|
||||
|
||||
@@ -64,3 +64,45 @@ func BenchmarkIndexByte2(b *testing.B) {
|
||||
t(pos, "scalar")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNotIndexByte(b *testing.B) {
|
||||
t := func(pos int, which string) {
|
||||
// Fill with 'a' and place 'q' (a non-matching byte) at the target position
|
||||
data := haystack('a', 'q', pos)
|
||||
f := NotIndexByte
|
||||
switch which {
|
||||
case "scalar":
|
||||
f = not_index_byte_scalar
|
||||
}
|
||||
b.Run(fmt.Sprintf("%s_sz=%d", which, pos), func(b *testing.B) {
|
||||
for b.Loop() {
|
||||
f(data, 'a')
|
||||
}
|
||||
})
|
||||
}
|
||||
for _, pos := range sizes {
|
||||
t(pos, "simdstring")
|
||||
t(pos, "scalar")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNotIndexByte2(b *testing.B) {
|
||||
t := func(pos int, which string) {
|
||||
// Fill with 'a' and place 'q' (neither 'a' nor 'x') at the target position
|
||||
data := haystack('a', 'q', pos)
|
||||
f := NotIndexByte2
|
||||
switch which {
|
||||
case "scalar":
|
||||
f = not_index_byte2_scalar
|
||||
}
|
||||
b.Run(fmt.Sprintf("%s_sz=%d", which, pos), func(b *testing.B) {
|
||||
for b.Loop() {
|
||||
f(data, 'a', 'x')
|
||||
}
|
||||
})
|
||||
}
|
||||
for _, pos := range sizes {
|
||||
t(pos, "simdstring")
|
||||
t(pos, "scalar")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,6 +404,12 @@ func encode_cmgt16b(a, b, dest Register) (ans uint32) {
|
||||
return 0x271<<21 | b.ARMId()<<16 | 0xd<<10 | a.ARMId()<<5 | dest.ARMId()
|
||||
}
|
||||
|
||||
func encode_not16b(src, dest Register) uint32 {
|
||||
// NOT Vd.16B, Vn.16B (alias of MVN)
|
||||
// Encoding: 0 Q 1 01110 size 10000 00101 10 Rn Rd (Q=1, size=00 for .16B)
|
||||
return 0x6E205800 | (src.ARMId() << 5) | dest.ARMId()
|
||||
}
|
||||
|
||||
func (f *Function) MaskForCountDestructive(vec, ans Register) {
|
||||
// vec is clobbered by this function
|
||||
f.Comment("Count the number of bytes to the first 0xff byte and put the result in", ans)
|
||||
@@ -688,6 +694,24 @@ func (f *Function) Or(a, b, dest Register) {
|
||||
f.AddTrailingComment(dest, "=", a, "|", b, "(bitwise)")
|
||||
}
|
||||
|
||||
func (f *Function) NotSelf(r Register) {
|
||||
if f.ISA.Goarch == ARM64 {
|
||||
f.Comment("Go assembler doesn't support the VMVN instruction, below we have: NOT", r.ARMFullWidth()+",", r.ARMFullWidth())
|
||||
f.instr("WORD", fmt.Sprintf("$0x%x", encode_not16b(r, r)))
|
||||
f.AddTrailingComment(r, "= ~", r, "(bitwise NOT)")
|
||||
return
|
||||
}
|
||||
all_ones := f.Vec(r.Size)
|
||||
defer f.ReleaseReg(all_ones)
|
||||
f.AllOnesRegister(all_ones)
|
||||
if r.Size == 128 {
|
||||
f.instr("PXOR", all_ones, r)
|
||||
} else {
|
||||
f.instr("VPXOR", all_ones, r, r)
|
||||
}
|
||||
f.AddTrailingComment(r, "= ~", r, "(bitwise NOT)")
|
||||
}
|
||||
|
||||
func (f *Function) And(a, b, dest Register) {
|
||||
if f.ISA.Goarch == ARM64 {
|
||||
f.instr("VAND", a.ARMFullWidth(), b.ARMFullWidth(), dest.ARMFullWidth())
|
||||
@@ -1504,6 +1528,54 @@ func (s *State) indexc0() {
|
||||
|
||||
}
|
||||
|
||||
func (s *State) not_index_byte_body(f *Function) {
|
||||
b := f.Vec()
|
||||
f.Set1Epi8("b", b)
|
||||
test_bytes := func(bytes_to_test, test_ans Register) {
|
||||
f.CmpEqEpi8(bytes_to_test, b, test_ans)
|
||||
f.NotSelf(test_ans)
|
||||
}
|
||||
s.index_func(f, test_bytes)
|
||||
}
|
||||
|
||||
func (s *State) not_index_byte() {
|
||||
f := s.NewFunction("not_index_byte_asm", "Find the index of the first byte that is not b", []FunctionParam{{"data", ByteSlice}, {"b", types.Byte}}, []FunctionParam{{"ans", types.Int}})
|
||||
if s.ISA.HasSIMD {
|
||||
s.not_index_byte_body(f)
|
||||
}
|
||||
f = s.NewFunction("not_index_byte_string_asm", "Find the index of the first byte that is not b", []FunctionParam{{"data", types.String}, {"b", types.Byte}}, []FunctionParam{{"ans", types.Int}})
|
||||
if s.ISA.HasSIMD {
|
||||
s.not_index_byte_body(f)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *State) not_index_byte2_body(f *Function) {
|
||||
b1 := f.Vec()
|
||||
b2 := f.Vec()
|
||||
f.Set1Epi8("b1", b1)
|
||||
f.Set1Epi8("b2", b2)
|
||||
test_bytes := func(bytes_to_test, test_ans Register) {
|
||||
f.CmpEqEpi8(bytes_to_test, b1, test_ans)
|
||||
f.CmpEqEpi8(bytes_to_test, b2, bytes_to_test)
|
||||
f.Or(test_ans, bytes_to_test, test_ans)
|
||||
f.NotSelf(test_ans)
|
||||
}
|
||||
s.index_func(f, test_bytes)
|
||||
}
|
||||
|
||||
func (s *State) not_index_byte2() {
|
||||
f := s.NewFunction("not_index_byte2_asm", "Find the index of the first byte that is neither b1 nor b2", []FunctionParam{{"data", ByteSlice}, {"b1", types.Byte}, {"b2", types.Byte}}, []FunctionParam{{"ans", types.Int}})
|
||||
if s.ISA.HasSIMD {
|
||||
s.not_index_byte2_body(f)
|
||||
}
|
||||
f = s.NewFunction("not_index_byte2_string_asm", "Find the index of the first byte that is neither b1 nor b2", []FunctionParam{{"data", types.String}, {"b1", types.Byte}, {"b2", types.Byte}}, []FunctionParam{{"ans", types.Int}})
|
||||
if s.ISA.HasSIMD {
|
||||
s.not_index_byte2_body(f)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *State) Generate() {
|
||||
s.test_load()
|
||||
s.test_set1_epi8()
|
||||
@@ -1516,6 +1588,8 @@ func (s *State) Generate() {
|
||||
s.indexbyte2()
|
||||
s.indexc0()
|
||||
s.indexbyte()
|
||||
s.not_index_byte()
|
||||
s.not_index_byte2()
|
||||
|
||||
s.OutputFunction()
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@ var VectorSize = 1
|
||||
// Return the index at which b first occurs in data. If not found -1 is returned.
|
||||
var IndexByte func(data []byte, b byte) int = index_byte_scalar
|
||||
|
||||
// Return the index at which either a or b first occurs in text. If neither is
|
||||
// found -1 is returned.
|
||||
// Return the index at which b first occurs in text. If not found -1 is returned.
|
||||
var IndexByteString func(text string, b byte) int = index_byte_string_scalar
|
||||
|
||||
// Return the index at which either a or b first occurs in data. If neither is
|
||||
@@ -33,6 +32,18 @@ var IndexC0 func(data []byte) int = index_c0_scalar
|
||||
// Return the index at which the first C0 byte is found or -1 when no such bytes are present.
|
||||
var IndexC0String func(data string) int = index_c0_string_scalar
|
||||
|
||||
// Return the index of the first byte in data that is not equal to b. If all bytes equal b, -1 is returned.
|
||||
var NotIndexByte func(data []byte, b byte) int = not_index_byte_scalar
|
||||
|
||||
// Return the index of the first byte in text that is not equal to b. If all bytes equal b, -1 is returned.
|
||||
var NotIndexByteString func(text string, b byte) int = not_index_byte_string_scalar
|
||||
|
||||
// Return the index of the first byte in data that is neither a nor b. If all bytes are a or b, -1 is returned.
|
||||
var NotIndexByte2 func(data []byte, a, b byte) int = not_index_byte2_scalar
|
||||
|
||||
// Return the index of the first byte in text that is neither a nor b. If all bytes are a or b, -1 is returned.
|
||||
var NotIndexByte2String func(text string, a, b byte) int = not_index_byte2_string_scalar
|
||||
|
||||
func init() {
|
||||
switch runtime.GOARCH {
|
||||
case "amd64":
|
||||
@@ -51,6 +62,10 @@ func init() {
|
||||
IndexByte2String = index_byte2_string_asm_256
|
||||
IndexC0 = index_c0_asm_256
|
||||
IndexC0String = index_c0_string_asm_256
|
||||
NotIndexByte = not_index_byte_asm_256
|
||||
NotIndexByteString = not_index_byte_string_asm_256
|
||||
NotIndexByte2 = not_index_byte2_asm_256
|
||||
NotIndexByte2String = not_index_byte2_string_asm_256
|
||||
VectorSize = 32
|
||||
} else if Have128bit {
|
||||
IndexByte = index_byte_asm_128
|
||||
@@ -59,6 +74,10 @@ func init() {
|
||||
IndexByte2String = index_byte2_string_asm_128
|
||||
IndexC0 = index_c0_asm_128
|
||||
IndexC0String = index_c0_string_asm_128
|
||||
NotIndexByte = not_index_byte_asm_128
|
||||
NotIndexByteString = not_index_byte_string_asm_128
|
||||
NotIndexByte2 = not_index_byte2_asm_128
|
||||
NotIndexByte2String = not_index_byte2_string_asm_128
|
||||
VectorSize = 16
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,6 +244,65 @@ func TestSIMDStringOps(t *testing.T) {
|
||||
index_test([]byte("abc"), 'x')
|
||||
index_test([]byte("abc"), 'b')
|
||||
|
||||
not_index_test := func(haystack []byte, needle byte) {
|
||||
var actual int
|
||||
expected := not_index_byte_scalar(haystack, needle)
|
||||
|
||||
for _, sz := range sizes {
|
||||
switch sz {
|
||||
case 16:
|
||||
actual = not_index_byte_asm_128(haystack, needle)
|
||||
case 32:
|
||||
actual = not_index_byte_asm_256(haystack, needle)
|
||||
}
|
||||
if actual != expected {
|
||||
t.Fatalf("not_index failed in: %#v (%d != %d) at size: %d with needle: %#v", string(haystack), expected, actual, sz, needle)
|
||||
}
|
||||
}
|
||||
}
|
||||
not_index_test(nil, 'a')
|
||||
not_index_test([]byte{}, 'a')
|
||||
not_index_test([]byte("aaa"), 'a')
|
||||
not_index_test([]byte("aaab"), 'a')
|
||||
not_index_test([]byte("baaa"), 'a')
|
||||
not_index_test([]byte("abc"), 'a')
|
||||
for _, sz := range []int{0, 16, 32, 64, 79} {
|
||||
q := strings.Repeat("a", sz) + "b"
|
||||
not_index_test([]byte(q), 'a')
|
||||
not_index_test([]byte(q), 'b')
|
||||
not_index_test([]byte(strings.Repeat("a", sz)), 'a')
|
||||
}
|
||||
|
||||
not_index2_test := func(haystack []byte, a, b byte) {
|
||||
var actual int
|
||||
expected := not_index_byte2_scalar(haystack, a, b)
|
||||
|
||||
for _, sz := range sizes {
|
||||
switch sz {
|
||||
case 16:
|
||||
actual = not_index_byte2_asm_128(haystack, a, b)
|
||||
case 32:
|
||||
actual = not_index_byte2_asm_256(haystack, a, b)
|
||||
}
|
||||
if actual != expected {
|
||||
t.Fatalf("not_index2 failed in: %#v (%d != %d) at size: %d with needles: %#v %#v", string(haystack), expected, actual, sz, a, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
not_index2_test(nil, 'a', 'b')
|
||||
not_index2_test([]byte{}, 'a', 'b')
|
||||
not_index2_test([]byte("aabb"), 'a', 'b')
|
||||
not_index2_test([]byte("aabbc"), 'a', 'b')
|
||||
not_index2_test([]byte("caabb"), 'a', 'b')
|
||||
for _, sz := range []int{0, 16, 32, 64, 79} {
|
||||
q := strings.Repeat("ab", sz) + "c"
|
||||
not_index2_test([]byte(q), 'a', 'b')
|
||||
not_index2_test([]byte(strings.Repeat("ab", sz)), 'a', 'b')
|
||||
for align := range 32 {
|
||||
not_index2_test([]byte(strings.Repeat(" ", align)+q), 'a', 'b')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestIntrinsics(t *testing.T) {
|
||||
|
||||
@@ -57,3 +57,39 @@ func index_c0_string_scalar(data string) int {
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func not_index_byte_scalar(data []byte, b byte) int {
|
||||
for i, ch := range data {
|
||||
if ch != b {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func not_index_byte_string_scalar(data string, b byte) int {
|
||||
for i := 0; i < len(data); i++ {
|
||||
if data[i] != b {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func not_index_byte2_scalar(data []byte, a, b byte) int {
|
||||
for i, ch := range data {
|
||||
if ch != a && ch != b {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func not_index_byte2_string_scalar(data string, a, b byte) int {
|
||||
for i := 0; i < len(data); i++ {
|
||||
if data[i] != a && data[i] != b {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user