diff --git a/configuration.nix b/configuration.nix index 4690eac..84e7329 100644 --- a/configuration.nix +++ b/configuration.nix @@ -88,14 +88,11 @@ xwayland-satellite xdg-desktop-portal-gnome xdg-desktop-portal-gtk - nerd-fonts.jetbrains-mono - jetbrains-mono kime nautilus adw-gtk3 - kdePackages.qt6ct + jetbrains.datagrip python313 - python313Packages.conda ]; environment.pathsToLink = [ @@ -174,6 +171,19 @@ ]; }; + fonts.packages = with pkgs; [ + nerd-fonts.jetbrains-mono + jetbrains-mono + ]; + + fonts.fontconfig = { + enable = true; + defaultFonts = { + serif = ["JetBrains Mono Nerd Font"]; + sansSerif = ["JetBrains Mono Nerd Font"]; + monospace = ["JetBrains Mono Nerd Font"]; + }; + }; #systemd.user.services.niri-flake-polkit.enable = false; # List services that you want to enable: diff --git a/hm/conf/alacritty.nix b/hm/conf/alacritty.nix deleted file mode 100644 index 6321e82..0000000 --- a/hm/conf/alacritty.nix +++ /dev/null @@ -1,18 +0,0 @@ -{...}: -{ - home.file.alacritty = { - target = ".config/alacritty/alacritty.toml"; - text = '' - [general] - import = ["dank-theme.toml"] - - [window] - padding = { x = 10, y = 10 } - - [font] - normal = { family = 'JetBrainsMono Nerd Font', style = 'Regular' } - size = 11 - offset = { x = 1, y = 2} - ''; - }; -} diff --git a/hm/config.kdl b/hm/config.kdl index 093f147..94678ce 100644 --- a/hm/config.kdl +++ b/hm/config.kdl @@ -141,6 +141,7 @@ hotkey-overlay { prefer-no-csd screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png" animations { + off workspace-switch { spring damping-ratio=0.80 stiffness=523 epsilon=0.0001 } diff --git a/hm/home.nix b/hm/home.nix index 3e5c750..58c9540 100644 --- a/hm/home.nix +++ b/hm/home.nix @@ -5,7 +5,6 @@ niri.homeModules.niri dankMaterialShell.homeModules.dankMaterialShell.default dankMaterialShell.homeModules.dankMaterialShell.niri - ./conf/alacritty.nix ]; programs.niri = { @@ -24,7 +23,7 @@ enableSpawn = true; }; systemd = { - enable = true; +# enable = true; restartIfChanged = true; }; }; @@ -38,7 +37,7 @@ xdg.enable = true; home.packages = [ - pkgs.alacritty + pkgs.kitty pkgs.lite-xl ]; @@ -47,6 +46,10 @@ XDG_SESSION_DESKTOP = "niri"; }; + xdg.configFile."kitty" = { + source = config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/niri/hm/kitty"; + recursive = true; + }; # programs.zsh.enable = false; # programs.fish.enable = true; } diff --git a/hm/kitty/dank-tabs.conf b/hm/kitty/dank-tabs.conf new file mode 100644 index 0000000..82a8ac4 --- /dev/null +++ b/hm/kitty/dank-tabs.conf @@ -0,0 +1,24 @@ +tab_bar_edge top +tab_bar_style powerline +tab_powerline_style slanted +tab_bar_align left +tab_bar_min_tabs 2 +tab_bar_margin_width 0.0 +tab_bar_margin_height 2.5 1.5 +tab_bar_margin_color #fcf8ff + +tab_bar_background #fcf8ff + +active_tab_foreground #17134a +active_tab_background #e3dfff +active_tab_font_style bold + +inactive_tab_foreground #47464f +inactive_tab_background #fcf8ff +inactive_tab_font_style normal + +tab_activity_symbol " ● " +tab_numbers_style 1 + +tab_title_template "{fmt.fg.red}{bell_symbol}{activity_symbol}{fmt.fg.tab}{title[:30]}{title[30:] and '…'} [{index}]" +active_tab_title_template "{fmt.fg.red}{bell_symbol}{activity_symbol}{fmt.fg.tab}{title[:30]}{title[30:] and '…'} [{index}]" \ No newline at end of file diff --git a/hm/kitty/dank-theme.conf b/hm/kitty/dank-theme.conf new file mode 100644 index 0000000..5008ad7 --- /dev/null +++ b/hm/kitty/dank-theme.conf @@ -0,0 +1,24 @@ +cursor #1c1b21 +cursor_text_color #47464f + +foreground #1c1b21 +background #fcf8ff +selection_foreground #ffffff +selection_background #c7c4dc +url_color #5b5891 +color0 #fcf8ff +color1 #8c120b +color2 #09720c +color3 #7f7a11 +color4 #6054ff +color5 #29247b +color6 #5b5891 +color7 #1a1a1a +color8 #2e2e2e +color9 #a52720 +color10 #138c17 +color11 #999321 +color12 #6662ad +color13 #3f56ff +color14 #b259ff +color15 #1a1a1a diff --git a/hm/kitty/kitty.conf b/hm/kitty/kitty.conf new file mode 100644 index 0000000..e3abfae --- /dev/null +++ b/hm/kitty/kitty.conf @@ -0,0 +1,40 @@ +# Font +font_family JetBrains Mono Nerd Font +font_size 11.0 + +# Cursor +cursor_shape beam +cursor_trail 1 + +# Padding (why weird value? consistency with foot) +window_margin_width 21.75 + +# No stupid close confirmation +confirm_os_window_close 0 + +# Use fish shell +shell fish + +# Copy +map ctrl+c copy_or_interrupt + +# Search +# map ctrl+f launch --location=hsplit --allow-remote-control kitty +kitten search.py @active-kitty-window-id +# map kitty_mod+f launch --location=hsplit --allow-remote-control kitty +kitten search.py @active-kitty-window-id + +# Scroll & Zoom +map page_up scroll_page_up +map page_down scroll_page_down + +map ctrl+plus change_font_size all +1 +map ctrl+equal change_font_size all +1 +map ctrl+kp_add change_font_size all +1 +map ctrl+minus change_font_size all -1 +map ctrl+underscore change_font_size all -1 +map ctrl+kp_subtract change_font_size all -1 +map ctrl+0 change_font_size all 0 +map ctrl+kp_0 change_font_size all 0 + +# Dank color generation +include dank-tabs.conf +include dank-theme.conf diff --git a/hm/kitty/scroll_mark.py b/hm/kitty/scroll_mark.py new file mode 100644 index 0000000..0193b20 --- /dev/null +++ b/hm/kitty/scroll_mark.py @@ -0,0 +1,18 @@ +from kittens.tui.handler import result_handler +from kitty.boss import Boss + + +def main(args: list[str]) -> None: + pass + + +@result_handler(no_ui=True) +def handle_result( + args: list[str], answer: str, target_window_id: int, boss: Boss +) -> None: + w = boss.window_id_map.get(target_window_id) + if w is not None: + if len(args) > 1 and args[1] != "prev": + w.scroll_to_mark(prev=False) + else: + w.scroll_to_mark() diff --git a/hm/kitty/search.py b/hm/kitty/search.py new file mode 100644 index 0000000..1fd554d --- /dev/null +++ b/hm/kitty/search.py @@ -0,0 +1,341 @@ +# Kitty search from https://github.com/trygveaa/kitty-kitten-search +# License: GPLv3 + +import json +import re +import subprocess +from gettext import gettext as _ +from pathlib import Path +from subprocess import PIPE, run + +from kittens.tui.handler import Handler +from kittens.tui.line_edit import LineEdit +from kittens.tui.loop import Loop +from kittens.tui.operations import ( + clear_screen, + cursor, + set_line_wrapping, + set_window_title, + styled, +) +from kitty.config import cached_values_for +from kitty.key_encoding import EventType +from kitty.typing_compat import KeyEventType, ScreenSize + +NON_SPACE_PATTERN = re.compile(r"\S+") +SPACE_PATTERN = re.compile(r"\s+") +SPACE_PATTERN_END = re.compile(r"\s+$") +SPACE_PATTERN_START = re.compile(r"^\s+") + +NON_ALPHANUM_PATTERN = re.compile(r"[^\w\d]+") +NON_ALPHANUM_PATTERN_END = re.compile(r"[^\w\d]+$") +NON_ALPHANUM_PATTERN_START = re.compile(r"^[^\w\d]+") +ALPHANUM_PATTERN = re.compile(r"[\w\d]+") + + +def call_remote_control(args: list[str]) -> None: + subprocess.run(["kitty", "@", *args], capture_output=True) + + +def reindex( + text: str, pattern: re.Pattern[str], right: bool = False +) -> tuple[int, int]: + if not right: + m = pattern.search(text) + else: + matches = [x for x in pattern.finditer(text) if x] + if not matches: + raise ValueError + m = matches[-1] + + if not m: + raise ValueError + + return m.span() + + +SCROLLMARK_FILE = Path(__file__).parent.absolute() / "scroll_mark.py" + + +class Search(Handler): + def __init__( + self, cached_values: dict[str, str], window_ids: list[int], error: str = "" + ) -> None: + self.cached_values = cached_values + self.window_ids = window_ids + self.error = error + self.line_edit = LineEdit() + last_search = cached_values.get("last_search", "") + self.line_edit.add_text(last_search) + self.text_marked = bool(last_search) + self.mode = cached_values.get("mode", "text") + self.update_prompt() + self.mark() + + def update_prompt(self) -> None: + self.prompt = "~> " if self.mode == "regex" else "=> " + + def init_terminal_state(self) -> None: + self.write(set_line_wrapping(False)) + self.write(set_window_title(_("Search"))) + + def initialize(self) -> None: + self.init_terminal_state() + self.draw_screen() + + def draw_screen(self) -> None: + self.write(clear_screen()) + if self.window_ids: + input_text = self.line_edit.current_input + if self.text_marked: + self.line_edit.current_input = styled(input_text, reverse=True) + self.line_edit.write(self.write, self.prompt) + self.line_edit.current_input = input_text + if self.error: + with cursor(self.write): + self.print("") + for l in self.error.split("\n"): + self.print(l) + + def refresh(self) -> None: + self.draw_screen() + self.mark() + + def switch_mode(self) -> None: + if self.mode == "regex": + self.mode = "text" + else: + self.mode = "regex" + self.cached_values["mode"] = self.mode + self.update_prompt() + + def on_text(self, text: str, in_bracketed_paste: bool = False) -> None: + if self.text_marked: + self.text_marked = False + self.line_edit.clear() + self.line_edit.on_text(text, in_bracketed_paste) + self.refresh() + + def on_key(self, key_event: KeyEventType) -> None: + if ( + self.text_marked + and key_event.type == EventType.PRESS + and key_event.key + not in [ + "TAB", + "LEFT_CONTROL", + "RIGHT_CONTROL", + "LEFT_ALT", + "RIGHT_ALT", + "LEFT_SHIFT", + "RIGHT_SHIFT", + "LEFT_SUPER", + "RIGHT_SUPER", + ] + ): + self.text_marked = False + self.refresh() + + if self.line_edit.on_key(key_event): + self.refresh() + return + + if key_event.matches("ctrl+u"): + self.line_edit.clear() + self.refresh() + elif key_event.matches("ctrl+a"): + self.line_edit.home() + self.refresh() + elif key_event.matches("ctrl+e"): + self.line_edit.end() + self.refresh() + elif key_event.matches("ctrl+backspace") or key_event.matches("ctrl+w"): + before, _ = self.line_edit.split_at_cursor() + + try: + start, _ = reindex(before, SPACE_PATTERN_END, right=True) + except ValueError: + start = -1 + + try: + space = before[:start].rindex(" ") + except ValueError: + space = 0 + self.line_edit.backspace(len(before) - space) + self.refresh() + elif key_event.matches("ctrl+left") or key_event.matches("ctrl+b"): + before, _ = self.line_edit.split_at_cursor() + try: + start, _ = reindex(before, SPACE_PATTERN_END, right=True) + except ValueError: + start = -1 + + try: + space = before[:start].rindex(" ") + except ValueError: + space = 0 + self.line_edit.left(len(before) - space) + self.refresh() + elif key_event.matches("ctrl+right") or key_event.matches("ctrl+f"): + _, after = self.line_edit.split_at_cursor() + try: + _, end = reindex(after, SPACE_PATTERN_START) + except ValueError: + end = 0 + + try: + space = after[end:].index(" ") + 1 + except ValueError: + space = len(after) + self.line_edit.right(space) + self.refresh() + elif key_event.matches("alt+backspace") or key_event.matches("alt+w"): + before, _ = self.line_edit.split_at_cursor() + + try: + start, _ = reindex(before, NON_ALPHANUM_PATTERN_END, right=True) + except ValueError: + start = -1 + else: + self.line_edit.backspace(len(before) - start) + self.refresh() + return + + try: + start, _ = reindex(before, NON_ALPHANUM_PATTERN, right=True) + except ValueError: + self.line_edit.backspace(len(before)) + self.refresh() + return + + self.line_edit.backspace(len(before) - (start + 1)) + self.refresh() + elif key_event.matches("alt+left") or key_event.matches("alt+b"): + before, _ = self.line_edit.split_at_cursor() + + try: + start, _ = reindex(before, NON_ALPHANUM_PATTERN_END, right=True) + except ValueError: + start = -1 + else: + self.line_edit.left(len(before) - start) + self.refresh() + return + + try: + start, _ = reindex(before, NON_ALPHANUM_PATTERN, right=True) + except ValueError: + self.line_edit.left(len(before)) + self.refresh() + return + + self.line_edit.left(len(before) - (start + 1)) + self.refresh() + elif key_event.matches("alt+right") or key_event.matches("alt+f"): + _, after = self.line_edit.split_at_cursor() + + try: + _, end = reindex(after, NON_ALPHANUM_PATTERN_START) + except ValueError: + end = 0 + else: + self.line_edit.right(end) + self.refresh() + return + + try: + _, end = reindex(after, NON_ALPHANUM_PATTERN) + except ValueError: + self.line_edit.right(len(after)) + self.refresh() + return + + self.line_edit.right(end - 1) + self.refresh() + elif key_event.matches("tab"): + self.switch_mode() + self.refresh() + elif key_event.matches("up") or key_event.matches("f3"): + for match_arg in self.match_args(): + call_remote_control(["kitten", match_arg, str(SCROLLMARK_FILE)]) + elif key_event.matches("down") or key_event.matches("shift+f3"): + for match_arg in self.match_args(): + call_remote_control(["kitten", match_arg, str(SCROLLMARK_FILE), "next"]) + elif key_event.matches("enter"): + self.quit(0) + elif key_event.matches("esc"): + self.quit(1) + + def on_interrupt(self) -> None: + self.quit(1) + + def on_eot(self) -> None: + self.quit(1) + + def on_resize(self, screen_size: ScreenSize) -> None: + self.refresh() + + def match_args(self) -> list[str]: + return [f"--match=id:{window_id}" for window_id in self.window_ids] + + def mark(self) -> None: + if not self.window_ids: + return + text = self.line_edit.current_input + if text: + match_case = "i" if text.islower() else "" + match_type = match_case + self.mode + for match_arg in self.match_args(): + try: + call_remote_control( + ["create-marker", match_arg, match_type, "1", text] + ) + except SystemExit: + self.remove_mark() + else: + self.remove_mark() + + def remove_mark(self) -> None: + for match_arg in self.match_args(): + call_remote_control(["remove-marker", match_arg]) + + def quit(self, return_code: int) -> None: + self.cached_values["last_search"] = self.line_edit.current_input + self.remove_mark() + if return_code: + for match_arg in self.match_args(): + call_remote_control(["scroll-window", match_arg, "end"]) + self.quit_loop(return_code) + + +def main(args: list[str]) -> None: + call_remote_control( + ["resize-window", "--self", "--axis=vertical", "--increment", "-100"] + ) + + error = "" + if len(args) < 2 or not args[1].isdigit(): + error = "Error: Window id must be provided as the first argument." + + window_id = int(args[1]) + window_ids = [window_id] + if len(args) > 2 and args[2] == "--all-windows": + ls_output = run(["kitty", "@", "ls"], stdout=PIPE) + ls_json = json.loads(ls_output.stdout.decode()) + current_tab = None + for os_window in ls_json: + for tab in os_window["tabs"]: + for kitty_window in tab["windows"]: + if kitty_window["id"] == window_id: + current_tab = tab + if current_tab: + window_ids = [ + w["id"] for w in current_tab["windows"] if not w["is_focused"] + ] + else: + error = "Error: Could not find the window id provided." + + loop = Loop() + with cached_values_for("search") as cached_values: + handler = Search(cached_values, window_ids, error) + loop.loop(handler)