├── .github └── workflows │ ├── check.yml │ ├── release-bin.yml │ ├── update-nix-inputs.yml │ └── update.yml ├── .gitignore ├── .gitmodules ├── .ortfo └── description.md ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── CONTRIBUTING.md ├── Justfile ├── LICENSE ├── README.md ├── cmd └── hyprls │ └── main.go ├── color.go ├── color_test.go ├── completion.go ├── default.nix ├── demo-colors.png ├── demo-completion.png ├── demo-hover-keyword.png ├── demo-hover.png ├── demo-symbols.png ├── flake.lock ├── flake.nix ├── go.mod ├── go.sum ├── gomod2nix.toml ├── handler.go ├── hover.go ├── hyprland_version ├── main.go ├── parser ├── data │ ├── generate │ │ ├── ast.json │ │ └── main.go │ ├── keywords.go │ ├── keywords_test.go │ ├── load.go │ ├── sections.go │ ├── sources │ │ ├── Animations.md │ │ ├── Binds.md │ │ ├── Configuring-Hyprland.md │ │ ├── Dispatchers.md │ │ ├── Dwindle-Layout.md │ │ ├── Environment-variables.md │ │ ├── Example-configurations.md │ │ ├── Expanding-functionality.md │ │ ├── Keywords.md │ │ ├── Master-Layout.md │ │ ├── Monitors.md │ │ ├── Multi-GPU.md │ │ ├── Performance.md │ │ ├── Permissions.md │ │ ├── Start.md │ │ ├── Tearing.md │ │ ├── Uncommon-tips-&-tricks.md │ │ ├── Using-hyprctl.md │ │ ├── Variables.md │ │ ├── Window-Rules.md │ │ ├── Workspace-Rules.md │ │ ├── XWayland.md │ │ └── _index.md │ └── variables.go ├── decode.go ├── decode_test.go ├── fixtures │ ├── empty.hl │ ├── test.hl │ └── test.json ├── highlevel.go ├── lowlevel.go ├── lowlevel_test.go └── utils.go ├── renovate.json ├── shell.nix ├── state.go ├── symbols.go ├── sync.go ├── unimplemented.go ├── utils.go ├── vscode-extension-pack ├── .gitignore ├── .vscode │ └── launch.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bun.lockb ├── icon.png ├── package.json ├── package.json.bak └── vsc-extension-quickstart.md └── vscode ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .vscodeignore ├── LICENSE ├── README.md ├── bun.lockb ├── icon.png ├── package.json ├── scripts └── e2e.sh ├── src └── extension.ts ├── tsconfig.json └── vscode-hyprlang-0.0.1.vsix /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: 4 | pull_request: 5 | types: [opened, reopened, synchronize] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: checkout 12 | uses: actions/checkout@v4 13 | 14 | - uses: actions/setup-go@v5 15 | 16 | - name: Build 17 | run: go build -o hyprls cmd/hyprls/main.go 18 | 19 | - name: Run tests 20 | run: go test 21 | -------------------------------------------------------------------------------- /.github/workflows/release-bin.yml: -------------------------------------------------------------------------------- 1 | name: Release pre-built binaries for new tag 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: checkout 16 | uses: actions/checkout@v4 17 | 18 | - name: go-setup 19 | uses: actions/setup-go@v5 20 | 21 | - name: go-build 22 | run: go build -o hyprls cmd/hyprls/main.go 23 | 24 | - name: zip-artifacts 25 | run: zip hyprls.zip hyprls 26 | 27 | - name: deploy-binaries 28 | uses: softprops/action-gh-release@v2 29 | with: 30 | tag_name: ${{ github.ref_name }} 31 | files: hyprls.zip 32 | token: ${{secrets.GITHUB_TOKEN}} 33 | -------------------------------------------------------------------------------- /.github/workflows/update-nix-inputs.yml: -------------------------------------------------------------------------------- 1 | name: Update nix inputs 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '51 2 * * 0' 7 | 8 | jobs: 9 | update: 10 | name: inputs 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Clone repository 14 | uses: actions/checkout@v4 15 | 16 | - uses: DeterminateSystems/nix-installer-action@main 17 | - uses: extractions/setup-just@v3 18 | - name: Update inputs 19 | run: just update-nix-inputs 20 | 21 | - name: Commit 22 | uses: stefanzweifel/git-auto-commit-action@v5 23 | with: 24 | commit_message: '⬆️ Nix: update inputs' 25 | -------------------------------------------------------------------------------- /.github/workflows/update.yml: -------------------------------------------------------------------------------- 1 | name: Update submodules 2 | 3 | on: 4 | workflow_dispatch: {} 5 | schedule: 6 | - cron: 0 2 * * * # every day at 2 am 7 | 8 | jobs: 9 | pull: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | steps: 14 | - uses: ConorMacBride/install-package@v1 15 | with: 16 | apt: moreutils 17 | - uses: actions/checkout@v4 18 | - uses: extractions/setup-just@v3 19 | 20 | - run: just check-for-hyprland-updates 21 | 22 | - name: Check if there are changes 23 | id: changes 24 | uses: UnicornGlobal/has-changes-action@v1.0.12 25 | 26 | - if: steps.changes.outputs.changed == 1 27 | name: Commit changes 28 | uses: stefanzweifel/git-auto-commit-action@v5 29 | with: 30 | commit_message: 🍱 Update parser data from wiki 31 | 32 | - if: steps.changes.outputs.changed == 1 33 | name: Bump version and push tag 34 | id: tag_version 35 | uses: miguelfito/github-bump-and-tag-action@v1 36 | with: 37 | github_token: ${{ secrets.GITHUB_TOKEN }} 38 | default_bump: minor 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/go 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=go 3 | 4 | ### Go ### 5 | # If you prefer the allow list template instead of the deny list, see community template: 6 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 7 | # 8 | # Binaries for programs and plugins 9 | *.exe 10 | *.exe~ 11 | *.dll 12 | *.so 13 | *.dylib 14 | 15 | # Test binary, built with `go test -c` 16 | *.test 17 | 18 | # Output of the go coverage tool, specifically when used with LiteIDE 19 | *.out 20 | 21 | # Dependency directories (remove the comment below to include it) 22 | # vendor/ 23 | 24 | # Go workspace file 25 | go.work 26 | 27 | # End of https://www.toptal.com/developers/gitignore/api/go 28 | hyprlang-lsp 29 | parser/data/generate/generator 30 | logs 31 | *.vsix 32 | hyprls 33 | hyprls 34 | result/ 35 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "hyprland-wiki"] 2 | path = hyprland-wiki 3 | url = https://github.com/hyprwm/hyprland-wiki 4 | -------------------------------------------------------------------------------- /.ortfo/description.md: -------------------------------------------------------------------------------- 1 | --- 2 | started: "2024-04-25" 3 | made with: 4 | - go 5 | - hyprland 6 | tags: 7 | - language 8 | - programming 9 | aliases: [hyprlang-lsp] 10 | layout: 11 | - [m1, p1] 12 | - [m2, l1] 13 | - [m3, m4, m5] 14 | thumbnail: ../demo-completion.png 15 | --- 16 | 17 | # HyprLS 18 | 19 | :: en 20 | 21 | A [LSP](https://en.wikipedia.org/wiki/Language_Server_Protocol) server for [Hyprland](https://hyprland.org)'s configuration language 22 | 23 | [Source code](https://github.com/ewen-lbh/hyprlang-lsp.git) 24 | 25 | ![](../demo-completion.png) 26 | 27 | ![](../demo-hover.png) 28 | 29 | ![](../demo-hover-keyword.png) 30 | 31 | ![](../demo-symbols.png) 32 | 33 | ![](../demo-colors.png) 34 | 35 | 36 | :: fr 37 | 38 | Un serveur [LSP](https://en.wikipedia.org/wiki/Language_Server_Protocol) pour le langage de configuration de [Hyprland](https://hyprland.org) 39 | 40 | [Code source](https://github.com/ewen-lbh/hyprlang-lsp.git) 41 | 42 | ![](../demo-completion.png) 43 | 44 | ![](../demo-hover.png) 45 | 46 | ![](../demo-hover-keyword.png) 47 | 48 | ![](../demo-symbols.png) 49 | 50 | ![](../demo-colors.png) 51 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "dbaeumer.vscode-eslint" 8 | ] 9 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "type": "extensionHost", 7 | "request": "launch", 8 | "name": "Launch Client", 9 | "runtimeExecutable": "${execPath}", 10 | "cwd": "${workspaceFolder}/vscode", 11 | "args": ["--extensionDevelopmentPath=${workspaceRoot}"], 12 | "outFiles": ["${workspaceRoot}/out/**/*.js"], 13 | "autoAttachChildProcesses": true, 14 | "preLaunchTask": "bun:watch" 15 | }, 16 | { 17 | "name": "Language Server E2E Test", 18 | "type": "extensionHost", 19 | "request": "launch", 20 | "runtimeExecutable": "${execPath}", 21 | "args": [ 22 | "--extensionDevelopmentPath=${workspaceRoot}/vscode", 23 | "--extensionTestsPath=${workspaceRoot}/out/test/index", 24 | "${workspaceRoot}/testFixture" 25 | ], 26 | "outFiles": ["${workspaceRoot}/vscode/out/test/**/*.js"] 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.insertSpaces": false, 3 | "typescript.tsc.autoDetect": "off", 4 | "typescript.preferences.quoteStyle": "single", 5 | "editor.codeActionsOnSave": { 6 | "source.fixAll.eslint": "explicit" 7 | } 8 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "bun", 6 | "script": "compile", 7 | "group": "build", 8 | "presentation": { 9 | "panel": "dedicated", 10 | "reveal": "never" 11 | }, 12 | "problemMatcher": ["$tsc"] 13 | }, 14 | { 15 | "label": "bun:watch", 16 | "type": "bun", 17 | "options": { 18 | "cwd": "${workspaceFolder}/vscode" 19 | }, 20 | "script": "watch", 21 | "isBackground": true, 22 | "group": { 23 | "kind": "build", 24 | "isDefault": true 25 | }, 26 | "presentation": { 27 | "panel": "dedicated", 28 | "reveal": "never" 29 | }, 30 | "problemMatcher": ["$tsc-watch"], 31 | "dependsOn": ["go:compile"] 32 | }, 33 | { 34 | "label": "go:compile", 35 | "type": "shell", 36 | "command": "just", 37 | "args": ["build-debug"] 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for your interest in contributing to HyprLS :3! 4 | 5 | ## Setup your environment development 6 | 7 | You just need to install: 8 | 9 | - [Go](https://golang.org/doc/install) 10 | - [Just](https://just.systems) 11 | 12 | Then: 13 | 14 | ```sh 15 | git clone --recurse-submodules https://github.com/your/fork 16 | cd fork 17 | just build 18 | ``` 19 | 20 | Then, you can build a binary locally with `just build`. 21 | To create a "debug build" that logs all the requests, responses and server logs to files (useful for debugging), you can run `just build-debug`. 22 | 23 | The debug binary is named `hyprlang-lsp` and the regular binary is named `hyprls`. 24 | 25 | ### VSCode 26 | 27 | To develop the vscode extension, you'll also need: 28 | 29 | - [VSCode](https://code.visualstudio.com/), really useful for debugging the extension, you just have to launch from the "start/debug" menu in the sidebar 30 | - [Bun](https://bun.sh) 31 | 32 | Then: 33 | 34 | ```sh 35 | cd vscode 36 | bun i 37 | ``` 38 | 39 | > **Note:** "Reloading" does not re-build the Go server, you'll need to kill the "Develoment Host Extension" window and kill the terminal that ran the `go:compile` task before launching again. 40 | 41 | The VSCode dev environment is set up to use the debug binary in development. The path is absolute and hardcoded, so you may need to change it to where your debug binary is (check `vscode/src/extension.ts`). 42 | 43 | ## Implementing a new LSP feature 44 | 45 | 1. Make the server signal that it supports said feature in the `initialize` method in `handler.go` 46 | 2. Add a new file for the feature in the root directory (e.g. `formatting.go`) 47 | 3. Cut the corresponding method(s) from `unimplemented.go` and paste them in the new file 48 | 49 | Read on for more details on the file structure. 50 | 51 | ## File structure 52 | 53 | - `vscode/`: source code for the VSCode client extension 54 | - `cmd/hyprls/main.go`: source code for the executable binary. should contain _very little_ code, just enough to start the server 55 | - `handler.go`: defines the `Handler` struct, which is responsible for handling all the LSP requests. Also defines initialization code. 56 | - `color.go`, `completion.go`, `hover.go`, `symbols.go`: code for the different LSP features 57 | - `state.go`: in-memory map of the opened files' contents as well as a few functions to get things like the current section we are in, the current line, etc. 58 | - `sync.go`: code for `did*` events (when the client signals that content was changed or that files were opened or closed). responsible for maintaining `state.go`'s data up-to-date 59 | - `unimplemented.go`: stub functions for the LSP features that are not yet implemented 60 | - `parser/`: source code for the parser: 61 | - `lowlevel.go`: the low-level parser, which reads the raw data from the server and converts it to sections, that contain: 62 | - assignments: setting a [variable](https://wiki.hyprland.org/Configuring/Variables) 63 | - statements: stuff like `exec-once`, `bind`, etc (see [keywords](https://wiki.hyprland.org/Configuring/Keywords)) 64 | - sub-sections: sections nested within that section 65 | - `highlevel.go`: the high-level parser, which reads the sections and converts them to a more structured format. The file is generated by `parser/data/generate/main.go` from the wiki pages (continue reading for more information) 66 | - `decode.go`: transform the representation from the low-level parser to the high-level parser (WIP) 67 | - `data/`: code responsible for storing and getting all the config data: all the valid variable names, their types and descriptions, all valid keywords, etc. 68 | - `keywords.go`: all valid keywords with data to allow getting their documentation from wiki pages 69 | - `sections.go`: code related to sections, mostly used by `parser/data/generate` to create the Go struct definitions for the high-level parser 70 | - `variables.go`: same as `sections.go`, but for the different variables 71 | - `load.go`: code to actually load all the data from the wiki pages. declares a few variables that embed the wiki pages' contents from `parser/data/sources`, then, in an `init()` function (which is run at the start of the program), it loads all the data from the wiki pages into the variables: 72 | 1. Convert the markdown content to HTML 73 | 2. Parse that HTML 74 | 3. Walk through it, extracting data from tables and headings 75 | 4. Store that data in `Section` and `Keywords` 76 | - `sources/`: contains the wiki pages' markdown content. Copied `hyprland-wiki/pages/Configuring/*.md` to here when running `just build` 77 | - `generate/`: code to generate the `highlevel.go` file from the wiki pages. Leverages the data loaded by `load.go` to generate the Go struct definitions for the high-level parser, and also output `ast.json` for debugging purposes (and later maybe to use _that_ instead of converting markdown to HTML and parsing every time the server starts, see #1) 78 | 79 | ## Commit names 80 | 81 | We use the [gitmoji](https://gitmoji.dev/) convention for commit names. 82 | -------------------------------------------------------------------------------- /Justfile: -------------------------------------------------------------------------------- 1 | serverLogsFilepath := `realpath ./logs/server.log || echo ./logs/server.log` 2 | latestTag := `git describe --tags --abbrev=0 || echo commit:$(git rev-parse --short HEAD)` 3 | latestVersion := `git describe --tags --abbrev=0 | sed 's/v//' || echo commit:$(git rev-parse --short HEAD)` 4 | # Parse content of https://raw.githubusercontent.com/hyprwm/hyprland-wiki/main/pages/version-selector.md to get latest documented version 5 | 6 | check-for-hyprland-updates: 7 | #!/bin/env bash 8 | set -euxo pipefail 9 | latestHyprlandVersion=`curl -s https://raw.githubusercontent.com/hyprwm/hyprland-wiki/main/pages/version-selector.md | grep -oP 'v\d+\.\d+\.\d+' | head -n 1 | sed 's/v//'` 10 | touch hyprland_version 11 | if [ "$(cat hyprland_version)" != "$latestHyprlandVersion" ]; then 12 | echo New version $latestHyprlandVersion released!!! update time :3 13 | echo "$latestHyprlandVersion" > hyprland_version 14 | just pull-wiki 15 | just parser-data 16 | else 17 | echo Nyathing to update 18 | fi 19 | 20 | release tag: 21 | jq '.version = "{{ tag }}"' < vscode/package.json | sponge vscode/package.json 22 | sed -i "s/$(grep 'version' default.nix)/ version = \"{{ tag }}\";/" default.nix 23 | git add vscode/package.json default.nix 24 | git commit -m "🏷️ Release {{ tag }}" 25 | git tag -- v{{ tag }} 26 | cd vscode; bun vsce package; bun vsce publish 27 | git push 28 | git push --tags 29 | 30 | run: 31 | just build 32 | ./hyprlang-lsp 33 | 34 | build: 35 | mkdir -p logs 36 | touch logs/server.log 37 | mkdir -p parser/data/sources 38 | # cp hyprland-wiki/pages/Configuring/*.md parser/data/sources/ 39 | go mod tidy 40 | go build -ldflags "-X main.HyprlandWikiVersion=$(cat hyprland_version) -X main.HyprlsVersion={{ latestVersion }}" -o hyprls cmd/hyprls/main.go 41 | 42 | build-debug: 43 | mkdir -p parser/data/sources 44 | cp hyprland-wiki/pages/Configuring/*.md parser/data/sources/ 45 | go mod tidy 46 | go build -ldflags "-X main.OutputServerLogs={{ serverLogsFilepath }}" -o hyprlang-lsp cmd/hyprls/main.go 47 | 48 | install: 49 | just build 50 | mkdir -p ~/.local/bin 51 | cp hyprls ~/.local/bin/hyprls 52 | 53 | pull-wiki: 54 | #!/bin/bash 55 | git submodule update --init --recursive --remote 56 | cd hyprland-wiki 57 | hash=$(git log --all --oneline --grep="versions: add $(cat ../hyprland_version)" | cut -d' ' -f1) 58 | echo Using wiki https://github.com/hyprwm/hyprland-wiki/commit/$hash 59 | git checkout $hash 60 | cd .. 61 | cp hyprland-wiki/pages/Configuring/*.md parser/data/sources/ 62 | 63 | parser-data: 64 | #!/bin/bash 65 | set -euxo pipefail 66 | just build 67 | cd parser/data/generate 68 | go build -o generator main.go 69 | ./generator > ../../highlevel.go ast.json 70 | gofmt -s -w ../../highlevel.go 71 | jq . < ast.json | sponge ast.json 72 | 73 | update-nix-inputs: 74 | nix flake update 75 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice (including the next 11 | paragraph) shall be included in all copies or substantial portions of the 12 | Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 17 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 19 | OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HyprLS 2 | 3 | 4 | 5 | 8 | 9 | 13 |
6 | 7 |
10 | 11 | 12 |
14 | 15 | A LSP server for Hyprland configuration files. 16 | 17 | ## Features 18 | 19 | Not checked means planned / work in progress. 20 | 21 | - [x] Auto-complete 22 | - [x] Hover 23 | - [ ] TODO: Documentation on hover of categories? 24 | - [x] Go to definition 25 | - [x] Color pickers 26 | - [x] Document symbols 27 | - [ ] Diagnostics 28 | - [ ] Formatting 29 | - [ ] Semantic highlighting 30 | 31 | ## Installation 32 | 33 | ### Linux packages 34 | 35 | hyprls has packages for various distributions, kindly maintained by other people 36 | 37 | [![Packaging status](https://repology.org/badge/vertical-allrepos/hyprls.svg)](https://repology.org/project/hyprls/versions) 38 | 39 | ### With `go install` 40 | 41 | ```sh 42 | go install github.com/hyprland-community/hyprls/cmd/hyprls@latest 43 | ``` 44 | 45 | ### Pre-built binaries 46 | 47 | Binaries for linux are available in [Releases](https://github.com/hyprland-community/hyprls/releases) 48 | 49 | ### From source 50 | 51 | - Required: [Just](https://just.systems) (`paru -S just` on Arch Linux (btw)) 52 | 53 | ```sh 54 | git clone --recurse-submodules https://github.com/hyprland-community/hyprls 55 | cd hyprls 56 | # installs the binary to ~/.local/bin. 57 | # Make sure that directory exists and is in your PATH 58 | just install 59 | ``` 60 | 61 | ## Usage 62 | 63 | ### With Neovim 64 | 65 | _Combine with [The tree-sitter grammar for Hyprlang](https://github.com/tree-sitter-grammars/tree-sitter-hyprlang) for syntax highlighting._ 66 | 67 | Add this to your `init.lua`: 68 | 69 | ```lua 70 | -- Hyprlang LSP 71 | vim.api.nvim_create_autocmd({'BufEnter', 'BufWinEnter'}, { 72 | pattern = {"*.hl", "hypr*.conf"}, 73 | callback = function(event) 74 | print(string.format("starting hyprls for %s", vim.inspect(event))) 75 | vim.lsp.start { 76 | name = "hyprlang", 77 | cmd = {"hyprls"}, 78 | root_dir = vim.fn.getcwd(), 79 | } 80 | end 81 | }) 82 | ``` 83 | 84 | ### VSCode 85 | 86 | #### Official Marketplace (VisualStudio Marketplace) 87 | 88 | Install it [from the marketplace](https://marketplace.visualstudio.com/items?itemName=gwenn°-lbh.vscode-hyprls). 89 | 90 | > [!TIP] 91 | > You can use [the Hyprland extension pack](https://marketplace.visualstudio.com/items?itemName=gwenn°-lbh.hyprland) to also get syntax highlighting. 92 | 93 | #### Open VSX (for VSCodium & others) 94 | 95 | Install it [on OpenVSX](https://open-vsx.org/extension/gwenn°-lbh/vscode-hyprls) 96 | 97 | ### Zed 98 | 99 | Language server support is provided by the [Hyprlang extension](https://zed.dev/extensions?query=hyprlang). 100 | Detailed installation and setup instructions can be found in the [extension repository](https://github.com/WhySoBad/zed-hyprlang-extension) [maintainer = [@WhySoBad](https://github.com/WhySoBad)]. 101 | -------------------------------------------------------------------------------- /cmd/hyprls/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | hyprls "github.com/hyprland-community/hyprls" 9 | "go.uber.org/zap" 10 | ) 11 | 12 | var OutputServerLogs string 13 | 14 | func main() { 15 | logconf := zap.NewDevelopmentConfig() 16 | if OutputServerLogs != "" { 17 | logconf.OutputPaths = []string{OutputServerLogs, "stderr"} 18 | } 19 | logger, err := logconf.Build() 20 | if err != nil { 21 | fmt.Printf("while building logger: %s", err) 22 | os.Exit(1) 23 | } 24 | 25 | logger.Debug("going to start server") 26 | if OutputServerLogs != "" { 27 | hyprls.StartServer(logger, filepath.Dir(OutputServerLogs)) 28 | } else { 29 | hyprls.StartServer(logger, "") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /color.go: -------------------------------------------------------------------------------- 1 | package hyprls 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math" 7 | "strings" 8 | 9 | "github.com/hyprland-community/hyprls/parser" 10 | "github.com/mazznoer/csscolorparser" 11 | "go.lsp.dev/protocol" 12 | "go.uber.org/zap" 13 | ) 14 | 15 | func (h Handler) ColorPresentation(ctx context.Context, params *protocol.ColorPresentationParams) ([]protocol.ColorPresentation, error) { 16 | logger.Debug("LSP:ColorPresentation", zap.Any("color", params.Color), zap.Any("range", params.Range)) 17 | return []protocol.ColorPresentation{ 18 | { 19 | Label: encodeColorLiteral(params.Color), 20 | TextEdit: &protocol.TextEdit{ 21 | Range: params.Range, 22 | NewText: encodeColorLiteral(params.Color), 23 | }, 24 | }, 25 | }, nil 26 | } 27 | 28 | func (h Handler) DocumentColor(ctx context.Context, params *protocol.DocumentColorParams) ([]protocol.ColorInformation, error) { 29 | document, err := parse(params.TextDocument.URI) 30 | if err != nil { 31 | return []protocol.ColorInformation{}, fmt.Errorf("while parsing: %w", err) 32 | } 33 | colors := make([]protocol.ColorInformation, 0) 34 | document.WalkValues(func(a *parser.Assignment, v *parser.Value) { 35 | if v.Kind == parser.Gradient { 36 | for _, stop := range v.Gradient.Stops { 37 | colors = append(colors, protocol.ColorInformation{ 38 | Color: stop.LSPColor(), 39 | Range: stop.LSPRange(), 40 | }) 41 | } 42 | return 43 | } 44 | 45 | if v.Kind != parser.Color { 46 | return 47 | } 48 | 49 | colors = append(colors, protocol.ColorInformation{ 50 | Color: v.LSPColor(), 51 | Range: v.LSPRange(), 52 | }) 53 | }) 54 | return colors, nil 55 | } 56 | 57 | func decodeColorLiteral(raw string) protocol.Color { 58 | logger.Debug("decodeColorLiteral", zap.String("raw", raw)) 59 | color, err := parser.ParseColor(raw) 60 | if err != nil { 61 | return protocol.Color{} 62 | } 63 | 64 | logger.Debug("decodeColorLiteral", zap.Any("color", color)) 65 | 66 | return protocol.Color{ 67 | Red: roundToThree(float64(color.R) / 255.0), 68 | Alpha: roundToThree(float64(color.A) / 255.0), 69 | Blue: roundToThree(float64(color.B) / 255.0), 70 | Green: roundToThree(float64(color.G) / 255.0), 71 | } 72 | } 73 | 74 | func encodeColorLiteral(color protocol.Color) string { 75 | logger.Debug("encodeColorLiteral", zap.Any("color", color)) 76 | out := strings.TrimPrefix(csscolorparser.Color{ 77 | R: roundToThree(color.Red), 78 | G: roundToThree(color.Green), 79 | B: roundToThree(color.Blue), 80 | A: roundToThree(color.Alpha), 81 | }.HexString(), "#") 82 | 83 | if color.Alpha == 1.0 { 84 | out = fmt.Sprintf("rgb(%s)", out) 85 | } else { 86 | out = fmt.Sprintf("rgba(%s)", out) 87 | } 88 | 89 | logger.Debug("encodeColorLiteral", zap.String("out", out)) 90 | return out 91 | } 92 | 93 | func roundToThree(f float64) float64 { 94 | return math.Round(f*1_00) / 1_00 95 | } 96 | -------------------------------------------------------------------------------- /color_test.go: -------------------------------------------------------------------------------- 1 | package hyprls 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | 7 | "go.lsp.dev/protocol" 8 | "go.uber.org/zap" 9 | ) 10 | 11 | func init() { 12 | if logger == nil { 13 | logger, _ = zap.NewDevelopmentConfig().Build() 14 | } 15 | } 16 | 17 | func TestColorEncoding(t *testing.T) { 18 | for i := 0; i < 20; i++ { 19 | color := randomColor() 20 | colorstring := encodeColorLiteral(color) 21 | decoded := decodeColorLiteral(colorstring) 22 | encoded := encodeColorLiteral(decoded) 23 | if colorstring != encoded { 24 | t.Errorf("Color %d: %s != %s (was decoded to %v)", i, colorstring, encoded, decoded) 25 | } 26 | } 27 | } 28 | 29 | func TestColorDecoding(t *testing.T) { 30 | // red 31 | if !compareColorStructs(protocol.Color{Red: 1, Green: 0, Blue: 0, Alpha: 1}, decodeColorLiteral("rgb(ff0000)")) { 32 | t.Errorf("Color 'red' != {1, 0, 0, 1} (was decoded to %v)", decodeColorLiteral("red")) 33 | } 34 | 35 | // blue 36 | if !compareColorStructs(protocol.Color{Red: 0, Green: 0, Blue: 1, Alpha: 1}, decodeColorLiteral("rgba(0000ffff)")) { 37 | t.Errorf("Color 'blue' != {0, 0, 1, 1} (was decoded to %v)", decodeColorLiteral("blue")) 38 | } 39 | 40 | // green 41 | if !compareColorStructs(protocol.Color{Red: 0, Green: 1, Blue: 0, Alpha: 1}, decodeColorLiteral("0xff00ff00")) { 42 | t.Errorf("Color '0xff00ff00' != {0, 1, 0, 1} (was decoded to %v)", decodeColorLiteral("0xff00ff00")) 43 | } 44 | 45 | for i := 0; i < 20; i++ { 46 | color := randomColor() 47 | originalLogger := *logger 48 | logger = logger.WithOptions(zap.Fields(zap.Int("test color number", i))) 49 | encoded := encodeColorLiteral(color) 50 | decoded := decodeColorLiteral(encoded) 51 | if !compareColorStructs(color, decoded) { 52 | t.Errorf("Color %d: %#v != %#v (was encoded to %s)", i, color, decoded, encoded) 53 | } 54 | logger = &originalLogger 55 | } 56 | } 57 | 58 | func compareColorStructs(a, b protocol.Color) bool { 59 | delta := 0 60 | delta += int(a.Red*255 - b.Red*255) 61 | delta += int(a.Green*255 - b.Green*255) 62 | delta += int(a.Blue*255 - b.Blue*255) 63 | delta += int(a.Alpha*255 - b.Alpha*255) 64 | return delta == 0 65 | } 66 | 67 | func randomColor() protocol.Color { 68 | return protocol.Color{ 69 | Red: roundToThree(rand.Float64()), 70 | Green: roundToThree(rand.Float64()), 71 | Blue: roundToThree(rand.Float64()), 72 | Alpha: roundToThree(rand.Float64()), 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /completion.go: -------------------------------------------------------------------------------- 1 | package hyprls 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "strings" 8 | "unicode" 9 | 10 | "github.com/hyprland-community/hyprls/parser" 11 | parser_data "github.com/hyprland-community/hyprls/parser/data" 12 | "go.lsp.dev/protocol" 13 | "go.uber.org/zap" 14 | ) 15 | 16 | func (h Handler) Completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, error) { 17 | line, err := currentLine(params.TextDocument.URI, params.Position) 18 | if err != nil { 19 | return nil, nil 20 | } 21 | 22 | file, err := parse(params.TextDocument.URI) 23 | if err != nil { 24 | return nil, nil 25 | } 26 | 27 | sec := currentSection(file, params.Position) 28 | if sec == nil { 29 | sec = &parser.Section{} 30 | } 31 | 32 | cursorIsAfterEquals := err == nil && strings.Contains(line, "=") && strings.Index(line, "=") < int(params.Position.Character) 33 | 34 | // we are after the equals sign, suggest custom properties only 35 | if cursorIsAfterEquals { 36 | items := make([]protocol.CompletionItem, 0) 37 | 38 | cursorOrLineEnd := min(int(params.Position.Character), len(line)-1) 39 | characterBeforeCursorIsDollarSign := line[cursorOrLineEnd] == '$' 40 | 41 | // Don't propose custom variables if in the middle of typing a word 42 | // Only propose if a dollar sign was typed or is just before the cursor 43 | // Or we are after whitespace 44 | // Or we are in the middle of a color completion (typed a r, and key is a color or gradient) 45 | if !characterBeforeCursorIsDollarSign && !unicode.IsSpace(rune(line[params.Position.Character-1])) { 46 | return nil, nil 47 | } 48 | 49 | // Prevent duplicate dollar signs upon completion accept 50 | var textEditRange protocol.Range 51 | if characterBeforeCursorIsDollarSign { 52 | textEditRange = protocol.Range{ 53 | protocol.Position{params.Position.Line, params.Position.Character - 1}, 54 | protocol.Position{params.Position.Line, params.Position.Character}, 55 | } 56 | } else { 57 | textEditRange = collapsedRange(params.Position) 58 | } 59 | 60 | file.WalkCustomVariables(func(v *parser.CustomVariable) { 61 | items = append(items, protocol.CompletionItem{ 62 | Label: "$" + v.Key, 63 | Kind: protocol.CompletionItemKindVariable, 64 | Documentation: protocol.MarkupContent{ 65 | Kind: protocol.PlainText, 66 | Value: v.ValueRaw, 67 | }, 68 | TextEdit: &protocol.TextEdit{ 69 | Range: textEditRange, 70 | NewText: "$" + v.Key, 71 | }, 72 | }) 73 | }) 74 | 75 | textedit := func(t string) *protocol.TextEdit { 76 | return &protocol.TextEdit{ 77 | Range: collapsedRange(params.Position), 78 | NewText: t, 79 | } 80 | } 81 | 82 | valueKind := parser.String 83 | if sec != nil { 84 | assignment := currentAssignment(*sec, params.Position) 85 | if assignment != nil { 86 | valueKind, err = parser.ValueKindFromString(assignment.ParserTypeString()) 87 | if err != nil { 88 | valueKind = parser.String 89 | } 90 | } 91 | } 92 | 93 | logger.Debug("LSP:Completion", zap.Any("valueKind", valueKind)) 94 | 95 | switch valueKind { 96 | case parser.Color, parser.Gradient: 97 | 98 | items = append(items, protocol.CompletionItem{ 99 | Label: "rgba(⋯)", 100 | Kind: protocol.CompletionItemKindColor, 101 | InsertTextFormat: protocol.InsertTextFormatSnippet, 102 | Documentation: "Define a color with an alpha channel of the form rgba(RRGGBBAA) in hexadecimal notation.", 103 | TextEdit: textedit("rgba(${0:ffffffff})"), 104 | }) 105 | 106 | items = append(items, protocol.CompletionItem{ 107 | Label: "rgb(⋯)", 108 | Kind: protocol.CompletionItemKindColor, 109 | InsertTextFormat: protocol.InsertTextFormatSnippet, 110 | TextEdit: textedit("rgb(${0:ffffff})"), 111 | Documentation: "Define a color of the form rgb(RRGGBB) in hexadecimal notation.", 112 | }) 113 | 114 | items = append(items, protocol.CompletionItem{ 115 | Label: "0xAARRGGBB", 116 | Kind: protocol.CompletionItemKindColor, 117 | InsertTextFormat: protocol.InsertTextFormatSnippet, 118 | TextEdit: textedit("0x${1:ffffffff}"), 119 | Deprecated: true, 120 | Documentation: "Define a color of the form 0xAARRGGBB in hexadecimal notation.", 121 | }) 122 | case parser.Bool: 123 | items = append(items, protocol.CompletionItem{ 124 | Label: "true", 125 | Kind: protocol.CompletionItemKindValue, 126 | }) 127 | items = append(items, protocol.CompletionItem{ 128 | Label: "false", 129 | Kind: protocol.CompletionItemKindValue, 130 | }) 131 | case parser.Modmask: 132 | for keystring := range parser.ModKeyNames { 133 | items = append(items, protocol.CompletionItem{ 134 | Label: keystring, 135 | Kind: protocol.CompletionItemKindEnumMember, 136 | }) 137 | } 138 | 139 | } 140 | 141 | return &protocol.CompletionList{ 142 | Items: items, 143 | }, nil 144 | } 145 | 146 | availableVariables := make([]parser_data.VariableDefinition, 0) 147 | if sec != nil { 148 | secDef := parser_data.FindSectionDefinitionByName(sec.Name) 149 | if secDef != nil { 150 | availableVariables = append(availableVariables, secDef.Variables...) 151 | } 152 | } 153 | 154 | items := make([]protocol.CompletionItem, 0) 155 | vars: 156 | for _, vardef := range availableVariables { 157 | // Don't suggest variables that are already defined 158 | for _, definedvar := range sec.Assignments { 159 | if vardef.Name == definedvar.Key { 160 | continue vars 161 | } 162 | } 163 | 164 | items = append(items, protocol.CompletionItem{ 165 | Label: vardef.Name, 166 | Kind: protocol.CompletionItemKindField, 167 | Documentation: protocol.MarkupContent{ 168 | Kind: protocol.Markdown, 169 | Value: fmt.Sprintf("Type: %s\n\n%s", vardef.Type, vardef.Description), 170 | }, 171 | }) 172 | } 173 | 174 | for _, kw := range parser_data.Keywords { 175 | items = append(items, protocol.CompletionItem{ 176 | Label: kw.Name, 177 | Kind: protocol.CompletionItemKindKeyword, 178 | Documentation: protocol.MarkupContent{ 179 | Kind: protocol.Markdown, 180 | Value: kw.Description, 181 | }, 182 | }) 183 | } 184 | 185 | subsections: 186 | for _, subsections := range sec.Subsections { 187 | // Don't suggest subsections that are already defined 188 | for _, definedSubsection := range sec.Subsections { 189 | if subsections.Name == definedSubsection.Name { 190 | continue subsections 191 | } 192 | } 193 | 194 | items = append(items, protocol.CompletionItem{ 195 | Label: subsections.Name, 196 | Kind: protocol.CompletionItemKindModule, 197 | Documentation: protocol.MarkupContent{ 198 | Kind: protocol.Markdown, 199 | Value: fmt.Sprintf("Subsection of %s", sec.Name), 200 | }, 201 | }) 202 | } 203 | 204 | return &protocol.CompletionList{ 205 | Items: items, 206 | }, nil 207 | } 208 | 209 | func (h Handler) CompletionResolve(ctx context.Context, params *protocol.CompletionItem) (*protocol.CompletionItem, error) { 210 | return nil, errors.New("unimplemented") 211 | } 212 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? ( 2 | let 3 | inherit (builtins) fetchTree fromJSON readFile; 4 | inherit ((fromJSON (readFile ./flake.lock)).nodes) nixpkgs gomod2nix; 5 | in 6 | import (fetchTree nixpkgs.locked) { 7 | overlays = [ 8 | (import "${fetchTree gomod2nix.locked}/overlay.nix") 9 | ]; 10 | } 11 | ) 12 | , buildGoApplication ? pkgs.buildGoApplication 13 | }: 14 | 15 | buildGoApplication { 16 | pname = "hyprls"; 17 | version = "0.7.0"; 18 | pwd = ./.; 19 | src = ./.; 20 | 21 | postBuild = '' 22 | rm $GOPATH/bin/generate 23 | ''; 24 | 25 | modules = ./gomod2nix.toml; 26 | checkFlags = ["-skip=TestHighLevelParse"]; # not yet implemented 27 | } 28 | -------------------------------------------------------------------------------- /demo-colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyprland-community/hyprls/04c579ad66e7d1e841896fb28ef8b7bfcf67af40/demo-colors.png -------------------------------------------------------------------------------- /demo-completion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyprland-community/hyprls/04c579ad66e7d1e841896fb28ef8b7bfcf67af40/demo-completion.png -------------------------------------------------------------------------------- /demo-hover-keyword.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyprland-community/hyprls/04c579ad66e7d1e841896fb28ef8b7bfcf67af40/demo-hover-keyword.png -------------------------------------------------------------------------------- /demo-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyprland-community/hyprls/04c579ad66e7d1e841896fb28ef8b7bfcf67af40/demo-hover.png -------------------------------------------------------------------------------- /demo-symbols.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyprland-community/hyprls/04c579ad66e7d1e841896fb28ef8b7bfcf67af40/demo-symbols.png -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1710146030, 9 | "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "gomod2nix": { 22 | "inputs": { 23 | "flake-utils": [ 24 | "flake-utils" 25 | ], 26 | "nixpkgs": [ 27 | "nixpkgs" 28 | ] 29 | }, 30 | "locked": { 31 | "lastModified": 1717050755, 32 | "narHash": "sha256-C9IEHABulv2zEDFA+Bf0E1nmfN4y6MIUe5eM2RCrDC0=", 33 | "owner": "nix-community", 34 | "repo": "gomod2nix", 35 | "rev": "31b6d2e40b36456e792cd6cf50d5a8ddd2fa59a1", 36 | "type": "github" 37 | }, 38 | "original": { 39 | "owner": "nix-community", 40 | "repo": "gomod2nix", 41 | "type": "github" 42 | } 43 | }, 44 | "nixpkgs": { 45 | "locked": { 46 | "lastModified": 1720957393, 47 | "narHash": "sha256-oedh2RwpjEa+TNxhg5Je9Ch6d3W1NKi7DbRO1ziHemA=", 48 | "owner": "NixOS", 49 | "repo": "nixpkgs", 50 | "rev": "693bc46d169f5af9c992095736e82c3488bf7dbb", 51 | "type": "github" 52 | }, 53 | "original": { 54 | "owner": "NixOS", 55 | "ref": "nixos-unstable", 56 | "repo": "nixpkgs", 57 | "type": "github" 58 | } 59 | }, 60 | "root": { 61 | "inputs": { 62 | "flake-utils": "flake-utils", 63 | "gomod2nix": "gomod2nix", 64 | "nixpkgs": "nixpkgs" 65 | } 66 | }, 67 | "systems": { 68 | "locked": { 69 | "lastModified": 1681028828, 70 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 71 | "owner": "nix-systems", 72 | "repo": "default", 73 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 74 | "type": "github" 75 | }, 76 | "original": { 77 | "owner": "nix-systems", 78 | "repo": "default", 79 | "type": "github" 80 | } 81 | } 82 | }, 83 | "root": "root", 84 | "version": 7 85 | } 86 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A LSP server for Hyprland config files"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | gomod2nix.url = "github:nix-community/gomod2nix"; 8 | gomod2nix.inputs.nixpkgs.follows = "nixpkgs"; 9 | gomod2nix.inputs.flake-utils.follows = "flake-utils"; 10 | }; 11 | 12 | outputs = { self, nixpkgs, flake-utils, gomod2nix }: 13 | (flake-utils.lib.eachDefaultSystem 14 | (system: 15 | let 16 | pkgs = nixpkgs.legacyPackages.${system}; 17 | 18 | # The current default sdk for macOS fails to compile go projects, so we use a newer one for now. 19 | # This has no effect on other platforms. 20 | callPackage = pkgs.darwin.apple_sdk_11_0.callPackage or pkgs.callPackage; 21 | in 22 | { 23 | packages.default = callPackage ./. { 24 | inherit (gomod2nix.legacyPackages.${system}) buildGoApplication; 25 | }; 26 | devShells.default = callPackage ./shell.nix { 27 | inherit (gomod2nix.legacyPackages.${system}) mkGoEnv gomod2nix; 28 | }; 29 | }) 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hyprland-community/hyprls 2 | 3 | go 1.24.3 4 | 5 | require ( 6 | github.com/MakeNowJust/heredoc v1.0.0 7 | github.com/PuerkitoBio/goquery v1.10.3 8 | github.com/davecgh/go-spew v1.1.1 9 | go.lsp.dev/jsonrpc2 v0.10.0 10 | go.uber.org/multierr v1.11.0 11 | go.uber.org/zap v1.27.0 12 | ) 13 | 14 | require ( 15 | github.com/andybalholm/cascadia v1.3.3 // indirect 16 | go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 // indirect 17 | go.lsp.dev/uri v0.3.0 // indirect 18 | golang.org/x/net v0.39.0 // indirect 19 | golang.org/x/text v0.24.0 // indirect 20 | ) 21 | 22 | require ( 23 | github.com/MakeNowJust/heredoc/v2 v2.0.1 24 | github.com/anaskhan96/soup v1.2.5 25 | github.com/evorts/html-to-markdown v0.0.10 26 | github.com/mazznoer/csscolorparser v0.1.6 27 | github.com/metal3d/go-slugify v0.0.0-20160607203414-7ac2014b2f23 28 | github.com/segmentio/asm v1.2.0 // indirect 29 | github.com/segmentio/encoding v0.4.0 // indirect 30 | github.com/yuin/goldmark v1.7.12 31 | go.lsp.dev/protocol v0.12.0 32 | golang.org/x/sys v0.32.0 // indirect 33 | ) 34 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= 2 | github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= 3 | github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM= 4 | github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE= 5 | github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= 6 | github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo= 7 | github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y= 8 | github.com/anaskhan96/soup v1.2.5 h1:V/FHiusdTrPrdF4iA1YkVxsOpdNcgvqT1hG+YtcZ5hM= 9 | github.com/anaskhan96/soup v1.2.5/go.mod h1:6YnEp9A2yywlYdM4EgDz9NEHclocMepEtku7wg6Cq3s= 10 | github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo= 11 | github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= 12 | github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= 13 | github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= 14 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 16 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 17 | github.com/evorts/html-to-markdown v0.0.10 h1:8t+OvbvAtooQjAbUH6ekVBxT8DF+t8a5RPPx/lf3NDA= 18 | github.com/evorts/html-to-markdown v0.0.10/go.mod h1:Tng5L77N4uR2zXaoIiIxsQlrQoKPP84rhmEGrOnkBNw= 19 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 20 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 21 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 22 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 23 | github.com/mazznoer/csscolorparser v0.1.6 h1:uK6p5zBA8HaQZJSInHgHVmkVBodUAy+6snSmKJG7pqA= 24 | github.com/mazznoer/csscolorparser v0.1.6/go.mod h1:OQRVvgCyHDCAquR1YWfSwwaDcM0LhnSffGnlbOew/3I= 25 | github.com/metal3d/go-slugify v0.0.0-20160607203414-7ac2014b2f23 h1:UhdgaX0bR9ZSz+jRK6cPQLU94Q3KB14ijuHum8YbvBA= 26 | github.com/metal3d/go-slugify v0.0.0-20160607203414-7ac2014b2f23/go.mod h1:sCALRmIiknhX1lHQ8flRsWKMazu5BBjMochEnDupxrk= 27 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 28 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 29 | github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= 30 | github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= 31 | github.com/segmentio/encoding v0.4.0 h1:MEBYvRqiUB2nfR2criEXWqwdY6HJOUrCn5hboVOVmy8= 32 | github.com/segmentio/encoding v0.4.0/go.mod h1:/d03Cd8PoaDeceuhUUUQWjU0KhWjrmYrWPgtJHYZSnI= 33 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 34 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 35 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 36 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 37 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 38 | github.com/yuin/goldmark v1.7.12 h1:YwGP/rrea2/CnCtUHgjuolG/PnMxdQtPMO5PvaE2/nY= 39 | github.com/yuin/goldmark v1.7.12/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= 40 | go.lsp.dev/jsonrpc2 v0.10.0 h1:Pr/YcXJoEOTMc/b6OTmcR1DPJ3mSWl/SWiU1Cct6VmI= 41 | go.lsp.dev/jsonrpc2 v0.10.0/go.mod h1:fmEzIdXPi/rf6d4uFcayi8HpFP1nBF99ERP1htC72Ac= 42 | go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 h1:hCzQgh6UcwbKgNSRurYWSqh8MufqRRPODRBblutn4TE= 43 | go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2/go.mod h1:gtSHRuYfbCT0qnbLnovpie/WEmqyJ7T4n6VXiFMBtcw= 44 | go.lsp.dev/protocol v0.12.0 h1:tNprUI9klQW5FAFVM4Sa+AbPFuVQByWhP1ttNUAjIWg= 45 | go.lsp.dev/protocol v0.12.0/go.mod h1:Qb11/HgZQ72qQbeyPfJbu3hZBH23s1sr4st8czGeDMQ= 46 | go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo= 47 | go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I= 48 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 49 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 50 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 51 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 52 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 53 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 54 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 55 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 56 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 57 | golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 58 | golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= 59 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 60 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 61 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 62 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 63 | golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 64 | golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 65 | golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 66 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 67 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 68 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 69 | golang.org/x/net v0.0.0-20200320220750-118fecf932d8 h1:1+zQlQqEEhUeStBTi653GZAnAuivZq/2hz+Iz+OP7rg= 70 | golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 71 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 72 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 73 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 74 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 75 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 76 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 77 | golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 78 | golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 79 | golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= 80 | golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= 81 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 82 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 83 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 84 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 85 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 86 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 87 | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 88 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 89 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 90 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 91 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 92 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 93 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 94 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 95 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 96 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 97 | golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= 98 | golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 99 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 100 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 101 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 102 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 103 | golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= 104 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 105 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 106 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 107 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 108 | golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= 109 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 110 | golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= 111 | golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= 112 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 113 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 114 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 115 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 116 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 117 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 118 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 119 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 120 | golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 121 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 122 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 123 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 124 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 125 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 126 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 127 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 128 | golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 129 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 130 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 131 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 132 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 133 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 134 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 135 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 136 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 137 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 138 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 139 | -------------------------------------------------------------------------------- /gomod2nix.toml: -------------------------------------------------------------------------------- 1 | schema = 3 2 | 3 | [mod] 4 | [mod."github.com/MakeNowJust/heredoc"] 5 | version = "v1.0.0" 6 | hash = "sha256-8hKERAVV1Pew84kc9GkW23dcO8uIUx/+tJQLi+oPwqE=" 7 | [mod."github.com/PuerkitoBio/goquery"] 8 | version = "v1.5.1" 9 | hash = "sha256-GqxIdhuN1L3enT8pNlWm+rmkdN5uMfF8TfpxhAHhEDQ=" 10 | [mod."github.com/anaskhan96/soup"] 11 | version = "v1.2.5" 12 | hash = "sha256-t8yCyK2y7x2qaI/3Yw16q3zVFqu+3acLcPgTr1MIKWg=" 13 | [mod."github.com/andybalholm/cascadia"] 14 | version = "v1.1.0" 15 | hash = "sha256-mL40/Q9iXBzzMHwMomqTOpp9rnI/MRsufb5cIU1MMPg=" 16 | [mod."github.com/davecgh/go-spew"] 17 | version = "v1.1.1" 18 | hash = "sha256-nhzSUrE1fCkN0+RL04N4h8jWmRFPPPWbCuDc7Ss0akI=" 19 | [mod."github.com/evorts/html-to-markdown"] 20 | version = "v0.0.10" 21 | hash = "sha256-JV/v/rbKakJuSUTcOtRabeB0EzZRL+Nw5fYW5DRMxPg=" 22 | [mod."github.com/mazznoer/csscolorparser"] 23 | version = "v0.1.3" 24 | hash = "sha256-v8WyeQKL+SkOar4nftTFrvyitVjFPZRoVeuksp8OnMk=" 25 | [mod."github.com/metal3d/go-slugify"] 26 | version = "v0.0.0-20160607203414-7ac2014b2f23" 27 | hash = "sha256-vAfoTRKnDwLi7xLmFXP88auA8ZLJ8W6sLIj569hlkTk=" 28 | [mod."github.com/segmentio/asm"] 29 | version = "v1.2.0" 30 | hash = "sha256-zbNuKxNrUDUc6IlmRQNuJQzVe5Ol/mqp7srDg9IMMqs=" 31 | [mod."github.com/segmentio/encoding"] 32 | version = "v0.4.0" 33 | hash = "sha256-4pWI9eTZRRDP9kO8rG6vbLCtBVVRLtbCJKd0Z2+8JoU=" 34 | [mod."github.com/yuin/goldmark"] 35 | version = "v1.7.1" 36 | hash = "sha256-3EUgwoZRRs2jNBWSbB0DGNmfBvx7CeAgEwyUdaRaeR4=" 37 | [mod."go.lsp.dev/jsonrpc2"] 38 | version = "v0.10.0" 39 | hash = "sha256-RbRsMYVBLR7ZDHHGMooycrkdbIauMXkQjVOGP7ggSgM=" 40 | [mod."go.lsp.dev/pkg"] 41 | version = "v0.0.0-20210717090340-384b27a52fb2" 42 | hash = "sha256-TxS0Iqe1wbIaFe7MWZJRQdgqhKE8i8CggaGSV9zU1Vg=" 43 | [mod."go.lsp.dev/protocol"] 44 | version = "v0.12.0" 45 | hash = "sha256-rZgWIQpHHeYpJL8kiHJ21m1BHXAcr+4Xv2Yh9RNw3s4=" 46 | [mod."go.lsp.dev/uri"] 47 | version = "v0.3.0" 48 | hash = "sha256-jGP0N7Gf+bql5oJraUo33sXqWg7AKOTj0D8b4paV4dc=" 49 | [mod."go.uber.org/multierr"] 50 | version = "v1.11.0" 51 | hash = "sha256-Lb6rHHfR62Ozg2j2JZy3MKOMKdsfzd1IYTR57r3Mhp0=" 52 | [mod."go.uber.org/zap"] 53 | version = "v1.27.0" 54 | hash = "sha256-8655KDrulc4Das3VRduO9MjCn8ZYD5WkULjCvruaYsU=" 55 | [mod."golang.org/x/net"] 56 | version = "v0.0.0-20200320220750-118fecf932d8" 57 | hash = "sha256-YdLYymyqwWmiBR6QJdgvOq+QbQvmAh2VAk4KnB+GZAQ=" 58 | [mod."golang.org/x/sys"] 59 | version = "v0.19.0" 60 | hash = "sha256-cmuL31TYLJmDm/fDnI2Sn0wB88cpdOHV1+urorsJWx4=" 61 | [mod."golang.org/x/text"] 62 | version = "v0.3.0" 63 | hash = "sha256-0FFbaxF1ZuAQF3sCcA85e8MO6prFeHint36inija4NY=" 64 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | package hyprls 2 | 3 | import ( 4 | "context" 5 | 6 | "go.lsp.dev/protocol" 7 | "go.uber.org/zap" 8 | ) 9 | 10 | type Handler struct { 11 | protocol.Server 12 | Logger *zap.Logger 13 | } 14 | 15 | func NewHandler(ctx context.Context, server protocol.Server, logger *zap.Logger) (Handler, context.Context, error) { 16 | 17 | return Handler{ 18 | Server: server, 19 | Logger: logger, 20 | }, context.WithValue(ctx, "state", state{}), nil 21 | } 22 | 23 | func (h Handler) Initialize(ctx context.Context, params *protocol.InitializeParams) (*protocol.InitializeResult, error) { 24 | logger = h.Logger 25 | return &protocol.InitializeResult{ 26 | Capabilities: protocol.ServerCapabilities{ 27 | HoverProvider: true, 28 | DocumentSymbolProvider: true, 29 | ColorProvider: true, 30 | CompletionProvider: &protocol.CompletionOptions{ 31 | ResolveProvider: false, 32 | TriggerCharacters: []string{}, 33 | }, 34 | TextDocumentSync: protocol.TextDocumentSyncOptions{ 35 | OpenClose: true, 36 | Change: protocol.TextDocumentSyncKindFull, 37 | }, 38 | }, 39 | ServerInfo: &protocol.ServerInfo{ 40 | Name: "hyprls", 41 | Version: Version, 42 | }, 43 | }, nil 44 | } 45 | 46 | func (h Handler) Initialized(ctx context.Context, params *protocol.InitializedParams) error { 47 | return nil 48 | } 49 | 50 | func (h Handler) Shutdown(ctx context.Context) error { 51 | return nil 52 | } 53 | 54 | func (h Handler) Exit(ctx context.Context) error { 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /hover.go: -------------------------------------------------------------------------------- 1 | package hyprls 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/MakeNowJust/heredoc" 9 | parser_data "github.com/hyprland-community/hyprls/parser/data" 10 | "go.lsp.dev/protocol" 11 | ) 12 | 13 | func (h Handler) Hover(ctx context.Context, params *protocol.HoverParams) (*protocol.Hover, error) { 14 | line, err := currentLine(params.TextDocument.URI, params.Position) 15 | if err != nil { 16 | return nil, fmt.Errorf("while getting current line of file: %w", err) 17 | } 18 | 19 | if !strings.Contains(line, "=") { 20 | return nil, nil 21 | } 22 | 23 | // key is word before the equal sign. [0] is safe since we checked for "=" above 24 | key := strings.TrimSpace(strings.Split(line, "=")[0]) 25 | 26 | indexOfFirstNonWhitespace := strings.IndexFunc(line, func(r rune) bool { 27 | return r != ' ' && r != '\t' 28 | }) 29 | indexOfLastNonWhitespace := strings.LastIndexFunc(line, func(r rune) bool { 30 | return r != ' ' && r != '\t' 31 | }) + 1 32 | 33 | for _, section := range parser_data.Sections { 34 | if def := section.VariableDefinition(key); def != nil { 35 | return &protocol.Hover{ 36 | Contents: protocol.MarkupContent{ 37 | Kind: protocol.Markdown, 38 | Value: heredoc.Docf(`### %s: %s (%s) 39 | %s 40 | 41 | - Defaults to: %s 42 | `, strings.Join(section.Path, ":"), def.Name, def.Type, def.Description, def.PrettyDefault()), 43 | }, 44 | Range: &protocol.Range{ 45 | Start: protocol.Position{ 46 | Line: params.Position.Line, 47 | Character: uint32(indexOfFirstNonWhitespace), 48 | }, 49 | End: protocol.Position{ 50 | Line: params.Position.Line, 51 | Character: uint32(indexOfLastNonWhitespace), 52 | }, 53 | }, 54 | }, nil 55 | } else if kw, found := parser_data.FindKeyword(key); found { 56 | flagsLine := "" 57 | if len(kw.Flags) > 0 { 58 | flagsLine = fmt.Sprintf("\n- Accepts the following flags: %s\n", strings.Join(kw.Flags, ", ")) 59 | } 60 | return &protocol.Hover{ 61 | Contents: protocol.MarkupContent{ 62 | Kind: protocol.Markdown, 63 | Value: fmt.Sprintf("### %s [[docs]](%s)%s\n%s", kw.Name, kw.DocumentationLink(), flagsLine, kw.Description), 64 | }, 65 | Range: &protocol.Range{ 66 | Start: protocol.Position{ 67 | Line: params.Position.Line, 68 | Character: uint32(indexOfFirstNonWhitespace), 69 | }, 70 | End: protocol.Position{ 71 | Line: params.Position.Line, 72 | Character: uint32(indexOfLastNonWhitespace), 73 | }, 74 | }, 75 | }, nil 76 | } 77 | } 78 | 79 | return nil, nil 80 | } 81 | -------------------------------------------------------------------------------- /hyprland_version: -------------------------------------------------------------------------------- 1 | 0.49.0 2 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package hyprls 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "os" 7 | "path/filepath" 8 | 9 | "go.lsp.dev/jsonrpc2" 10 | "go.lsp.dev/protocol" 11 | "go.uber.org/multierr" 12 | "go.uber.org/zap" 13 | ) 14 | 15 | var Version string 16 | 17 | func StartServer(logger *zap.Logger, logClientIn string) { 18 | logger.Debug("starting server") 19 | conn := jsonrpc2.NewConn(jsonrpc2.NewStream(&readWriteCloser{ 20 | reader: os.Stdin, 21 | writer: os.Stdout, 22 | logAt: logClientIn, 23 | })) 24 | handler, ctx, err := NewHandler(context.Background(), protocol.ServerDispatcher(conn, logger), logger) 25 | if err != nil { 26 | logger.Sugar().Fatalf("while initializing handler: %w", err) 27 | } 28 | 29 | conn.Go(ctx, protocol.ServerHandler(handler, jsonrpc2.MethodNotFoundHandler)) 30 | <-conn.Done() 31 | } 32 | 33 | type readWriteCloser struct { 34 | reader io.ReadCloser 35 | writer io.WriteCloser 36 | logAt string 37 | } 38 | 39 | func (r *readWriteCloser) Read(b []byte) (int, error) { 40 | var f *os.File 41 | if r.logAt != "" { 42 | f, _ = os.OpenFile(filepath.Join(r.logAt, "client-request-from.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 43 | } 44 | n, err := r.reader.Read(b) 45 | if r.logAt != "" { 46 | if err != nil { 47 | f.Write([]byte(err.Error() + "\n")) 48 | } else { 49 | f.Write(b) 50 | } 51 | } 52 | return n, err 53 | } 54 | 55 | func (r *readWriteCloser) Write(b []byte) (int, error) { 56 | if r.logAt != "" { 57 | f, _ := os.OpenFile(filepath.Join(r.logAt, "client-response-to.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 58 | f.Write(b) 59 | } 60 | return r.writer.Write(b) 61 | } 62 | 63 | func (r *readWriteCloser) Close() error { 64 | return multierr.Append(r.reader.Close(), r.writer.Close()) 65 | } 66 | -------------------------------------------------------------------------------- /parser/data/generate/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/MakeNowJust/heredoc" 9 | . "github.com/hyprland-community/hyprls/parser/data" 10 | ) 11 | 12 | func main() { 13 | rootSections := make([]SectionDefinition, 0) 14 | for _, section := range Sections { 15 | if len(section.Path) == 1 { 16 | rootSections = append(rootSections, section) 17 | } 18 | } 19 | 20 | jsoned, _ := json.Marshal(struct { 21 | Sections []SectionDefinition `json:"sections"` 22 | Keywords []KeywordDefinition `json:"keywords"` 23 | }{ 24 | Sections: rootSections, 25 | Keywords: Keywords, 26 | }) 27 | os.WriteFile(os.Args[1], jsoned, 0644) 28 | 29 | fmt.Println(heredoc.Doc(`package parser 30 | 31 | import "image/color" 32 | 33 | type Configuration struct { 34 | CustomVariables map[string]string 35 | `)) 36 | 37 | for _, section := range rootSections { 38 | fmt.Printf("\t%s %s `json:\"%s\"`\n", section.Name(), section.TypeName(), section.JSONName()) 39 | } 40 | 41 | fmt.Println("}\n\n") 42 | 43 | for _, section := range rootSections { 44 | fmt.Println(section.Typedef()) 45 | } 46 | 47 | fmt.Println("\n\n") 48 | } 49 | -------------------------------------------------------------------------------- /parser/data/keywords.go: -------------------------------------------------------------------------------- 1 | package parser_data 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type KeywordDefinition struct { 9 | Name string 10 | Description string 11 | documentationHeadingSlug string 12 | documentationFile string 13 | Flags []string 14 | } 15 | 16 | func (k KeywordDefinition) DocumentationLink() string { 17 | return fmt.Sprintf("https://wiki.hyprland.org/Configuring/%s/#%s", k.documentationFile, k.documentationHeadingSlug) 18 | } 19 | 20 | var Keywords = []KeywordDefinition{ 21 | { 22 | Name: "submap", 23 | documentationHeadingSlug: "submaps", 24 | documentationFile: "Binds", 25 | Flags: []string{}, 26 | }, 27 | { 28 | Name: "windowrule", 29 | documentationHeadingSlug: "window-rules-v1", 30 | documentationFile: "Window-Rules", 31 | Flags: []string{}, 32 | }, 33 | { 34 | Name: "windowrulev2", 35 | documentationHeadingSlug: "window-rules-v2", 36 | documentationFile: "Window-Rules", 37 | Flags: []string{}, 38 | }, 39 | { 40 | Name: "layerrule", 41 | documentationHeadingSlug: "layer-rules", 42 | documentationFile: "Window-Rules", 43 | Flags: []string{}, 44 | }, 45 | { 46 | Name: "workspace", 47 | documentationHeadingSlug: "workspace-rules", 48 | documentationFile: "Workspace-Rules", 49 | Flags: []string{}, 50 | }, 51 | { 52 | Name: "animation", 53 | documentationHeadingSlug: "general", 54 | documentationFile: "Animations", 55 | Flags: []string{}, 56 | }, 57 | { 58 | Name: "bezier", 59 | documentationHeadingSlug: "curves", 60 | documentationFile: "Animations", 61 | Flags: []string{}, 62 | }, 63 | { 64 | Name: "exec", 65 | documentationHeadingSlug: "executing", 66 | documentationFile: "Keywords", 67 | Flags: []string{}, 68 | }, 69 | { 70 | Name: "exec-once", 71 | documentationHeadingSlug: "executing", 72 | documentationFile: "Keywords", 73 | Flags: []string{}, 74 | }, 75 | { 76 | Name: "source", 77 | documentationHeadingSlug: "sourcing-multi-file", 78 | documentationFile: "Keywords", 79 | Flags: []string{}, 80 | }, 81 | { 82 | Name: "env", 83 | documentationHeadingSlug: "setting-the-environment", 84 | documentationFile: "Keywords", 85 | Flags: []string{"d"}, 86 | }, 87 | { 88 | Name: "monitor", 89 | documentationHeadingSlug: "general", 90 | documentationFile: "Monitors", 91 | Flags: []string{}, 92 | }, 93 | { 94 | Name: "bind", 95 | documentationHeadingSlug: "basic", 96 | documentationFile: "Binds", 97 | Flags: []string{"r", "l", "e", "n", "m", "t", "i"}, 98 | }, 99 | { 100 | Name: "unbind", 101 | documentationHeadingSlug: "unbind", 102 | documentationFile: "Binds", 103 | Flags: []string{}, 104 | }, 105 | } 106 | 107 | func FindKeyword(key string) (keyword KeywordDefinition, found bool) { 108 | for _, k := range Keywords { 109 | if key == k.Name { 110 | return k, true 111 | } 112 | 113 | if strings.HasPrefix(key, k.Name) { 114 | if len(k.Flags) > 0 { 115 | usedFlags := strings.Split(strings.TrimPrefix(key, k.Name), "") 116 | for _, used := range usedFlags { 117 | flagIsValid := false 118 | for _, available := range k.Flags { 119 | if used == available { 120 | flagIsValid = true 121 | break 122 | } 123 | } 124 | if !flagIsValid { 125 | return KeywordDefinition{}, false 126 | } 127 | } 128 | return k, true 129 | } 130 | } 131 | } 132 | return KeywordDefinition{}, false 133 | } 134 | -------------------------------------------------------------------------------- /parser/data/keywords_test.go: -------------------------------------------------------------------------------- 1 | package parser_data 2 | 3 | import "testing" 4 | 5 | func TestFindKeyword(t *testing.T) { 6 | k, found := FindKeyword("submap") 7 | if !found { 8 | t.Fatal("submap not found") 9 | } 10 | if k.Name != "submap" { 11 | t.Fatalf("unexpected name: %q", k.Name) 12 | } 13 | 14 | k, found = FindKeyword("binde") 15 | if !found { 16 | t.Fatal("binde not found") 17 | } 18 | if k.Name != "bind" { 19 | t.Fatalf("unexpected name: %q", k.Name) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /parser/data/load.go: -------------------------------------------------------------------------------- 1 | package parser_data 2 | 3 | import ( 4 | "bytes" 5 | "embed" 6 | _ "embed" 7 | "fmt" 8 | "os" 9 | "path/filepath" 10 | "regexp" 11 | "strconv" 12 | "strings" 13 | 14 | "github.com/PuerkitoBio/goquery" 15 | "github.com/anaskhan96/soup" 16 | "github.com/metal3d/go-slugify" 17 | "github.com/yuin/goldmark" 18 | "github.com/yuin/goldmark/extension" 19 | 20 | html2markdown "github.com/evorts/html-to-markdown" 21 | ) 22 | 23 | var html2md = html2markdown.NewConverter("wiki.hyprlang.org", true, &html2markdown.Options{}) 24 | var md = goldmark.New(goldmark.WithExtensions(extension.GFM)) 25 | 26 | func debug(msg string, fmtArgs ...any) { 27 | // fmt.Fprintf(os.Stderr, msg, fmtArgs...) 28 | } 29 | 30 | //go:embed sources/Variables.md 31 | var documentationSource []byte 32 | 33 | //go:embed sources/Master-Layout.md 34 | var masterLayoutDocumentationSource []byte 35 | 36 | //go:embed sources/Dwindle-Layout.md 37 | var dwindleLayoutDocumentationSource []byte 38 | 39 | //go:embed sources/*.md 40 | var documentationSources embed.FS 41 | 42 | var Sections = []SectionDefinition{} 43 | 44 | var undocumentedGeneralSectionVariables = []VariableDefinition{ 45 | { 46 | Name: "autogenerated", 47 | Description: "Whether this configuration was autogenerated", 48 | Type: "bool", 49 | Default: "1", 50 | }, 51 | } 52 | 53 | func (s SectionDefinition) VariableDefinition(name string) *VariableDefinition { 54 | for _, v := range s.Variables { 55 | if v.Name == name { 56 | return &v 57 | } 58 | } 59 | return nil 60 | } 61 | 62 | func init() { 63 | html2md.AddRules(html2markdown.Rule{ 64 | Filter: []string{"a"}, 65 | Replacement: func(content string, selec *goquery.Selection, options *html2markdown.Options) *string { 66 | href, _ := selec.Attr("href") 67 | if strings.HasPrefix(href, "../") { 68 | href = strings.Replace(href, "../", "https://wiki.hyprland.org/Configuring/", 1) 69 | } 70 | result := fmt.Sprintf("[%s](%s)", content, href) 71 | return html2markdown.String(result) 72 | }, 73 | }) 74 | 75 | Sections = parseDocumentationMarkdown(documentationSource, 3) 76 | Sections = append(Sections, parseDocumentationMarkdownWithRootSectionName(masterLayoutDocumentationSource, 2, "Master")...) 77 | Sections = append(Sections, parseDocumentationMarkdownWithRootSectionName(dwindleLayoutDocumentationSource, 2, "Dwindle")...) 78 | addVariableDefsOnSection("General", undocumentedGeneralSectionVariables) 79 | 80 | for i, kw := range Keywords { 81 | if kw.Description != "" { 82 | continue 83 | } 84 | 85 | content, err := documentationSources.ReadFile(filepath.Join("sources", kw.documentationFile+".md")) 86 | if err != nil { 87 | fmt.Fprintf(os.Stderr, "Failed to read documentation file for %s: %s\n", kw.Name, err) 88 | continue 89 | } 90 | 91 | document := markdownToHTML(content) 92 | headings := make([]soup.Root, 0) 93 | for _, t := range []string{"h1", "h2", "h3", "h4", "h5", "h6"} { 94 | headings = append(headings, document.FindAll(t)...) 95 | } 96 | var heading soup.Root 97 | found := false 98 | for _, h := range headings { 99 | if id, ok := h.Attrs()["id"]; ok && id == kw.documentationHeadingSlug { 100 | heading = h 101 | found = true 102 | break 103 | } 104 | anchor := slugify.Marshal(strings.TrimSpace(h.Text()), true) 105 | anchor = regexp.MustCompile(`^weight-%d+-title-`).ReplaceAllString(anchor, "") 106 | if anchor == kw.documentationHeadingSlug { 107 | heading = h 108 | found = true 109 | break 110 | } 111 | } 112 | if !found { 113 | fmt.Fprintf(os.Stderr, "Failed to find heading %s in %s\n", kw.documentationHeadingSlug, kw.documentationFile) 114 | continue 115 | } 116 | Keywords[i].Description, _ = html2md.ConvertString(htmlBetweenHeadingAndNextHeading(heading, heading)) 117 | } 118 | } 119 | 120 | func addVariableDefsOnSection(sectionName string, variables []VariableDefinition) { 121 | for i, sec := range Sections { 122 | if sec.Name() != sectionName { 123 | continue 124 | } 125 | Sections[i].Variables = append(Sections[i].Variables, variables...) 126 | } 127 | } 128 | 129 | func parseDocumentationMarkdownWithRootSectionName(source []byte, headingRootLevel int, rootSectionName string) []SectionDefinition { 130 | sections := parseDocumentationMarkdown(source, headingRootLevel) 131 | for i := range sections { 132 | sections[i].Path[0] = rootSectionName 133 | } 134 | return sections 135 | } 136 | 137 | func markdownToHTML(source []byte) soup.Root { 138 | var html bytes.Buffer 139 | err := md.Convert(source, &html) 140 | if err != nil { 141 | panic(err) 142 | } 143 | 144 | return soup.HTMLParse(html.String()) 145 | } 146 | 147 | func parseDocumentationMarkdown(source []byte, headingRootLevel int) (sections []SectionDefinition) { 148 | document := markdownToHTML(source) 149 | for _, table := range document.FindAll("table") { 150 | if !arraysEqual(tableHeaderCells(table), []string{"name", "description", "type", "default"}) { 151 | continue 152 | } 153 | 154 | // fmt.Printf("Processing table %s\n", table.HTML()) 155 | section := SectionDefinition{ 156 | Path: tablePath(table, headingRootLevel), 157 | } 158 | section.Variables = make([]VariableDefinition, 0) 159 | for _, row := range table.FindAll("tr")[1:] { 160 | cells := row.FindAll("td") 161 | if len(cells) != 4 { 162 | continue 163 | } 164 | 165 | section.Variables = append(section.Variables, VariableDefinition{ 166 | Name: cells[0].FullText(), 167 | Description: cells[1].FullText(), 168 | Type: cells[2].FullText(), 169 | Default: cells[3].FullText()}) 170 | } 171 | sections = append(sections, section) 172 | } 173 | 174 | for i, section := range sections { 175 | if len(section.Path) == 1 { 176 | sections[i] = section.AttachSubsections(sections) 177 | } 178 | } 179 | return sections 180 | } 181 | 182 | func (s SectionDefinition) AttachSubsections(sections []SectionDefinition) SectionDefinition { 183 | // TODO make it work for recursively nested sections 184 | s.Subsections = make([]SectionDefinition, 0) 185 | for _, section := range sections { 186 | if len(section.Path) == 1 { 187 | continue 188 | } 189 | if section.Path[0] == s.Name() { 190 | debug("adding %s to %s\n", section.Name(), s.Name()) 191 | s.Subsections = append(s.Subsections, section) 192 | } 193 | } 194 | return s 195 | } 196 | 197 | func tableHeaderCells(table soup.Root) []string { 198 | headerCells := table.FindAll("th") 199 | cells := make([]string, 0, len(headerCells)) 200 | for _, cell := range headerCells { 201 | cells = append(cells, cell.FullText()) 202 | } 203 | return cells 204 | } 205 | 206 | func tablePath(table soup.Root, headingRootLevel int) []string { 207 | header := backtrackToNearestHeader(table) 208 | level, err := strconv.Atoi(header.NodeValue[1:]) 209 | if err != nil { 210 | panic(err) 211 | } 212 | if level <= headingRootLevel { 213 | return []string{header.FullText()} 214 | } 215 | return append(tablePath(header.FindPrevElementSibling(), headingRootLevel), header.FullText()) 216 | } 217 | 218 | func backtrackToNearestHeader(element soup.Root) soup.Root { 219 | if element.NodeValue != "table" { 220 | debug("backtracking to nearest header from %s\n", element.HTML()) 221 | } 222 | if regexp.MustCompile(`^h[1-6]$`).MatchString(element.NodeValue) { 223 | debug("-> returning from backtrack with %s\n", element.HTML()) 224 | return element 225 | } 226 | prev := element.FindPrevElementSibling() 227 | debug("-> prev is %s\n", prev.HTML()) 228 | return backtrackToNearestHeader(prev) 229 | } 230 | 231 | func htmlBetweenHeadingAndNextHeading(heading soup.Root, element soup.Root) string { 232 | next := element.FindNextElementSibling() 233 | if isHeading(next) && headingLevel(next) == headingLevel(heading) { 234 | return "" 235 | } 236 | 237 | defer func() { 238 | if crash := recover(); crash != nil { 239 | if os.Getenv("DEBUG") != "" { 240 | fmt.Fprintf(os.Stderr, "Panic while rendering %s\n", next.HTML()) 241 | } 242 | } 243 | }() 244 | 245 | rendered := next.HTML() 246 | 247 | return rendered + htmlBetweenHeadingAndNextHeading(heading, next) 248 | } 249 | 250 | func isHeading(element soup.Root) bool { 251 | return regexp.MustCompile(`^h[1-6]$`).MatchString(element.NodeValue) 252 | } 253 | 254 | func headingLevel(heading soup.Root) int { 255 | level, err := strconv.Atoi(heading.NodeValue[1:]) 256 | if err != nil { 257 | panic(err) 258 | } 259 | return level 260 | } 261 | 262 | func arraysEqual(a, b []string) bool { 263 | if len(a) != len(b) { 264 | return false 265 | } 266 | for i, v := range a { 267 | if strings.TrimSpace(v) != strings.TrimSpace(b[i]) { 268 | return false 269 | } 270 | } 271 | return true 272 | } 273 | 274 | func toPascalCase(s string) string { 275 | out := "" 276 | for _, word := range regexp.MustCompile(`[-_\.]`).Split(s, -1) { 277 | out += strings.ToUpper(word[:1]) + word[1:] 278 | } 279 | return out 280 | } 281 | -------------------------------------------------------------------------------- /parser/data/sections.go: -------------------------------------------------------------------------------- 1 | package parser_data 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | func FindSectionDefinitionByName(name string) *SectionDefinition { 9 | for _, sec := range Sections { 10 | if sec.Name() == name || sec.JSONName() == name { 11 | return &sec 12 | } 13 | } 14 | return nil 15 | } 16 | 17 | type SectionDefinition struct { 18 | Path []string 19 | Subsections []SectionDefinition 20 | Variables []VariableDefinition 21 | } 22 | 23 | func (s SectionDefinition) Name() string { 24 | if len(s.Path) == 0 { 25 | return "" 26 | } 27 | return s.Path[len(s.Path)-1] 28 | } 29 | 30 | func (s SectionDefinition) JSONName() string { 31 | return strings.ToLower(s.Name()) 32 | } 33 | 34 | func (s SectionDefinition) TypeName() string { 35 | return "Configuration" + toPascalCase(strings.Join(s.Path, "_")) 36 | } 37 | 38 | func (s SectionDefinition) Typedef() string { 39 | out := fmt.Sprintf("type %s struct {\n", s.TypeName()) 40 | for _, def := range s.Variables { 41 | out += fmt.Sprintf("\t// %s\n", def.Description) 42 | out += fmt.Sprintf("\t%s %s `json:\"%s\"`\n", def.PascalCaseName(), def.GoType(), def.Name) 43 | out += "\n" 44 | } 45 | for _, sec := range s.Subsections { 46 | out += fmt.Sprintf("\t%s %s `json:\"%s\"`\n", sec.Name(), sec.TypeName(), strings.ToLower(sec.Name())) 47 | } 48 | out += "}\n" 49 | out += "\n" 50 | 51 | for _, sec := range s.Subsections { 52 | out += sec.Typedef() 53 | } 54 | return out 55 | } 56 | -------------------------------------------------------------------------------- /parser/data/sources/Animations.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 9 3 | title: Animations 4 | --- 5 | 6 | ## General 7 | 8 | Animations are declared with the `animation` keyword. 9 | 10 | ```ini 11 | animation = NAME, ONOFF, SPEED, CURVE [,STYLE] 12 | ``` 13 | 14 | `ONOFF` can be either 0 or 1, 0 to disable, 1 to enable. _note:_ if it's 0, you 15 | can omit further args. 16 | 17 | `SPEED` is the amount of ds (1ds = 100ms) the animation will take 18 | 19 | `CURVE` is the bezier curve name, see [curves](#curves). 20 | 21 | `STYLE` (optional) is the animation style 22 | 23 | The animations are a tree. If an animation is unset, it will inherit its 24 | parent's values. See [the animation tree](#animation-tree). 25 | 26 | ### Examples 27 | 28 | ```ini 29 | animation = workspaces, 1, 8, default 30 | animation = windows, 1, 10, myepiccurve, slide 31 | animation = fade, 0 32 | ``` 33 | 34 | ### Animation tree 35 | 36 | ```txt 37 | global 38 | ↳ windows - styles: slide, popin, gnomed 39 | ↳ windowsIn - window open - styles: same as windows 40 | ↳ windowsOut - window close - styles: same as windows 41 | ↳ windowsMove - everything in between, moving, dragging, resizing. 42 | ↳ layers - styles: slide, popin, fade 43 | ↳ layersIn - layer open 44 | ↳ layersOut - layer close 45 | ↳ fade 46 | ↳ fadeIn - fade in for window open 47 | ↳ fadeOut - fade out for window close 48 | ↳ fadeSwitch - fade on changing activewindow and its opacity 49 | ↳ fadeShadow - fade on changing activewindow for shadows 50 | ↳ fadeDim - the easing of the dimming of inactive windows 51 | ↳ fadeLayers - for controlling fade on layers 52 | ↳ fadeLayersIn - fade in for layer open 53 | ↳ fadeLayersOut - fade out for layer close 54 | ↳ border - for animating the border's color switch speed 55 | ↳ borderangle - for animating the border's gradient angle - styles: once (default), loop 56 | ↳ workspaces - styles: slide, slidevert, fade, slidefade, slidefadevert 57 | ↳ workspacesIn - styles: same as workspaces 58 | ↳ workspacesOut - styles: same as workspaces 59 | ↳ specialWorkspace - styles: same as workspaces 60 | ↳ specialWorkspaceIn - styles: same as workspaces 61 | ↳ specialWorkspaceOut - styles: same as workspaces 62 | ``` 63 | 64 | {{< callout type=warning >}} 65 | 66 | Using borderangle style: loop requires Hyprland to constantly push out new frames at [your monitor's hz] times a second which might put stress on your CPU/GPU and affect battery life. This will be applied even if animation are disabled or borders are not visible. 67 | 68 | {{}} 69 | 70 | ## Curves 71 | 72 | Defining your own Bezier curve can be done with the `bezier` keyword: 73 | 74 | ```ini 75 | bezier = NAME, X0, Y0, X1, Y1 76 | ``` 77 | 78 | where `NAME` is the name, and the rest are two points for the Cubic Bezier. A 79 | good website to design your bezier can be found 80 | [here, on cssportal.com](https://www.cssportal.com/css-cubic-bezier-generator/), 81 | but if you want to instead choose from a list of beziers, you can check out 82 | [easings.net](https://easings.net). 83 | 84 | ### Example 85 | 86 | ```ini 87 | bezier = overshot, 0.05, 0.9, 0.1, 1.1 88 | ``` 89 | 90 | ### Extras 91 | 92 | For animation style `popin` in `windows`, you can specify a minimum percentage 93 | to start from. For example, the following will make the animation 80% -> 100% of 94 | the size: 95 | 96 | ```ini 97 | animation = windows, 1, 8, default, popin 80% 98 | ``` 99 | 100 | For animation styles `slidefade` and `slidefadevert` in `workspaces`, you can 101 | specify a movement percentage. For example, the following will make windows move 102 | 20% of the screen width: 103 | 104 | ```ini 105 | animation = workspaces, 1, 8, default, slidefade 20% 106 | ``` 107 | 108 | For animation style `slide` in windows and layers you can specify a forced side, 109 | e.g.: 110 | 111 | ```ini 112 | animation = windows, 1, 8, default, slide left 113 | ``` 114 | 115 | You can use `top`, `bottom`, `left` or `right`. 116 | -------------------------------------------------------------------------------- /parser/data/sources/Binds.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 5 3 | title: Binds 4 | --- 5 | 6 | ## Basic 7 | 8 | ```ini 9 | bind = MODS, key, dispatcher, params 10 | ``` 11 | 12 | for example, 13 | 14 | ```ini 15 | bind = SUPER_SHIFT, Q, exec, firefox 16 | ``` 17 | 18 | will bind opening Firefox to SUPER + SHIFT + Q 19 | 20 | {{< callout type=info >}} 21 | 22 | For binding keys without a modkey, leave it empty: 23 | 24 | ```ini 25 | bind = , Print, exec, grim 26 | ``` 27 | 28 | {{< /callout >}} 29 | 30 | _For a complete mod list, see [Variables](../Variables/#variable-types)._ 31 | 32 | _The dispatcher list can be found in 33 | [Dispatchers](../Dispatchers/#list-of-dispatchers)._ 34 | 35 | ## Uncommon syms / binding with a keycode 36 | 37 | See the 38 | [xkbcommon-keysyms.h header](https://github.com/xkbcommon/libxkbcommon/blob/master/include/xkbcommon/xkbcommon-keysyms.h) 39 | for all the keysyms. The name you should use is the segment after `XKB_KEY_`. 40 | 41 | If you are unsure of what your key's name is, you can use `xev` or `wev` to find 42 | that information. 43 | 44 | If you want to bind by a keycode, you can put it in the KEY position with 45 | a `code:` prefix, e.g.: 46 | 47 | ```ini 48 | bind = SUPER, code:28, exec, amongus 49 | ``` 50 | 51 | Will bind SUPER + T. (T is keycode 28.) - You 52 | can also use `xev` or `wev` to find keycodes. 53 | 54 | ## Misc 55 | 56 | ### Workspace bindings on non-qwerty layouts 57 | 58 | Keys used for keybinds need to be accessible without any modifiers in your layout. For instance, the `French Azerty` layout uses `SHIFT+unmodified_key` to write `0-9` numbers. As such, the workspace keybinds for this layout need to use the names of the `unmodified_key`s, and will not work when using the `0-9` numbers. 59 | 60 | {{< callout type=info >}} 61 | 62 | To get the correct name for an `unmodified_key`, refer to [the section on uncommon syms](#uncommon-syms--binding-with-a-keycode) 63 | 64 | {{< /callout >}} 65 | 66 | ```ini 67 | # On a french layout, instead of 68 | # bind = $mainMod, 1, workspace, 1 69 | 70 | # Use 71 | bind = $mainMod, ampersand, workspace, 1 72 | ``` 73 | 74 | For help configuring the `French Azerty` layout, [see](https://rherault.dev/articles/hyprland-fr-layout). 75 | 76 | ### Unbind 77 | 78 | You can also unbind with `unbind`, e.g.: 79 | 80 | ```ini 81 | unbind = SUPER, O 82 | ``` 83 | 84 | May be useful for dynamic keybindings with `hyprctl`: 85 | 86 | ```bash 87 | hyprctl keyword unbind SUPER, O 88 | ``` 89 | 90 | ### Mouse buttons 91 | 92 | You can also bind mouse buttons, by prefacing the mouse keycode with `mouse:`, 93 | for example: 94 | 95 | ```ini 96 | bind = SUPER, mouse:272, exec, amongus 97 | ``` 98 | 99 | will bind it to SUPER + LMB. 100 | 101 | ### Only modkeys 102 | 103 | For binding only modkeys, you need to use the TARGET modmask (with the 104 | activating mod) and the `r` flag, e.g.: 105 | 106 | ```ini 107 | bindr = SUPER ALT, Alt_L, exec, amongus 108 | ``` 109 | 110 | Will bind `exec amongus` to SUPER + ALT 111 | 112 | ### Keysym combos 113 | 114 | For an arbitrary combination of multiple keys, separate keysyms with `&` between 115 | each mod/key and use the `s` flag, e.g.: 116 | 117 | ```ini 118 | # You can use a single mod with multiple keys. 119 | binds = Control_L, A&Z, exec, kitty 120 | # You can also specify multiple specific mods. 121 | binds = Control_L&Shift_L, K, exec, kitty 122 | # You can also do both! 123 | binds = Control_R&Super_R&Alt_L, J&K&L, exec, kitty 124 | # If you are feeling a little wild... you can use other keys for binds... 125 | binds = Escape&Apostrophe&F7, T&O&A&D, exec, battletoads 2: retoaded 126 | ``` 127 | 128 | (Please note this is only valid for keysyms and it makes all mods keysyms. If 129 | you don't know what a keysym is use `xev` and press the key you want to use.) 130 | 131 | ### Mouse wheel 132 | 133 | You can also bind the mouse wheel with `mouse_up` and `mouse_down` (or 134 | `mouse_left` and `mouse_right` if your wheel supports horizontal scrolling): 135 | 136 | ```ini 137 | bind = SUPER, mouse_down, workspace, e-1 138 | ``` 139 | 140 | (control the reset time with `binds:scroll_event_delay`) 141 | 142 | ### Switches 143 | 144 | Useful for binding e.g. the lid close/open event: 145 | 146 | ```ini 147 | # trigger when the switch is toggled 148 | bindl = , switch:[switch name], exec, swaylock 149 | # trigger when the switch is turning on 150 | bindl = , switch:on:[switch name], exec, hyprctl keyword monitor "eDP-1, disable" 151 | # trigger when the switch is turning off 152 | bindl = , switch:off:[switch name], exec, hyprctl keyword monitor "eDP-1, 2560x1600, 0x0, 1" 153 | ``` 154 | 155 | You can view your switches in `hyprctl devices`. 156 | 157 | ### Multiple binds to one key 158 | 159 | You can trigger multiple actions with one keybind by assigning multiple binds to 160 | one combination, e.g.: 161 | 162 | ```ini 163 | # to switch between windows in a floating workspace 164 | bind = SUPER, Tab, cyclenext, # change focus to another window 165 | bind = SUPER, Tab, bringactivetotop, # bring it to the top 166 | ``` 167 | 168 | The keybinds will be executed in the order they were created. (top to bottom) 169 | 170 | ### Description 171 | 172 | You can describe your keybind with the description flag. 173 | Your description always goes in front of the dispatcher and should never contain the character `,`! 174 | 175 | ```ini 176 | bindd = MODS, key, description, dispatcher, params 177 | ``` 178 | 179 | for example, 180 | 181 | ```ini 182 | bindd = SUPER, Q, Open my favourite terminal, exec, kitty 183 | ``` 184 | 185 | If you want to access your description you can use `hyprctl binds`. For more information have a look at [Using Hyprctl](../Using-hyprctl). 186 | 187 | ## Bind flags 188 | 189 | `bind` supports flags in this format: 190 | 191 | ```ini 192 | bind[flags] = ... 193 | ``` 194 | 195 | e.g.: 196 | 197 | ```ini 198 | bindrl = MOD, KEY, exec, amongus 199 | ``` 200 | 201 | Flags: 202 | 203 | ```plain 204 | l -> locked, will also work when an input inhibitor (e.g. a lockscreen) is active. 205 | r -> release, will trigger on release of a key. 206 | c -> click, will trigger on release of a key or button as long as the mouse cursor stays inside binds:drag_threshold. 207 | g -> drag, will trigger on release of a key or button as long as the mouse cursor moves outside binds:drag_threshold. 208 | o -> longPress, will trigger on long press of a key. 209 | e -> repeat, will repeat when held. 210 | n -> non-consuming, key/mouse events will be passed to the active window in addition to triggering the dispatcher. 211 | m -> mouse, see below. 212 | t -> transparent, cannot be shadowed by other binds. 213 | i -> ignore mods, will ignore modifiers. 214 | s -> separate, will arbitrarily combine keys between each mod/key, see [Keysym combos](#keysym-combos) above. 215 | d -> has description, will allow you to write a description for your bind. 216 | p -> bypasses the app's requests to inhibit keybinds. 217 | ``` 218 | 219 | Example Usage: 220 | 221 | ```ini 222 | # Example volume button that allows press and hold, volume limited to 150% 223 | binde = , XF86AudioRaiseVolume, exec, wpctl set-volume -l 1.5 @DEFAULT_AUDIO_SINK@ 5%+ 224 | 225 | # Example volume button that will activate even while an input inhibitor is active 226 | bindl = , XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- 227 | 228 | # Start wofi opens wofi on first press, closes it on second 229 | bindr = SUPER, SUPER_L, exec, pkill wofi || wofi 230 | 231 | # Describe a bind 232 | bindd = SUPER, Q, Open my favourite terminal, exec, kitty 233 | 234 | # Skip player on long press and only skip 5s on normal press 235 | bindo = SUPER, XF86AudioNext, exec, playerctl next 236 | bind = SUPER, XF86AudioNext, exec, playerctl position +5 237 | ``` 238 | 239 | ## Mouse Binds 240 | 241 | Mouse binds are binds that rely on mouse movement. They will have one less arg. 242 | `binds:drag_threshold` can be used to differentiate between clicks and drags with the same button: 243 | 244 | ```ini 245 | binds { 246 | drag_threshold = 10 247 | } 248 | bindm = ALT, mouse:272, movewindow 249 | bindc = ALT, mouse:272, togglefloating 250 | ``` 251 | 252 | This will create a bind with ALT + LMB to move the window 253 | by dragging more than 10px or float it by clicking. 254 | 255 | _Available mouse binds_: 256 | 257 | | Name | Description | Params | 258 | | --- | --- | --- | 259 | | movewindow | moves the active window | none | 260 | | resizewindow | resizes the active window | 1 - resize and keep window aspect ratio, 2 - resize and ignore `keepaspectratio` window rule/prop, none or anything else for normal resize | 261 | 262 | _Common mouse buttons' codes:_ 263 | 264 | ```txt 265 | LMB -> 272 266 | RMB -> 273 267 | ``` 268 | 269 | _for more, you can of course use `wev` to check._ 270 | 271 | {{< callout type=info >}} 272 | 273 | Mouse binds, despite their name, behave like normal binds. You are free to use 274 | whatever keys / mods you please. When held, the mouse function will be 275 | activated. 276 | 277 | {{< /callout >}} 278 | 279 | ### Touchpad 280 | 281 | As clicking and moving the mouse on a touchpad is unergonomic, you can also use keyboard keys instead of mouse clicks too. 282 | 283 | ```ini 284 | bindm = SUPER, mouse:272, movewindow 285 | bindm = SUPER, Control_L, movewindow 286 | bindm = SUPER, mouse:273, resizewindow 287 | bindm = SUPER, ALT_L, resizewindow 288 | ``` 289 | 290 | ## Binding mods 291 | 292 | You can bind a mod alone like this: 293 | 294 | ```ini 295 | bindr = ALT,Alt_L,exec,amongus 296 | ``` 297 | 298 | ## Global Keybinds 299 | 300 | ### Classic 301 | 302 | Yes, you heard this right, Hyprland does support global keybinds for ALL apps, 303 | including OBS, Discord, Firefox, etc. 304 | 305 | See the [`pass` dispatcher](../Dispatchers/#list-of-dispatchers) and the 306 | [`sendshortcut` dispatcher](../Dispatchers/#list-of-dispatchers) for keybinds. 307 | 308 | Let's take OBS as an example: the "Start/Stop Recording" keybind is set to 309 | SUPER + F10, and you want to make it work globally. Simply 310 | add 311 | 312 | ```ini 313 | bind = SUPER, F10, pass, class:^(com\.obsproject\.Studio)$ 314 | ``` 315 | 316 | to your config and you're done. 317 | 318 | `pass` will pass the PRESS and RELEASE events by itself, no need for a `bindr`. 319 | This also means that push-to-talk will work flawlessly with one pass, e.g.: 320 | 321 | ```ini 322 | bind = , mouse:276, pass, class:^(TeamSpeak 3)$ 323 | ``` 324 | 325 | Will pass MOUSE5 to TeamSpeak3. 326 | 327 | You may also add shortcuts, where other keys are passed to the window. 328 | 329 | ```ini 330 | bind = SUPER, F10, sendshortcut, SUPER, F4, class:^(com\.obsproject\.Studio)$ 331 | ``` 332 | 333 | Will send SUPER + F4 to OBS if you press 334 | SUPER + F10. 335 | 336 | {{< callout >}} 337 | 338 | This works flawlessly with all native Wayland applications. However, XWayland 339 | is a bit wonky. Make sure that what you're passing is a "global Xorg keybind", 340 | otherwise passing from a different XWayland app may not work. 341 | 342 | {{< /callout >}} 343 | 344 | ### DBus Global Shortcuts 345 | 346 | Some applications may already support the GlobalShortcuts portal in 347 | xdg-desktop-portal. 348 | 349 | If that's the case, then it's recommended to use this method instead of `pass`. 350 | 351 | Open your desired app and run `hyprctl globalshortcuts` in a terminal. This will 352 | give you a list of currently registered shortcuts with their description(s). 353 | 354 | Choose whichever you like, for example `coolApp:myToggle`, and bind it to 355 | whatever you want with the `global` dispatcher: 356 | 357 | ```ini 358 | bind = SUPERSHIFT, A, global, coolApp:myToggle 359 | ``` 360 | 361 | {{< callout type=info >}} 362 | 363 | Please note that this function will _only_ work with 364 | [XDPH](../../Hypr-Ecosystem/xdg-desktop-portal-hyprland). 365 | 366 | {{}} 367 | 368 | ## Submaps 369 | 370 | Keybind submaps, also known as _modes_ or _groups_, allow you to activate a 371 | separate set of keybinds. For example, if you want to enter a "resize" mode 372 | which allows you to resize windows with the arrow keys, you can do it like this: 373 | 374 | ```ini 375 | # will switch to a submap called resize 376 | bind = ALT, R, submap, resize 377 | 378 | # will start a submap called "resize" 379 | submap = resize 380 | 381 | # sets repeatable binds for resizing the active window 382 | binde = , right, resizeactive, 10 0 383 | binde = , left, resizeactive, -10 0 384 | binde = , up, resizeactive, 0 -10 385 | binde = , down, resizeactive, 0 10 386 | 387 | # use reset to go back to the global submap 388 | bind = , escape, submap, reset 389 | 390 | # will reset the submap, which will return to the global submap 391 | submap = reset 392 | 393 | # keybinds further down will be global again... 394 | ``` 395 | 396 | {{< callout type=warning >}} 397 | 398 | Do not forget a keybind to reset the keymap while inside it! (In this case, 399 | `escape`) 400 | 401 | {{< /callout >}} 402 | 403 | If you get stuck inside a keymap, you can use `hyprctl dispatch submap reset` to 404 | go back. If you do not have a terminal open, tough luck buddy. You have been 405 | warned. 406 | 407 | You can also set the same keybind to perform multiple actions, such as resize 408 | and close the submap, like so: 409 | 410 | ```ini 411 | bind = ALT, R, submap, resize 412 | 413 | submap = resize 414 | 415 | bind = , right, resizeactive, 10 0 416 | bind = , right, submap, reset 417 | # ... 418 | 419 | submap = reset 420 | ``` 421 | 422 | This works because the binds are executed in the order they appear, and 423 | assigning multiple actions per bind is possible. 424 | 425 | ### Nesting 426 | 427 | Submaps can be nested, see the following example: 428 | 429 | ```ini 430 | bind = $mainMod, M, submap, main_submap 431 | submap = main_submap 432 | 433 | # ... 434 | 435 | # nested_one 436 | bind = , 1, submap, nested_one 437 | submap = nested_one 438 | 439 | # ... 440 | 441 | bind = SHIFT, escape, submap, reset 442 | bind = , escape, submap, main_submap 443 | submap = main_submap 444 | # /nested_one 445 | 446 | # nested_two 447 | bind = , 2, submap, nested_two 448 | submap = nested_two 449 | 450 | # ... 451 | 452 | bind = SHIFT, escape, submap, reset 453 | bind = , escape, submap, main_submap 454 | submap = main_submap 455 | # /nested_two 456 | 457 | bind = , escape, submap, reset 458 | submap = reset 459 | ``` 460 | 461 | ### Catch-All 462 | 463 | You can also define a keybind via the special `catchall` keyword, which 464 | activates no matter which key is pressed. This can be used to prevent any keys 465 | from passing to your active application while in a submap or to exit it 466 | immediately when any unknown key is pressed: 467 | 468 | ```ini 469 | bind = , catchall, submap, reset 470 | ``` 471 | 472 | ## Example Binds 473 | 474 | ### Media 475 | 476 | These binds set the expected behavior for regular keyboard media volume keys, 477 | including when the screen is locked: 478 | 479 | ```ini 480 | bindel = , XF86AudioRaiseVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+ 481 | bindel = , XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- 482 | bindl = , XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle 483 | # Requires playerctl 484 | bindl = , XF86AudioPlay, exec, playerctl play-pause 485 | bindl = , XF86AudioPrev, exec, playerctl previous 486 | bindl = , XF86AudioNext, exec, playerctl next 487 | ``` 488 | -------------------------------------------------------------------------------- /parser/data/sources/Configuring-Hyprland.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 1 3 | title: Configuring Hyprland 4 | --- 5 | 6 | The config is located in `~/.config/hypr/hyprland.conf`. 7 | 8 | You can tell Hyprland to use a specific configuration file by using the 9 | `--config` (or `-c`) argument. 10 | 11 | Hyprland will automatically generate an example config for you if you don't have 12 | one. You can find an example config 13 | [here](https://github.com/hyprwm/Hyprland/blob/main/example/hyprland.conf). 14 | 15 | By removing the line `autogenerated=1` you'll remove the yellow warning. 16 | 17 | The config is reloaded the moment you save it. However, you can use `hyprctl reload` to reload the config manually. 18 | 19 | Start a section with `name {` and end in `}` **_in separate lines!_** 20 | 21 | {{< callout >}} 22 | 23 | The default config is not complete and does not list all the options / features 24 | of Hyprland. Please refer to this wiki page and the pages linked further down 25 | here for full configuration instructions. 26 | 27 | **Make sure to read the [Variables](../Variables) page as well**. It covers all 28 | the toggleable / numerical options. 29 | 30 | {{< /callout >}} 31 | 32 | ## Line style 33 | 34 | Every config line is a command followed by a value. 35 | 36 | ```ini 37 | COMMAND=VALUE 38 | ``` 39 | 40 | The command can be a variable, or a special keyword (described further in this 41 | page) 42 | 43 | You are **allowed to** input trailing spaces at the beginning and end. 44 | 45 | e.g.: 46 | 47 | ```ini 48 | COMMAND = VALUE 49 | ``` 50 | 51 | is valid. 52 | 53 | ### Comments 54 | 55 | Comments are started with the `#` character. 56 | 57 | If you want to escape it (put an actual `#` and not start a comment) you can use 58 | `##`. It will be turned into a single `#` that WILL be a part of your line. 59 | 60 | ### Escaping errors 61 | 62 | If you use plugins, you may want to ignore errors from missing options/keywords 63 | so that you don't get an error bar before they are loaded. To do so, do this: 64 | 65 | ```ini 66 | # hyprlang noerror true 67 | 68 | bind = MOD, KEY, something, amogus 69 | someoption = blah 70 | 71 | # hyprlang noerror false 72 | ``` 73 | 74 | ## Basic configuring 75 | 76 | To configure the "options" of Hyprland, animations, styling, etc. see 77 | [Variables](../Variables). 78 | 79 | ## Advanced configuring 80 | 81 | Some keywords (binds, curves, execs, monitors, etc.) are not variables but 82 | define special behavior. 83 | 84 | See all of them in [Keywords](../Keywords) and the sidebar. 85 | -------------------------------------------------------------------------------- /parser/data/sources/Dwindle-Layout.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 11 3 | title: Dwindle Layout 4 | --- 5 | 6 | Dwindle is a BSPWM-like layout, where every window on a workspace is a member of 7 | a binary tree. 8 | 9 | ## Quirks 10 | 11 | Dwindle splits are NOT PERMANENT. The split is determined dynamically with the 12 | W/H ratio of the parent node. If W > H, it's side-by-side. If H > W, it's 13 | top-and-bottom. You can make them permanent by enabling `preserve_split`. 14 | 15 | ## Config 16 | 17 | category name: `dwindle` 18 | 19 | | name | description | type | default | 20 | | --- | --- | --- | --- | 21 | | pseudotile | enable pseudotiling. Pseudotiled windows retain their floating size when tiled. | bool | false | 22 | | force_split | 0 -> split follows mouse, 1 -> always split to the left (new = left or top) 2 -> always split to the right (new = right or bottom) | int | 0 | 23 | | preserve_split | if enabled, the split (side/top) will not change regardless of what happens to the container. | bool | false | 24 | | smart_split | if enabled, allows a more precise control over the window split direction based on the cursor's position. The window is conceptually divided into four triangles, and cursor's triangle determines the split direction. This feature also turns on preserve_split. | bool | false | 25 | | smart_resizing | if enabled, resizing direction will be determined by the mouse's position on the window (nearest to which corner). Else, it is based on the window's tiling position. | bool | true | 26 | | permanent_direction_override | if enabled, makes the preselect direction persist until either this mode is turned off, another direction is specified, or a non-direction is specified (anything other than l,r,u/t,d/b) | bool | false | 27 | | special_scale_factor | specifies the scale factor of windows on the special workspace [0 - 1] | float | 1 | 28 | | split_width_multiplier | specifies the auto-split width multiplier | float | 1.0 | 29 | | use_active_for_splits | whether to prefer the active window or the mouse position for splits | bool | true | 30 | | default_split_ratio | the default split ratio on window open. 1 means even 50/50 split. [0.1 - 1.9] | float | 1.0 | 31 | | split_bias | specifies which window will receive the larger half of a split. positional - 0, current window - 1, opening window - 2 [0/1/2] | int | 0 | 32 | 33 | ## Bind Dispatchers 34 | 35 | | dispatcher | description | params | 36 | | --- | --- | --- | 37 | | pseudo | toggles the given window's pseudo mode | left empty / `active` for current, or `window` for a specific window | 38 | 39 | ## Layout messages 40 | 41 | Dispatcher `layoutmsg` params: 42 | 43 | | param | description | args | 44 | | --- | --- | --- | 45 | | togglesplit | toggles the split (top/side) of the current window. `preserve_split` must be enabled for toggling to work. | none | 46 | | swapsplit | swaps the two halves of the split of the current window. | none | 47 | | preselect | A one-time override for the split direction. (valid for the next window to be opened, only works on tiled windows) | direction | 48 | | movetoroot | moves the selected window (active window if unspecified) to the root of its workspace tree. The default behavior maximizes the window in its current subtree. If `unstable` is provided as the second argument, the window will be swapped with the other subtree instead. It is not possible to only provide the second argument, but `movetoroot active unstable` will achieve the same result. | [window, [ string ]] | 49 | 50 | e.g.: 51 | 52 | ```ini 53 | bind = SUPER, A, layoutmsg, preselect l 54 | ``` 55 | -------------------------------------------------------------------------------- /parser/data/sources/Environment-variables.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 16 3 | title: Environment variables 4 | --- 5 | 6 | {{< callout type=info >}} 7 | 8 | [uwsm](../../Useful-Utilities/Systemd-start) users should avoid placing environment variables in the `hyprland.conf` file. Instead, use `~/.config/uwsm/env` for theming, xcursor, nvidia and toolkit variables, and `~/.config/uwsm/env-hyprland` for `HYPR*` and `AQ_*` variables. The format is `export KEY=VAL`. 9 | 10 | ```plain 11 | export XCURSOR_SIZE=24 12 | ``` 13 | 14 | See [uwsm readme](https://github.com/Vladimir-csp/uwsm?tab=readme-ov-file#4-environments-and-shell-profile) for additional information. 15 | 16 | {{< /callout >}} 17 | 18 | You can use the `env` keyword to set environment variables prior to the 19 | initialization of the Display Server, e.g.: 20 | 21 | ```ini 22 | env = GTK_THEME,Nord 23 | ``` 24 | 25 | {{< callout >}} 26 | 27 | Hyprland puts the raw string to the envvar with the `env` keyword. You should 28 | _not_ add quotes around the values. 29 | 30 | e.g.: 31 | 32 | ```ini 33 | env = QT_QPA_PLATFORM,wayland 34 | ``` 35 | 36 | and _**NOT**_ 37 | 38 | ```ini 39 | env = QT_QPA_PLATFORM,"wayland" 40 | ``` 41 | 42 | {{< /callout >}} 43 | 44 | Please avoid putting those environment variables in /etc/environment. That will 45 | cause all sessions (including Xorg ones) to pick up your wayland-specific 46 | environment on traditional Linux distros. 47 | 48 | ## Hyprland Environment Variables 49 | 50 | - `HYPRLAND_TRACE=1` - Enables more verbose logging. 51 | - `HYPRLAND_NO_RT=1` - Disables realtime priority setting by Hyprland. 52 | - `HYPRLAND_NO_SD_NOTIFY=1` - If systemd, disables the `sd_notify` calls. 53 | - `HYPRLAND_NO_SD_VARS=1` - Disables management of variables in systemd and dbus activation environments. 54 | - `HYPRLAND_CONFIG` - Specifies where you want your Hyprland configuration. 55 | 56 | ## Aquamarine Environment Variables 57 | 58 | - `AQ_TRACE=1` - Enables more verbose logging. 59 | - `AQ_DRM_DEVICES=` - Set an explicit list of DRM devices (GPUs) to use. It's a colon-separated list of paths, with the first being the primary. 60 | E.g. `/dev/dri/card1:/dev/dri/card0` 61 | - `AQ_MGPU_NO_EXPLICIT=1` - Disables explicit syncing on mgpu buffers 62 | - `AQ_NO_MODIFIERS=1` - Disables modifiers for DRM buffers 63 | 64 | ## Toolkit Backend Variables 65 | 66 | - `env = GDK_BACKEND,wayland,x11,*` - GTK: Use wayland if available. If not: try x11, then any other GDK backend. 67 | - `env = QT_QPA_PLATFORM,wayland;xcb` - Qt: Use wayland if available, fall back to 68 | x11 if not. 69 | - `env = SDL_VIDEODRIVER,wayland` - Run SDL2 applications on Wayland. Remove or set to 70 | `x11` if games that provide older versions of SDL cause compatibility issues 71 | - `env = CLUTTER_BACKEND,wayland` - Clutter package already has wayland enabled, this 72 | variable will force Clutter applications to try and use the Wayland backend 73 | 74 | ## XDG Specifications 75 | 76 | - `env = XDG_CURRENT_DESKTOP,Hyprland` 77 | - `env = XDG_SESSION_TYPE,wayland` 78 | - `env = XDG_SESSION_DESKTOP,Hyprland` 79 | 80 | XDG specific environment variables are often detected through portals and 81 | applications that may set those for you, however it is not a bad idea to set 82 | them explicitly. 83 | 84 | If your [desktop portal](https://wiki.archlinux.org/title/XDG_Desktop_Portal) is malfunctioning for seemingly 85 | no reason (no errors), it's likely your XDG env isn't set correctly. 86 | 87 | {{< callout type=info >}} 88 | 89 | [uwsm](../../Useful-Utilities/Systemd-start) users don't need to explicitly set XDG environment variables, as uwsm sets them, automatically. 90 | 91 | {{< /callout >}} 92 | 93 | ## Qt Variables 94 | 95 | - `env = QT_AUTO_SCREEN_SCALE_FACTOR,1` - 96 | [(From the Qt documentation)](https://doc.qt.io/qt-5/highdpi.html) enables 97 | automatic scaling, based on the monitor's pixel density 98 | - `env = QT_QPA_PLATFORM,wayland;xcb` - Tell Qt applications to use the Wayland 99 | backend, and fall back to x11 if Wayland is unavailable 100 | - `env = QT_WAYLAND_DISABLE_WINDOWDECORATION,1` - Disables window decorations on Qt 101 | applications 102 | - `env = QT_QPA_PLATFORMTHEME,qt5ct` - Tells Qt based applications to pick your theme 103 | from qt5ct, use with Kvantum. 104 | 105 | ## NVIDIA Specific 106 | 107 | To force GBM as a backend, set the following environment variables: 108 | 109 | - `env = GBM_BACKEND,nvidia-drm` 110 | - `env = __GLX_VENDOR_LIBRARY_NAME,nvidia` 111 | 112 | > See 113 | > [Archwiki Wayland Page](https://wiki.archlinux.org/title/Wayland#Requirements) 114 | > for more details on those variables. 115 | 116 | - `env = LIBVA_DRIVER_NAME,nvidia` - Hardware acceleration on NVIDIA GPUs 117 | 118 | > See 119 | > [Archwiki Hardware Acceleration Page](https://wiki.archlinux.org/title/Hardware_video_acceleration) 120 | > for details and necessary values before setting this variable. 121 | 122 | - `__GL_GSYNC_ALLOWED` - Controls if G-Sync capable monitors should use Variable 123 | Refresh Rate (VRR) 124 | 125 | > See 126 | > [Nvidia Documentation](https://download.nvidia.com/XFree86/Linux-32bit-ARM/375.26/README/openglenvvariables.html) 127 | > for details. 128 | 129 | - `__GL_VRR_ALLOWED` - Controls if Adaptive Sync should be used. Recommended to 130 | set as "0" to avoid having problems on some games. 131 | 132 | - `env = AQ_NO_ATOMIC,1` - use legacy DRM interface instead of atomic mode 133 | setting. **NOT** recommended. 134 | 135 | ## Theming Related Variables 136 | 137 | - `GTK_THEME` - Set a GTK theme manually, for those who want to avoid appearance 138 | tools such as lxappearance or nwg-look 139 | - `XCURSOR_THEME` - Set your cursor theme. The theme needs to be installed and 140 | readable by your user. 141 | - `XCURSOR_SIZE` - Set cursor size. See [here](../../FAQ/) for why you might 142 | want this variable set. 143 | -------------------------------------------------------------------------------- /parser/data/sources/Example-configurations.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 19 3 | title: Example configurations 4 | --- 5 | 6 | This page houses links to a few repositories with beautiful Hyprland 7 | configurations for you to get inspired from or learn how to configure Hyprland 8 | from a more tangible example. 9 | 10 | {{< callout >}} 11 | 12 | These configurations are popular and have many people using them. PRs that add 13 | more configurations will not be merged. 14 | 15 | {{< /callout >}} 16 | 17 | ### end_4 18 | 19 | ![end-4/dots-hyprland screenshot](https://github.com/end-4/dots-hyprland/assets/97237370/5e081770-0f1e-45c4-ad9c-3d19f488cd85) 20 | 21 | [https://github.com/end-4/dots-hyprland](https://github.com/end-4/dots-hyprland) 22 | 23 | ### Stephan Raabe (ML4W) 24 | 25 | ![mylinuxforwork/dotfiles screenshot](https://i.ibb.co/6ydHNt9/screenshot-29-1.png) 26 | 27 | [https://github.com/mylinuxforwork/dotfiles](https://github.com/mylinuxforwork/dotfiles) 28 | 29 | ### fufexan 30 | 31 | ![fufexan/dotfiles screenshot](https://user-images.githubusercontent.com/36706276/192147190-cf9cf4df-94cb-4a3b-b9d8-137ed0c2538f.png) 32 | 33 | [https://github.com/fufexan/dotfiles](https://github.com/fufexan/dotfiles) 34 | 35 | ### linuxmobile 36 | 37 | ![linuxmobile/hyprland-dots screenshot](https://i.ibb.co/kGrhpKd/68747470733a2f2f692e696d6775722e636f6d2f553173336a69372e706e67.png) 38 | 39 | [https://github.com/linuxmobile/hyprland-dots](https://github.com/linuxmobile/hyprland-dots) 40 | 41 | ### flick0 42 | 43 | ![flick0/dotfiles screenshot](https://raw.githubusercontent.com/flick0/dotfiles/aurora/assets/fetch.png) 44 | 45 | [https://github.com/flick0/dotfiles](https://github.com/flick0/dotfiles) 46 | 47 | ### iamverysimp1e 48 | 49 | ![iamverysimp1e/dots screenshot](https://github.com/iamverysimp1e/dots/raw/main/ScreenShots/HyprLand/Rice1.png) 50 | 51 | [https://github.com/iamverysimp1e/dots](https://github.com/iamverysimp1e/dots) 52 | 53 | ### notusknot 54 | 55 | ![notusknot/dotfiles-nix screenshot](https://github.com/notusknot/dotfiles-nix/raw/main/pics/screenshot.png) 56 | 57 | [https://github.com/notusknot/dotfiles-nix](https://github.com/notusknot/dotfiles-nix) 58 | 59 | ### coffebar 60 | 61 | ![coffebar/dotfiles screenshot](https://github.com/coffebar/dotfiles/raw/6a5d595c594f108cd10219df08d338e98e1d2d7d/screenshot.png) 62 | 63 | [https://github.com/coffebar/dotfiles](https://github.com/coffebar/dotfiles) 64 | -------------------------------------------------------------------------------- /parser/data/sources/Expanding-functionality.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 14 3 | title: Expanding functionality 4 | --- 5 | 6 | Hyprland exposes two powerful sockets for you to use. 7 | 8 | The first, socket1, can be fully controlled with `hyprctl`, see its usage 9 | [here](../Using-hyprctl). 10 | 11 | The second, socket2, sends events for certain changes / actions and can be used 12 | to react to different events. See its description [here](../../IPC/). 13 | 14 | ## Example script 15 | 16 | This bash script will change the outer gaps to 20 if the currently focused 17 | monitor is DP-1, and 30 otherwise. 18 | 19 | ```bash 20 | #!/usr/bin/env bash 21 | 22 | function handle { 23 | if [[ ${1:0:10} == "focusedmon" ]]; then 24 | if [[ ${1:12:4} == "DP-1" ]]; then 25 | hyprctl keyword general:gaps_out 20 26 | else 27 | hyprctl keyword general:gaps_out 30 28 | fi 29 | fi 30 | } 31 | 32 | socat - "UNIX-CONNECT:$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock" | while read -r line; do handle "$line"; done 33 | ``` 34 | -------------------------------------------------------------------------------- /parser/data/sources/Keywords.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 3 3 | title: Keywords 4 | --- 5 | 6 | Keywords are not variables, but "commands" for more advanced configuring. On 7 | this page, you will be presented with some that do not deserve their own page. 8 | 9 | See the sidebar for more keywords to control binds, animations, monitors, et 10 | cetera. 11 | 12 | {{< callout >}} 13 | 14 | Please remember, that for ALL arguments separated by a comma, if you want to 15 | leave one of them empty, you cannot reduce the number of commas, _unless told 16 | otherwise in a specific section_: 17 | 18 | ```ini 19 | three_param_keyword = A, B, C # OK 20 | three_param_keyword = A, C # NOT OK 21 | three_param_keyword = A, , C # OK 22 | three_param_keyword = A, B, # OK 23 | ``` 24 | 25 | {{< /callout >}} 26 | 27 | ## Executing 28 | 29 | You can execute a shell script on: 30 | 31 | - startup of the compositor 32 | - every time the config is reloaded. 33 | - shutdown of the compositor 34 | 35 | `exec-once = command` will execute only on launch ([support rules](../Dispatchers/#executing-with-rules)) 36 | 37 | `execr-once = command` will execute only on launch 38 | 39 | `exec = command` will execute on each reload ([support rules](../Dispatchers/#executing-with-rules)) 40 | 41 | `execr = command` will execute on each reload 42 | 43 | `exec-shutdown = command` will execute only on shutdown 44 | 45 | ## Sourcing (multi-file) 46 | 47 | Use the `source` keyword to source another file. 48 | 49 | For example, in your `hyprland.conf` you can: 50 | 51 | ```ini 52 | source = ~/.config/hypr/myColors.conf 53 | ``` 54 | 55 | And Hyprland will enter that file and parse it like a Hyprland config. 56 | 57 | Please note it's LINEAR. Meaning lines above the `source =` will be parsed first, 58 | then lines inside `~/.config/hypr/myColors.conf`, then lines below. 59 | 60 | ## Gestures 61 | 62 | Use [libinput-gestures](https://github.com/bulletmark/libinput-gestures) with 63 | `hyprctl` if you want to expand Hyprland's gestures beyond what's offered in 64 | [Variables](../Variables). 65 | 66 | ## Per-device input configs 67 | 68 | Per-device config options will overwrite your options set in the `input` 69 | section. It's worth noting that ONLY values explicitly changed will be 70 | overwritten. 71 | 72 | In order to apply per-device config options, add a new category like this: 73 | 74 | ```ini 75 | device { 76 | name = ... 77 | # options ... 78 | } 79 | ``` 80 | 81 | The `name` can be easily obtained by checking the output of `hyprctl devices`. 82 | 83 | Inside of it, put your config options. All options from the `input` category 84 | (and all subcategories, e.g. `input:touchpad`) can be put inside, **EXCEPT**: 85 | 86 | - `force_no_accel` 87 | - `follow_mouse` 88 | - `float_switch_override_focus` 89 | - `scroll_factor` 90 | 91 | Properties that change names: 92 | 93 | ```plain 94 | touchdevice:transform -> transform 95 | touchdevice:output -> output 96 | ``` 97 | 98 | You can also use the `output` setting for tablets to bind them to outputs. 99 | Remember to use the name of the `Tablet` and not `Tablet Pad` or `Tablet tool`. 100 | 101 | Additional properties only present in per-device configs: 102 | 103 | - `enabled` -> (only for mice / touchpads / touchdevices / keyboards) 104 | - enables / disables the device (connects / disconnects from the on-screen cursor) 105 | - default: Enabled 106 | - `keybinds` -> (only for devices that send key events) 107 | - enables / disables keybinds for the device 108 | - default: Enabled 109 | 110 | Example config section: 111 | 112 | ```ini 113 | device { 114 | name = royuan-akko-multi-modes-keyboard-b 115 | repeat_rate = 50 116 | repeat_delay = 500 117 | middle_button_emulation = 0 118 | } 119 | ``` 120 | 121 | Example modifying per-device config values using `hyprctl`: 122 | 123 | ```bash 124 | hyprctl -r -- keyword device[my-device]:sensitivity -1 125 | ``` 126 | 127 | {{< callout type=info >}} 128 | 129 | Per-device layouts will by default not alter the keybind keymap, so for example 130 | with a global keymap of `us` and a per-device one of `fr`, the keybinds will 131 | still act as if you were on `us`. 132 | 133 | You can change this behavior by setting `resolve_binds_by_sym = 1`. In that case 134 | you'll need to type the symbol specified in the bind to activate it. 135 | 136 | {{< /callout >}} 137 | 138 | ## Wallpapers 139 | 140 | The "Hyprland" background you see when you first start Hyprland is **NOT A 141 | WALLPAPER**, it's the default image rendered at the bottom of the render stack. 142 | 143 | To set a wallpaper, use a wallpaper utility like 144 | [hyprpaper](https://github.com/hyprwm/hyprpaper) or 145 | [swaybg](https://github.com/swaywm/swaybg). 146 | 147 | More can be found in [Useful Utilities](../../Useful-Utilities). 148 | 149 | ## Blurring layerSurfaces 150 | 151 | Layer surfaces are not windows. These are, for example: wallpapers, 152 | notification overlays, bars, etc. 153 | 154 | If you want to blur them, use a layer rule: 155 | 156 | ```ini 157 | layerrule = blur, NAMESPACE 158 | # or 159 | layerrule = blur, address:0x
160 | ``` 161 | 162 | You can get the namespace / address from `hyprctl layers`. 163 | 164 | To remove a layer rule (useful in dynamic situations) use: 165 | 166 | ```ini 167 | layerrule = unset, 168 | ``` 169 | 170 | For example: 171 | 172 | ```ini 173 | layerrule = unset, NAMESPACE 174 | ``` 175 | 176 | ## Setting the environment 177 | 178 | {{< callout type=info >}} 179 | 180 | The `env` keyword works just like `exec-once`, meaning it will only fire once on 181 | Hyprland's launch. 182 | 183 | {{< /callout >}} 184 | 185 | You can use the `env` keyword to set environment variables when Hyprland starts, 186 | e.g: 187 | 188 | ```ini 189 | env = XCURSOR_SIZE,24 190 | ``` 191 | 192 | You can also add a `d` flag if you want the env var to be exported to D-Bus 193 | (systemd only): 194 | 195 | ```ini 196 | envd = XCURSOR_SIZE,24 197 | ``` 198 | 199 | {{< callout >}} 200 | 201 | Hyprland puts the raw string to the env var. You should _not_ add quotes around 202 | the values. 203 | 204 | e.g.: 205 | 206 | ```ini 207 | env = QT_QPA_PLATFORM,wayland 208 | ``` 209 | 210 | and _**NOT**_ 211 | 212 | ```ini 213 | env = QT_QPA_PLATFORM,"wayland" 214 | ``` 215 | 216 | {{< /callout >}} 217 | -------------------------------------------------------------------------------- /parser/data/sources/Master-Layout.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 11 3 | title: Master Layout 4 | --- 5 | 6 | The master layout makes one (or more) window(s) be the "master", taking (by 7 | default) the left part of the screen, and tiles the rest on the right. You can 8 | change the orientation on a per-workspace basis if you want to use anything other 9 | than the default left/right split. 10 | 11 | ![master1](https://user-images.githubusercontent.com/43317083/179357849-321f042c-f536-44b3-9e6f-371df5321836.gif) 12 | 13 | ## Config 14 | 15 | _category name `master`_ 16 | 17 | | name | description | type | default | 18 | | --- | --- | --- | --- | 19 | | allow_small_split | enable adding additional master windows in a horizontal split style | bool | false | 20 | | special_scale_factor | the scale of the special workspace windows. [0.0 - 1.0] | float | 1 | 21 | | mfact | the size as a percentage of the master window, for example `mfact = 0.70` would mean 70% of the screen will be the master window, and 30% the slave [0.0 - 1.0] | floatvalue | 0.55 | 22 | | new_status | `master`: new window becomes master; `slave`: new windows are added to slave stack; `inherit`: inherit from focused window | string | `slave` | 23 | | new_on_top | whether a newly open window should be on the top of the stack | bool | false | 24 | | new_on_active | `before`, `after`: place new window relative to the focused window; `none`: place new window according to the value of `new_on_top`. | string | `none` | 25 | | orientation | default placement of the master area, can be left, right, top, bottom or center | string | left | 26 | | inherit_fullscreen | inherit fullscreen status when cycling/swapping to another window (e.g. monocle layout) | bool | true | 27 | | slave_count_for_center_master | when using orientation=center, make the master window centered only when at least this many slave windows are open. (Set 0 to always_center_master) | int | 2 | 28 | | center_master_fallback | Set fallback for center master when slaves are less than slave_count_for_center_master, can be left ,right ,top ,bottom | string | left | 29 | | smart_resizing | if enabled, resizing direction will be determined by the mouse's position on the window (nearest to which corner). Else, it is based on the window's tiling position. | bool | true | 30 | | drop_at_cursor | when enabled, dragging and dropping windows will put them at the cursor position. Otherwise, when dropped at the stack side, they will go to the top/bottom of the stack depending on new_on_top. | bool | true | 31 | | always_keep_position | whether to keep the master window in its configured position when there are no slave windows | bool | false | 32 | 33 | ## Dispatchers 34 | 35 | `layoutmsg` commands: 36 | 37 | | command | description | params | 38 | | --- | --- | --- | 39 | | swapwithmaster | swaps the current window with master. If the current window is the master, swaps it with the first child. | either `master` (new focus is the new master window), `child` (new focus is the new child) or `auto` (which is the default, keeps the focus of the previously focused window) | 40 | | focusmaster | focuses the master window. | either `master` (focus stays at master, even if it was selected before) or `auto` (which is the default, if the current window is the master, focuses the first child.) | 41 | | cyclenext | focuses the next window respecting the layout | either `loop` (allow looping from the bottom of the pile back to master) or `noloop` (force stop at the bottom of the pile, like in DWM). `loop` is the default if left blank. | 42 | | cycleprev | focuses the previous window respecting the layout | either `loop` (allow looping from master to the bottom of the pile) or `noloop` (force stop at master, like in DWM). `loop` is the default if left blank. | 43 | | swapnext | swaps the focused window with the next window respecting the layout | either `loop` (allow swapping the bottom of the pile and master) or `noloop` (do not allow it, like in DWM). `loop` is the default if left blank. | 44 | | swapprev | swaps the focused window with the previous window respecting the layout | either `loop` (allow swapping master and the bottom of the pile) or `noloop` (do not allow it, like in DWM). `loop` is the default if left blank. | 45 | | addmaster | adds a master to the master side. That will be the active window, if it's not a master, or the first non-master window. | none | 46 | | removemaster | removes a master from the master side. That will be the active window, if it's a master, or the last master window. | none | 47 | | orientationleft | sets the orientation for the current workspace to left (master area left, slave windows to the right, vertically stacked) | none | 48 | | orientationright | sets the orientation for the current workspace to right (master area right, slave windows to the left, vertically stacked) | none | 49 | | orientationtop | sets the orientation for the current workspace to top (master area top, slave windows to the bottom, horizontally stacked) | none | 50 | | orientationbottom | sets the orientation for the current workspace to bottom (master area bottom, slave windows to the top, horizontally stacked) | none | 51 | | orientationcenter | sets the orientation for the current workspace to center (master area center, slave windows alternate to the left and right, vertically stacked) | none | 52 | | orientationnext | cycle to the next orientation for the current workspace (clockwise) | none | 53 | | orientationprev | cycle to the previous orientation for the current workspace (counter-clockwise) | none | 54 | | orientationcycle | cycle to the next orientation from the provided list, for the current workspace | allowed values: `left`, `top`, `right`, `bottom`, or `center`. The values have to be separated by a space. If left empty, it will work like `orientationnext` | 55 | | mfact | change mfact, the master split ratio | the new split ratio, a relative float delta (e.g `-0.2` or `+0.2`) or `exact` followed by a the exact float value between 0.0 and 1.0 | 56 | | rollnext | rotate the next window in stack to be the master, while keeping the focus on master | none | 57 | | rollprev | rotate the previous window in stack to be the master, while keeping the focus on master | none | 58 | 59 | Parameters for the commands are separated by a single space. 60 | 61 | {{< callout type=info >}} 62 | 63 | Example usage: 64 | 65 | ```ini 66 | bind = MOD, KEY, layoutmsg, cyclenext 67 | # behaves like xmonads promote feature (https://hackage.haskell.org/package/xmonad-contrib-0.17.1/docs/XMonad-Actions-Promote.html) 68 | bind = MOD, KEY, layoutmsg, swapwithmaster master 69 | ``` 70 | 71 | {{< /callout >}} 72 | 73 | ## Workspace Rules 74 | 75 | `layoutopt` rules: 76 | 77 | | rule | description | type | 78 | | --- | --- | --- | 79 | | orientation:[o] | Sets the orientation of a workspace. For available orientations, see [Config->orientation](#config) | string | 80 | 81 | Example usage: 82 | 83 | ```ini 84 | workspace = 2, layoutopt:orientation:top 85 | ``` 86 | -------------------------------------------------------------------------------- /parser/data/sources/Monitors.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 4 3 | title: Monitors 4 | --- 5 | 6 | ## General 7 | 8 | The general config of a monitor looks like this: 9 | 10 | ```ini 11 | monitor = name, resolution, position, scale 12 | ``` 13 | 14 | A common example: 15 | 16 | ```ini 17 | monitor = DP-1, 1920x1080@144, 0x0, 1 18 | ``` 19 | 20 | This will make the monitor on `DP-1` a `1920x1080` display, at 21 | 144Hz, `0x0` off from the top left corner, with a scale of 1 (unscaled). 22 | 23 | To list all available monitors (active and inactive): 24 | 25 | ```bash 26 | hyprctl monitors all 27 | ``` 28 | 29 | Monitors are positioned on a virtual "layout". The `position` is the position, 30 | in pixels, of said display in the layout. (calculated from the top-left corner) 31 | 32 | For example: 33 | 34 | ```ini 35 | monitor = DP-1, 1920x1080, 0x0, 1 36 | monitor = DP-2, 1920x1080, 1920x0, 1 37 | ``` 38 | 39 | will tell Hyprland to put DP-1 on the _left_ of DP-2, while 40 | 41 | ```ini 42 | monitor = DP-1, 1920x1080, 1920x0, 1 43 | monitor = DP-2, 1920x1080, 0x0, 1 44 | ``` 45 | 46 | will tell Hyprland to put DP-1 on the _right_. 47 | 48 | The `position` may contain _negative_ values, so the above example could also be 49 | written as 50 | 51 | ```ini 52 | monitor = DP-1, 1920x1080, 0x0, 1 53 | monitor = DP-2, 1920x1080, -1920x0, 1 54 | ``` 55 | 56 | Hyprland uses an inverse Y cartesian system. Thus, a negative y coordinate 57 | places a monitor higher, and a positive y coordinate will place it lower. 58 | 59 | For example: 60 | 61 | ```ini 62 | monitor = DP-1, 1920x1080, 0x0, 1 63 | monitor = DP-2, 1920x1080, 0x-1080, 1 64 | ``` 65 | 66 | will tell Hyprland to put DP-2 _above_ DP-1, while 67 | 68 | ```ini 69 | monitor = DP-1, 1920x1080, 0x0, 1 70 | monitor = DP-2, 1920x1080, 0x1080, 1 71 | ``` 72 | 73 | will tell Hyprland to put DP-2 _below_. 74 | 75 | {{< callout type=info >}} 76 | 77 | The position is calculated with the scaled (and transformed) resolution, meaning 78 | if you want your 4K monitor with scale 2 to the left of your 1080p one, you'd 79 | use the position `1920x0` for the second screen (3840 / 2). If the monitor is 80 | also rotated 90 degrees (vertical), you'd use `1080x0`. 81 | 82 | {{}} 83 | 84 | Leaving the name empty will define a fallback rule to use when no other rules 85 | match. 86 | 87 | There are a few special values for the resolutions: 88 | 89 | - `preferred` - use the display's preferred size and refresh rate. 90 | - `highres` - use the highest supported resolution. 91 | - `highrr` - use the highest supported refresh rate. 92 | 93 | Position also has a few special values: 94 | 95 | - `auto` - let Hyprland decide on a position. By default, it places each new monitor to the right of existing ones. 96 | - `auto-right/left/up/down` - place the monitor to the right/left, above or below other monitors. 97 | 98 | _**Please Note:**_ While specifying a monitor direction for your first monitor is allowed, this does nothing and it will 99 | be positioned at (0,0). Also the direction is always from the center out, so you can specify `auto-up` then `auto-left`, 100 | but the left monitors will just be left of the origin and above the origin. You can also specify duplicate directions and 101 | monitors will continue to go in that direction. 102 | 103 | You can also use `auto` as a scale to let Hyprland decide on a scale for you. 104 | These depend on the PPI of the monitor. 105 | 106 | Recommended rule for quickly plugging in random monitors: 107 | 108 | ```ini 109 | monitor = , preferred, auto, 1 110 | ``` 111 | 112 | This will make any monitor that was not specified with an explicit rule 113 | automatically placed on the right of the other(s), with its preferred 114 | resolution. 115 | 116 | For more specific rules, you can also use the output's description (see 117 | `hyprctl monitors` for more details). If the output of `hyprctl monitors` looks 118 | like the following: 119 | 120 | ```yaml 121 | Monitor eDP-1 (ID 0): 122 | 1920x1080@60.00100 at 0x0 123 | description: Chimei Innolux Corporation 0x150C (eDP-1) 124 | make: Chimei Innolux Corporation 125 | model: 0x150C 126 | [...] 127 | ``` 128 | 129 | then the `description` value up to, but not including the portname `(eDP-1)` can 130 | be used to specify the monitor: 131 | 132 | ```ini 133 | monitor = desc:Chimei Innolux Corporation 0x150C, preferred, auto, 1.5 134 | ``` 135 | 136 | Remember to remove the `(portname)`! 137 | 138 | ### Custom modelines 139 | 140 | You can set up a custom modeline by changing the resolution field to a modeline, 141 | for example: 142 | 143 | ```ini 144 | monitor = DP-1, modeline 1071.101 3840 3848 3880 3920 2160 2263 2271 2277 +hsync -vsync, 0x0, 1 145 | ``` 146 | 147 | ### Disabling a monitor 148 | 149 | To disable a monitor, use 150 | 151 | ```ini 152 | monitor = name, disable 153 | ``` 154 | 155 | {{< callout >}} 156 | 157 | Disabling a monitor will literally remove it from the layout, moving all windows 158 | and workspaces to any remaining ones. If you want to disable your monitor in a 159 | screensaver style (just turn off the monitor) use the `dpms` 160 | [dispatcher](../Dispatchers). 161 | 162 | {{}} 163 | 164 | ## Custom reserved area 165 | 166 | A reserved area is an area that remains unoccupied by tiled windows. 167 | If your workflow requires a custom reserved area, you can add it with: 168 | 169 | ```ini 170 | monitor = name, addreserved, TOP, BOTTOM, LEFT, RIGHT 171 | ``` 172 | 173 | Where `TOP` `BOTTOM` `LEFT` `RIGHT` are integers, i.e the number in pixels of 174 | the reserved area to add. This does stack on top of the calculated reserved area 175 | (e.g. bars), but you may only use one of these rules per monitor in the config. 176 | 177 | ## Extra args 178 | 179 | You can combine extra arguments at the end of the monitor rule, examples: 180 | 181 | ```ini 182 | monitor = eDP-1, 2880x1800@90, 0x0, 1, transform, 1, mirror, DP-2, bitdepth, 10 183 | ``` 184 | 185 | See below for more details about each argument. 186 | 187 | ### Mirrored displays 188 | 189 | If you want to mirror a display, add a `, mirror, ` at the end of the 190 | monitor rule, examples: 191 | 192 | ```ini 193 | monitor = DP-3, 1920x1080@60, 0x0, 1, mirror, DP-2 194 | monitor = , preferred, auto, 1, mirror, DP-1 195 | ``` 196 | 197 | Please remember that mirroring displays will not "re-render" everything for your 198 | second monitor, so if mirroring a 1080p screen onto a 4K one, the resolution 199 | will still be 1080p on the 4K display. This also means squishing and stretching 200 | will occur on aspect ratios that differ (e.g 16:9 and 16:10). 201 | 202 | ### 10 bit support 203 | 204 | If you want to enable 10 bit support for your display, add a `, bitdepth, 10` at 205 | the end of the monitor rule, e.g: 206 | 207 | ```ini 208 | monitor = eDP-1, 2880x1800@90, 0x0, 1, bitdepth, 10 209 | ``` 210 | 211 | {{< callout >}} 212 | 213 | Colors registered in Hyprland (e.g. the border color) do _not_ support 214 | 10 bit. 215 | 216 | Some applications do _not_ support screen capture with 10 bit enabled. 217 | 218 | {{< /callout >}} 219 | 220 | ### Color management presets 221 | 222 | Add a `, cm, X` to change default sRGB output preset 223 | 224 | ```ini 225 | monitor = eDP-1, 2880x1800@90, 0x0, 1, bitdepth, 10, cm, wide 226 | ``` 227 | 228 | ```plain 229 | auto - srgb for 8bpc, wide for 10bpc if supported (recommended) 230 | srgb - sRGB primaries (default) 231 | wide - wide color gamut, BT2020 primaries 232 | edid - primaries from edid (known to be inaccurate) 233 | hdr - wide color gamut and HDR PQ transfer function (experimental) 234 | hdredid - same as hdr with edid primaries (experimental) 235 | ``` 236 | 237 | Fullscreen HDR is possible without hdr `cm` setting if `render:cm_fs_passthrough` is enabled. 238 | 239 | Use `sdrbrightness, B` and `sdrsaturation, S` to control SDR brighness and saturation in HDR mode. The default for both values is `1.0`. Typical brightness value should be in `1.0 ... 2.0` range. 240 | 241 | ```ini 242 | monitor = eDP-1, 2880x1800@90, 0x0, 1, bitdepth, 10, cm, hdr, sdrbrightness, 1.2, sdrsaturation, 0.98 243 | ``` 244 | 245 | ### VRR 246 | 247 | Per-display VRR can be done by adding `, vrr, X` where `X` is the mode from the 248 | [variables page](../Variables). 249 | 250 | ## Rotating 251 | 252 | If you want to rotate a monitor, add a `, transform, X` at the end of the monitor 253 | rule, where `X` corresponds to a transform number, e.g.: 254 | 255 | ```ini 256 | monitor = eDP-1, 2880x1800@90, 0x0, 1, transform, 1 257 | ``` 258 | 259 | Transform list: 260 | 261 | ```plain 262 | 0 -> normal (no transforms) 263 | 1 -> 90 degrees 264 | 2 -> 180 degrees 265 | 3 -> 270 degrees 266 | 4 -> flipped 267 | 5 -> flipped + 90 degrees 268 | 6 -> flipped + 180 degrees 269 | 7 -> flipped + 270 degrees 270 | ``` 271 | 272 | ## Default workspace 273 | 274 | See [Workspace Rules](../Workspace-Rules). 275 | 276 | ### Binding workspaces to a monitor 277 | 278 | See [Workspace Rules](../Workspace-Rules). 279 | -------------------------------------------------------------------------------- /parser/data/sources/Multi-GPU.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 17 3 | title: Multi-GPU 4 | --- 5 | 6 | ## General 7 | 8 | If your host machine uses multiple GPUs, you may want to use one GPU 9 | for rendering all the elements for Hyprland including windows, animations, and 10 | another for hardware acceleration for certain applications, etc. 11 | 12 | This setup is very common in the likes of gaming laptops, GPU-passthrough 13 | (without VFIO) capable hosts, and if you have multiple GPUs in general. 14 | 15 | ## Detecting GPUs 16 | 17 | For this case, the writer is taking the example of their laptop. 18 | 19 | Upon running `lspci -d ::03xx`, one can list all the PCI display controllers 20 | available. 21 | 22 | ```plain 23 | 01:00.0 VGA compatible controller: NVIDIA Corporation TU117M [GeForce GTX 1650 Mobile / Max-Q] (rev a1) 24 | 06:00.0 VGA compatible controller: Advanced Micro Devices, Inc. [AMD/ATI] Cezanne [Radeon Vega Series / Radeon Vega Mobile Series] (rev c6) 25 | ``` 26 | 27 | Here it is clear that 2 GPUs are available, the dedicated NVIDIA GTX 1650 Mobile 28 | / Max-Q and the integrated AMD Cezanne Radeon Vega Series GPU. 29 | 30 | Now, run `ls -l /dev/dri/by-path` 31 | 32 | ```plain 33 | total 0 34 | lrwxrwxrwx 1 root root 8 Jul 14 15:45 pci-0000:01:00.0-card -> ../card0 35 | lrwxrwxrwx 1 root root 13 Jul 14 15:45 pci-0000:01:00.0-render -> ../renderD128 36 | lrwxrwxrwx 1 root root 8 Jul 14 15:45 pci-0000:06:00.0-card -> ../card1 37 | lrwxrwxrwx 1 root root 13 Jul 14 15:45 pci-0000:06:00.0-render -> ../renderD129 38 | ``` 39 | 40 | So from the above outputs, we can see that the path for the AMD card is 41 | `pci-0000:06:00.0-card`, due to the matching `06:00.0` from the first command. 42 | Do not use the `card1` symlink indicated here. It is dynamically assigned at 43 | boot and is subject to frequent change, making it unsuitable as a marker for GPU selection. 44 | 45 | ## Telling Hyprland which GPU to use 46 | 47 | After determining which "card" belongs to which GPU, we can now tell 48 | Hyprland which GPUs to use by setting the `AQ_DRM_DEVICES` environment variable. 49 | 50 | {{< callout type=info >}} 51 | 52 | It is generally a good idea for laptops to use the integrated GPU as the primary 53 | renderer as this preserves battery life and is practically indistinguishable 54 | from using the dedicated GPU on modern systems in most cases. Hyprland can be 55 | run on integrated GPUs just fine. The same principle applies for desktop setups 56 | with lower and higher power rating GPUs respectively. 57 | 58 | {{< /callout >}} 59 | 60 | If you would like to use another GPU, or the wrong GPU is picked by default, 61 | set `AQ_DRM_DEVICES` to a `:`-separated list of card paths, e.g. 62 | 63 | ```plain 64 | env = AQ_DRM_DEVICES,/dev/dri/card0:/dev/dri/card1 65 | ``` 66 | 67 | Here, we tell Hyprland which GPUs it's allowed to use, in order of priority. 68 | For example, `card0` will be the primary renderer, but if it isn't available for 69 | whatever reason, then `card1` is primary. 70 | 71 | Do note that if you have an external monitor connected to, for example `card1`, 72 | that card must be included in `AQ_DRM_DEVICES` for the monitor to work, though 73 | it doesn't have to be the primary renderer. 74 | 75 | You should now be able to use an integrated GPU for lighter GPU loads, 76 | including Hyprland, or default to your dGPU if you prefer. 77 | 78 | {{< callout type=info >}} 79 | 80 | [uwsm](../../Useful-Utilities/Systemd-start) users are advised to export the `AQ_DRM_DEVICES` variable inside `~/.config/uwsm/env-hyprland`, instead. 81 | This method ensures that the variable is properly exported to the systemd environment without conflicting with other compositors or desktop environments. 82 | 83 | ```plain 84 | export AQ_DRM_DEVICES="/dev/dri/card0:/dev/dri/card1" 85 | ``` 86 | 87 | {{< /callout >}} 88 | -------------------------------------------------------------------------------- /parser/data/sources/Performance.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 20 3 | title: Performance 4 | --- 5 | 6 | This page documents known tricks and fixes to boost performance if by any chance 7 | you stumble upon problems or you do not care that much about animations. 8 | 9 | ## Fractional scaling 10 | 11 | Wayland fractional scaling is a lot better than before, but it is not perfect. 12 | Some applications do not support it yet or the support is experimental at best. 13 | If you have problems with your graphics card having high usage or Hyprland 14 | feeling laggy, try setting the scaling to integer numbers such as `1` or `2` 15 | like in this example `monitor=,preferred,auto,2`. 16 | 17 | ## Low FPS/stutter/FPS drops on Intel iGPU with TLP (mainly laptops) 18 | 19 | The TLP defaults are rather aggressive, setting `INTEL_GPU_MIN_FREQ_ON_AC` 20 | and/or `INTEL_GPU_MIN_FREQ_ON_BAT` in `/etc/tlp.conf` to something slightly 21 | higher (e.g. to 500 from 300) will reduce stutter significantly or, in the best 22 | case, remove it completely. 23 | 24 | ## How do I make Hyprland draw as little power as possible on my laptop? 25 | 26 | **_Useful Optimizations_**: 27 | 28 | - `decoration:blur:enabled = false` and `decoration:shadow:enabled = false` to disable 29 | fancy but battery hungry effects. 30 | 31 | - `misc:vfr = true`, since it'll lower the amount of sent frames when nothing is 32 | happening on-screen. 33 | 34 | ## My games work poorly, especially proton ones 35 | 36 | Using `gamescope` tends to fix any and all issues with Wayland/Hyprland. 37 | -------------------------------------------------------------------------------- /parser/data/sources/Permissions.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 12 3 | title: Permissions 4 | --- 5 | 6 | If you have `hyprland-qtutils` installed, you can make use of Hyprland's built-in 7 | permission system. 8 | 9 | For now, it only has a few permissions, but it might include more in the future. 10 | 11 | ## Permissions 12 | 13 | Permissions work a bit like Android ones. If an app tries to do something sensitive with 14 | the compositor (Hyprland), Hyprland will pop up a notification asking you if you 15 | want to let it do that. 16 | 17 | {{< callout type=info >}} 18 | 19 | Before setting up permissions, make sure you enable them by setting 20 | `ecosystem:enforce_permissions = true`, as it's disabled by default. 21 | 22 | {{}} 23 | 24 | 25 | ### Configuring permissions 26 | 27 | {{< callout type=important >}} 28 | 29 | Permissions set up in the config are **not** reloaded on-the-fly and require a Hyprland 30 | restart for security reasons. 31 | 32 | {{}} 33 | 34 | Configuring them is simple: 35 | 36 | ```ini 37 | permission = regex, permission, mode 38 | ``` 39 | 40 | for example: 41 | ```ini 42 | permission = /usr/bin/grim, screencopy, allow 43 | ``` 44 | Will allow `/usr/bin/grim` to always capture your screen without asking. 45 | 46 | ```ini 47 | permission = /usr/bin/appsuite-.*, screencopy, allow 48 | ``` 49 | Will allow any app whose path starts with `/usr/bin/appsuite-` to capture your screen without asking. 50 | 51 | 52 | ### Permisision modes 53 | 54 | There are 3 modes: 55 | - `allow`: Don't ask, just allow the app to proceed. 56 | - `ask`: Pop up a notification every time the app tries to do something sensitive. These popups allow you to Deny, Allow until the app exits, or Allow until Hyprland exits. 57 | - `deny`: Don't ask, always deny the application access. 58 | 59 | 60 | ### Permission list 61 | 62 | `screencopy`: 63 | - Default: **ASK** 64 | - Access to your screen _without_ going through xdg-desktop-portal-hyprland. Examples include: `grim`, `wl-screenrec`, `wf-recorder`. 65 | - If denied, will render a black screen with a "permission denied" text. 66 | - Why deny? For apps / scripts that might maliciously try to capture your screen without your knowledge by using wayland protocols directly. 67 | 68 | `plugin`: 69 | - Default: **ASK** 70 | - Access to load a plugin. Can be either a regex for the app binary, or plugin path. 71 | - Do _not_ allow `hyprctl` to load your plugins by default (attacker could issue `hyprctl plugin load /tmp/my-malicious-plugin.so`) - use either `deny` to disable or `ask` to be prompted. 72 | 73 | `keyboard`: 74 | - Default: **ALLOW** 75 | - Access to connecting a new keyboard. Regex of the device name. 76 | - If you want to disable all keyboards not matching a regex, make a rule that sets `DENY` for `.*` _as the last keyboard permission rule_. 77 | - Why deny? Rubber duckies, malicious virtual / usb keyboards. 78 | 79 | ## Notes 80 | 81 | **xdg-desktop-portal** implementations (including xdph) are just regular applications. They will go through permissions too. You might want to consider 82 | adding a rule like this: 83 | ```ini 84 | permission = /usr/(lib|libexec|lib64)/xdg-desktop-portal-hyprland, screencopy, allow 85 | ``` 86 | if you are not allowing screencopy for all apps. 87 | 88 |
89 | 90 | On some **BSD** systems paths might not work. In such cases, you might want to disable permissions altogether, by setting 91 | ```ini 92 | ecosystem { 93 | enforce_permissions = false 94 | } 95 | ``` 96 | otherwise, you have no _config_ control over permissions (popups will still work, although will not show paths, and "remember" will not be available). 97 | 98 | -------------------------------------------------------------------------------- /parser/data/sources/Start.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 1 3 | title: Start 4 | --- 5 | 6 | The config is located in `$XDG_CONFIG_HOME/hypr/hyprland.conf`. In most cases, 7 | that maps to `~/.config/hypr/hyprland.conf`. 8 | 9 | You can tell Hyprland to use a specific configuration file by using the 10 | `--config` (or `-c`) argument. 11 | 12 | Hyprland will automatically generate an example config for you if you don't have 13 | one. You can find an example config 14 | [here](https://github.com/hyprwm/Hyprland/blob/main/example/hyprland.conf). 15 | 16 | By removing the line containing `autogenerated=1` you'll remove the yellow 17 | warning. 18 | 19 | The config is reloaded the moment you save it. However, you can use 20 | `hyprctl reload` to reload the config manually. 21 | 22 | Start a section with `name {` and end in `}` **_in separate lines!_** 23 | 24 | {{< callout >}} 25 | 26 | The default config is not complete and does not list all the options / features 27 | of Hyprland. Please refer to this wiki page and the pages linked further down 28 | here for full configuration instructions. 29 | 30 | **Make sure to read the [Variables](../Variables) page as well**. It covers all 31 | the toggleable / numerical options. 32 | 33 | {{< /callout >}} 34 | 35 | ## Language style and syntax 36 | 37 | See the [hyprlang page](../../Hypr-Ecosystem/hyprlang). 38 | 39 | ## Basic configuring 40 | 41 | To configure Hyprland's options, animations, styling, etc. see 42 | [Variables](../Variables). 43 | 44 | ## Advanced configuring 45 | 46 | Some keywords (binds, curves, execs, monitors, etc.) are not variables but 47 | define special behavior. 48 | 49 | See all of them in [Keywords](../Keywords) and the sidebar. -------------------------------------------------------------------------------- /parser/data/sources/Tearing.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 10 3 | title: Tearing 4 | --- 5 | 6 | Screen tearing is used to reduce latency and/or jitter in games. 7 | 8 | ## Enabling tearing 9 | 10 | To enable tearing: 11 | 12 | - Set `general:allow_tearing` to `true`. This is a "master toggle" 13 | - Add an `immediate` windowrule to your game of choice. This makes sure that 14 | Hyprland will tear it. 15 | 16 | {{< callout >}} 17 | 18 | Please note that tearing will only be in effect when the game is in fullscreen 19 | and the only thing visible on the screen. 20 | 21 | {{< /callout >}} 22 | 23 | Example snippet: 24 | 25 | ```env 26 | general { 27 | allow_tearing = true 28 | } 29 | 30 | windowrule = immediate, class:^(cs2)$ 31 | ``` 32 | 33 | {{< callout type=warning >}} 34 | 35 | If you experience graphical issues, you may be out of luck. Tearing support is 36 | experimental. 37 | 38 | See the likely culprits below. 39 | 40 | {{< /callout >}} 41 | 42 | ## Common issues 43 | 44 | ### No tearing at all 45 | 46 | Make sure your window rules are matching and you have the master toggle enabled. 47 | 48 | Also make sure nothing except for your game is showing on your monitor. No 49 | notifications, overlays, lockscreens, bars, other windows, etc. (on a different 50 | monitor is fine) 51 | 52 | ### Apps that should tear, freeze 53 | 54 | Almost definitely means your GPU driver does not support tearing. 55 | 56 | Please _do not_ report issues if this is the culprit. 57 | 58 | ### Graphical artifacts (random colorful pixels, etc) 59 | 60 | Likely issue with your graphics driver. 61 | 62 | Please _do not_ report issues if this is the culprit. Unfortunately, it's most 63 | likely your GPU driver's fault. 64 | -------------------------------------------------------------------------------- /parser/data/sources/Uncommon-tips-&-tricks.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 18 3 | title: Uncommon tips & tricks 4 | --- 5 | 6 | ## Switchable keyboard layouts 7 | 8 | The easiest way to accomplish this is to set this using XKB settings, for 9 | example: 10 | 11 | ```ini 12 | input { 13 | kb_layout = us,cz 14 | kb_variant = ,qwerty 15 | kb_options = grp:alt_shift_toggle 16 | } 17 | ``` 18 | 19 | Variants are set per layout. 20 | 21 | {{< callout >}} 22 | 23 | The first layout defined in the input section will be the one used for binds by 24 | default. 25 | 26 | For example: `us,ua` -> config binds would be e.g. `SUPER, A`, while on `ua,us` 27 | -> `SUPER, Cyrillic_ef` 28 | 29 | You can change this behavior globally or per-device by setting 30 | `resolve_binds_by_sym = 1`. In that case, binds will activate when the symbol 31 | typed matches the symbol specified in the bind. 32 | 33 | For example: if your layouts are `us,fr` and have a bind for `SUPER, A` you'd 34 | need to press the first letter on the second row while the `us` layout is active 35 | and the first letter on the first row while the `fr` layout is active. 36 | 37 | {{< /callout >}} 38 | 39 | You can also bind a key to execute `hyprctl switchxkblayout` for more keybind 40 | freedom. See [Using hyprctl](../Using-hyprctl). 41 | 42 | To find the valid layouts and `kb_options`, you can check out the 43 | `/usr/share/X11/xkb/rules/base.lst`. For example: 44 | 45 | To get the layout name of a language: 46 | 47 | ```sh 48 | grep -i 'persian' /usr/share/X11/xkb/rules/base.lst 49 | ``` 50 | 51 | To get the list of keyboard shortcuts you can put in the `kb_options` to toggle 52 | keyboard layouts: 53 | 54 | ```sh 55 | grep 'grp:.*toggle' /usr/share/X11/xkb/rules/base.lst 56 | ``` 57 | 58 | ## Disabling keybinds with one master keybind 59 | 60 | If you want to disable all keybinds with another keybind (make a keybind toggle 61 | of sorts) you can just use a submap with only a keybind to exit it. 62 | 63 | ```ini 64 | bind = MOD, KEY, submap, clean 65 | submap = clean 66 | bind = MOD, KEY, submap, reset 67 | submap = reset 68 | ``` 69 | 70 | ## Remap Caps-Lock to Ctrl 71 | 72 | ```ini 73 | input { 74 | kb_options = ctrl:nocaps 75 | } 76 | ``` 77 | 78 | ## Swap Caps-Lock and Escape 79 | 80 | ```ini 81 | input { 82 | kb_options = caps:swapescape 83 | } 84 | ``` 85 | 86 | ## Set F13-F24 as usual function keys 87 | 88 | By default, F13-F24 are mapped by xkb as various "XF86" keysyms. These cause binding 89 | issues in many programs. One example is OBS Studio, which does not detect the XF86 90 | keysyms as usable keybindings, making you unable to use them for binds. This option 91 | simply maps them back to the expected F13-F24 values, which are bindable as normal. 92 | 93 | {{< callout >}} 94 | This option was only added recently to `xkeyboard-config`. Please ensure you are on version 95 | 2.43 or greater for this option to do anything. 96 | {{< /callout >}} 97 | 98 | ```ini 99 | input { 100 | kb_options = fkeys:basic_13-24 101 | } 102 | ``` 103 | 104 | ## Minimize windows using special workspaces 105 | 106 | This approach uses special workspaces to mimic the "minimize window" function, by using a single keybind to toggle the minimized state. 107 | Note that one keybind can only handle one window. 108 | 109 | ```ini 110 | bind = $mod, S, togglespecialworkspace, magic 111 | bind = $mod, S, movetoworkspace, +0 112 | bind = $mod, S, togglespecialworkspace, magic 113 | bind = $mod, S, movetoworkspace, special:magic 114 | bind = $mod, S, togglespecialworkspace, magic 115 | ``` 116 | 117 | ## Show desktop 118 | 119 | This approach uses same principle as the [Minimize windows using special workspaces](#minimize-windows-using-special-workspaces) section. 120 | It moves all windows from current workspace to a special workspace named `desktop`. 121 | Showing desktop state is remembered per workspace. 122 | 123 | Create a script: 124 | 125 | ```sh 126 | #!/bin/env sh 127 | 128 | TMP_FILE="$XDG_RUNTIME_DIR/hyprland-show-desktop" 129 | 130 | CURRENT_WORKSPACE=$(hyprctl monitors -j | jq '.[] | .activeWorkspace | .name' | sed 's/"//g') 131 | 132 | if [ -s "$TMP_FILE-$CURRENT_WORKSPACE" ]; then 133 | readarray -d $'\n' -t ADDRESS_ARRAY <<< $(< "$TMP_FILE-$CURRENT_WORKSPACE") 134 | 135 | for address in "${ADDRESS_ARRAY[@]}" 136 | do 137 | CMDS+="dispatch movetoworkspacesilent name:$CURRENT_WORKSPACE,address:$address;" 138 | done 139 | 140 | hyprctl --batch "$CMDS" 141 | 142 | rm "$TMP_FILE-$CURRENT_WORKSPACE" 143 | else 144 | HIDDEN_WINDOWS=$(hyprctl clients -j | jq --arg CW "$CURRENT_WORKSPACE" '.[] | select (.workspace .name == $CW) | .address') 145 | 146 | readarray -d $'\n' -t ADDRESS_ARRAY <<< $HIDDEN_WINDOWS 147 | 148 | for address in "${ADDRESS_ARRAY[@]}" 149 | do 150 | address=$(sed 's/"//g' <<< $address ) 151 | 152 | if [[ -n address ]]; then 153 | TMP_ADDRESS+="$address\n" 154 | fi 155 | 156 | CMDS+="dispatch movetoworkspacesilent special:desktop,address:$address;" 157 | done 158 | 159 | hyprctl --batch "$CMDS" 160 | 161 | echo -e "$TMP_ADDRESS" | sed -e '/^$/d' > "$TMP_FILE-$CURRENT_WORKSPACE" 162 | fi 163 | ``` 164 | 165 | then bind it: 166 | 167 | ```ini 168 | bind = $mainMod , D, exec, 169 | ``` 170 | 171 | ## Minimize Steam instead of killing 172 | 173 | Steam will exit entirely when its last window is closed using the `killactive` 174 | dispatcher. To minimize Steam to tray, use the following script to close 175 | applications: 176 | 177 | ```sh 178 | if [ "$(hyprctl activewindow -j | jq -r ".class")" = "Steam" ]; then 179 | xdotool getactivewindow windowunmap 180 | else 181 | hyprctl dispatch killactive "" 182 | fi 183 | ``` 184 | 185 | ## Shimeji 186 | 187 | To use Shimeji programs like 188 | [this](https://codeberg.org/thatonecalculator/spamton-linux-shimeji), set the 189 | following rules: 190 | 191 | ```ini 192 | windowrule = float, class:com-group_finity-mascot-Main 193 | windowrule = noblur, class:com-group_finity-mascot-Main 194 | windowrule = nofocus, class:com-group_finity-mascot-Main 195 | windowrule = noshadow, class:com-group_finity-mascot-Main 196 | windowrule = noborder, class:com-group_finity-mascot-Main 197 | ``` 198 | 199 | {{< callout type=info >}} 200 | 201 | The app indicator probably won't show, so you'll have to `killall -9 java` to 202 | kill them. 203 | 204 | {{< /callout >}} 205 | 206 | ![Demo GIF of Spamton Shimeji](https://github.com/hyprwm/hyprland-wiki/assets/36706276/261afd03-bf41-4513-b72b-3483d43d418c) 207 | 208 | ## Toggle animations/blur/etc hotkey 209 | 210 | For increased performance in games, or for less distractions at a keypress 211 | 212 | 1. create file 213 | `~/.config/hypr/gamemode.sh && chmod +x ~/.config/hypr/gamemode.sh` and add: 214 | 215 | ```bash 216 | #!/usr/bin/env sh 217 | HYPRGAMEMODE=$(hyprctl getoption animations:enabled | awk 'NR==1{print $2}') 218 | if [ "$HYPRGAMEMODE" = 1 ] ; then 219 | hyprctl --batch "\ 220 | keyword animations:enabled 0;\ 221 | keyword decoration:shadow:enabled 0;\ 222 | keyword decoration:blur:enabled 0;\ 223 | keyword general:gaps_in 0;\ 224 | keyword general:gaps_out 0;\ 225 | keyword general:border_size 1;\ 226 | keyword decoration:rounding 0" 227 | exit 228 | fi 229 | hyprctl reload 230 | ``` 231 | 232 | Edit to your liking of course. If animations are enabled, it disables all the 233 | pretty stuff. Otherwise, the script reloads your config to grab your defaults. 234 | 235 | 2. Add this to your `hyprland.conf`: 236 | 237 | ```ini 238 | bind = WIN, F1, exec, ~/.config/hypr/gamemode.sh 239 | ``` 240 | 241 | The hotkey toggle will be WIN+F1, but you can change this to whatever you want. 242 | 243 | ## Zoom 244 | 245 | To zoom using Hyprland's built-in zoom utility 246 | {{< callout >}} 247 | If mouse wheel bindings work only for the first time, you should probably reduce reset time with `binds:scroll_event_delay` 248 | {{< /callout >}} 249 | 250 | ```ini 251 | bind = $mod, mouse_down, exec, hyprctl -q keyword cursor:zoom_factor $(hyprctl getoption cursor:zoom_factor | awk '/^float.*/ {print $2 * 1.1}') 252 | bind = $mod, mouse_up, exec, hyprctl -q keyword cursor:zoom_factor $(hyprctl getoption cursor:zoom_factor | awk '/^float.*/ {print $2 * 0.9}') 253 | 254 | binde = $mod, equal, exec, hyprctl -q keyword cursor:zoom_factor $(hyprctl getoption cursor:zoom_factor | awk '/^float.*/ {print $2 * 1.1}') 255 | binde = $mod, minus, exec, hyprctl -q keyword cursor:zoom_factor $(hyprctl getoption cursor:zoom_factor | awk '/^float.*/ {print $2 * 0.9}') 256 | binde = $mod, KP_ADD, exec, hyprctl -q keyword cursor:zoom_factor $(hyprctl getoption cursor:zoom_factor | awk '/^float.*/ {print $2 * 1.1}') 257 | binde = $mod, KP_SUBTRACT, exec, hyprctl -q keyword cursor:zoom_factor $(hyprctl getoption cursor:zoom_factor | awk '/^float.*/ {print $2 * 0.9}') 258 | 259 | bind = $mod SHIFT, mouse_up, exec, hyprctl -q keyword cursor:zoom_factor 1 260 | bind = $mod SHIFT, mouse_down, exec, hyprctl -q keyword cursor:zoom_factor 1 261 | bind = $mod SHIFT, minus, exec, hyprctl -q keyword cursor:zoom_factor 1 262 | bind = $mod SHIFT, KP_SUBTRACT, exec, hyprctl -q keyword cursor:zoom_factor 1 263 | bind = $mod SHIFT, 0, exec, hyprctl -q keyword cursor:zoom_factor 1 264 | ``` 265 | 266 | ## Alt tab behaviour 267 | To mimic DE's alt-tab behaviour. Here is an example that uses foot, fzf, [grim-hyprland](https://github.com/eriedaberrie/grim-hyprland) and chafa to the screenshot in the terminal. 268 | 269 | ![alttab](https://github.com/user-attachments/assets/2a260809-b1b0-4f72-8644-46cc9d8b8971) 270 | 271 | Dependencies : 272 | - foot 273 | - fzf 274 | - [grim-hyprland](https://github.com/eriedaberrie/grim-hyprland) 275 | - chafa 276 | - jq 277 | 278 | 1. add this to your config 279 | 280 | ```ini 281 | exec-once = foot --server 282 | 283 | bind = ALT, tab, exec, hyprctl -q keyword animations:enabled false ; hyprctl -q dispatch exec "footclient -a alttab $XDG_CONFIG_HOME/hypr/scripts/alttab/alttab.sh" ; hyprctl -q keyword unbind "ALT, TAB" ; hyprctl -q dispatch submap alttab 284 | 285 | submap=alttab 286 | bind = ALT, tab, sendshortcut, , tab, class:alttab 287 | bind = ALT SHIFT, tab, sendshortcut, shift, tab, class:alttab 288 | 289 | bindrt = ALT, ALT_L, exec, $XDG_CONFIG_HOME/hypr/scripts/alttab/disable.sh ; hyprctl -q dispatch sendshortcut ,return,class:alttab 290 | bind = ALT, escape, exec, $XDG_CONFIG_HOME/hypr/scripts/alttab/disable.sh ; hyprctl -q dispatch sendshortcut ,escape,class:alttab 291 | submap = reset 292 | 293 | workspace = special:alttab, gapsout:0, gapsin:0, bordersize:0 294 | windowrule = noanim, class:alttab 295 | windowrule = stayfocused, class:alttab 296 | windowrule = workspace special:alttab, class:alttab 297 | windowrule = bordersize 0, class:alttab 298 | ``` 299 | 300 | 2. create file `touch $XDG_CONFIG_HOME/hypr/scripts/alttab/alttab.sh && chmod +x $XDG_CONFIG_HOME/hypr/scripts/alttab/alttab.sh` and add: 301 | 302 | ```bash {filename="alttab.sh"} 303 | #!/usr/bin/env bash 304 | address=$(hyprctl -j clients | jq -r 'sort_by(.focusHistoryID) | .[] | select(.workspace.id >= 0) | "\(.address)\t\(.title)"' | 305 | fzf --color prompt:green,pointer:green,current-bg:-1,current-fg:green,gutter:-1,border:bright-black,current-hl:red,hl:red \ 306 | --cycle \ 307 | --sync \ 308 | --bind tab:down,shift-tab:up,start:down,double-click:ignore \ 309 | --wrap \ 310 | --delimiter=$'\t' \ 311 | --with-nth=2 \ 312 | --preview "$XDG_CONFIG_HOME/hypr/scripts/alttab/preview.sh {}" \ 313 | --preview-window=down:80% \ 314 | --layout=reverse | 315 | awk -F"\t" '{print $1}') 316 | 317 | if [ -n "$address" ] ; then 318 | hyprctl --batch -q "dispatch focuswindow address:$address;dispatch alterzorder top" 319 | fi 320 | 321 | hyprctl -q dispatch submap reset 322 | ``` 323 | 324 | I chose to exclude windows that are in special workspaces but it can be modified by removing `select(.workspace.id >= 0)` 325 | 326 | 3. create file `touch $XDG_CONFIG_HOME/hypr/scripts/alttab/preview.sh && chmod +x $XDG_CONFIG_HOME/hypr/scripts/alttab/preview.sh` and add: 327 | 328 | ```bash {filename="preview.sh"} 329 | #!/usr/bin/env bash 330 | line="$1" 331 | 332 | IFS=$'\t' read -r addr _ <<< "$line" 333 | dim=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES} 334 | 335 | grim -t png -l 0 -w "$addr" ~.config/hypr/scripts/alttab/preview.png 336 | chafa --animate false -s "$dim" "$XDG_CONFIG_HOME/hypr/scripts/alttab/preview.png" 337 | ``` 338 | 339 | 4. create file `touch $XDG_CONFIG_HOME/hypr/scripts/alttab/disable.sh && chmod +x $XDG_CONFIG_HOME/hypr/scripts/alttab/disable.sh` and add: 340 | 341 | ```bash {filename="disable.sh"} 342 | #!/usr/bin/env bash 343 | hyprctl -q keyword animations:enabled true 344 | 345 | hyprctl -q keyword unbind "ALT, tab" 346 | hyprctl -q keyword bind ALT, tab, exec, "hyprctl -q keyword animations:enabled false ; hyprctl -q dispatch exec 'footclient -a alttab $XDG_CONFIG_HOME/hypr/scripts/alttab/alttab.sh' ; hyprctl -q keyword unbind 'ALT, tab' ; hyprctl -q dispatch submap alttab" 347 | ``` 348 | -------------------------------------------------------------------------------- /parser/data/sources/Using-hyprctl.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 13 3 | title: Using hyprctl 4 | --- 5 | 6 | `hyprctl` is a utility for controlling some parts of the compositor from a CLI 7 | or a script. It should automatically be installed along with Hyprland. 8 | 9 | {{< callout type=warning >}} 10 | 11 | _hyprctl_ calls will be dispatched by the compositor _synchronously_, meaning 12 | any spam of the utility will cause slowdowns. It's recommended to use `--batch` 13 | for many control calls, and limiting the amount of info calls. 14 | 15 | For live event handling, see the [socket2](../../IPC/). 16 | 17 | {{< /callout >}} 18 | 19 | ## Commands 20 | 21 | ### dispatch 22 | 23 | Issue a `dispatch` to call a keybind dispatcher with an argument. 24 | 25 | An argument has to be present, for dispatchers without parameters it can be 26 | anything. 27 | 28 | To pass an argument starting with `-` or `--`, such as command line options 29 | to `exec` programs, pass `--` as an option. This will disable any subsequent 30 | parsing of options by _hyprctl_. 31 | 32 | Examples: 33 | 34 | ```sh 35 | hyprctl dispatch exec kitty 36 | 37 | hyprctl dispatch -- exec kitty --single-instance 38 | 39 | hyprctl dispatch pseudo x 40 | ``` 41 | 42 | Returns: `ok` on success, an error message on fail. 43 | 44 | See [Dispatchers](../Dispatchers) for a list of dispatchers. 45 | 46 | ### keyword 47 | 48 | issue a `keyword` to call a config keyword dynamically. 49 | 50 | Examples: 51 | 52 | ```sh 53 | hyprctl keyword bind SUPER,O,pseudo 54 | 55 | hyprctl keyword general:border_size 10 56 | 57 | hyprctl keyword monitor DP-3,1920x1080@144,0x0,1 58 | ``` 59 | 60 | Returns: `ok` on success, an error message on fail. 61 | 62 | ### reload 63 | 64 | Issue a `reload` to force reload the config. 65 | 66 | ### kill 67 | 68 | Issue a `kill` to get into a kill mode, where you can kill an app by clicking on 69 | it. You can exit it with ESCAPE. 70 | 71 | Kind of like xkill. 72 | 73 | ### setcursor 74 | 75 | Sets the cursor theme and reloads the cursor manager. Will set the theme for 76 | everything except GTK, because GTK. 77 | 78 | Please note that since 0.37.0, this only accepts hyprcursor themes. For legacy xcursor themes, 79 | use the `XCURSOR_THEME` and `XCURSOR_SIZE` env vars. 80 | 81 | params: theme and size 82 | 83 | e.g.: 84 | 85 | ```sh 86 | hyprctl setcursor Bibata-Modern-Classic 24 87 | ``` 88 | 89 | ### output 90 | 91 | Allows you to add and remove fake outputs to your preferred backend. 92 | 93 | Usage: 94 | 95 | ```sh 96 | hyprctl output create [backend] (name) 97 | ``` 98 | 99 | or 100 | 101 | ```sh 102 | hyprctl output remove [name] 103 | ``` 104 | 105 | Where `[backend]` is the name of the backend and `(name)` is an optional name 106 | for the output. If `(name)` is not specified, the default naming scheme will be 107 | used (`HEADLESS-2`, `WL-1`, etc.) 108 | 109 | {{< callout type=info >}} 110 | 111 | `create` and `remove` can also be `add` or `destroy`, respectively. 112 | 113 | {{< /callout >}} 114 | 115 | Available backends: 116 | 117 | - `wayland`: Creates an output as a Wayland window. This will only work if 118 | you're already running Hyprland with the Wayland backend. 119 | - `headless`: Creates a headless monitor output. If you're running a VNC/RDP/ 120 | Sunshine server, you should use this. 121 | - `auto`: Picks a backend for you. For example, if you're running Hyprland from 122 | the TTY, `headless` will be chosen. 123 | 124 | For example, to create a headless output named "test": 125 | 126 | ```sh 127 | hyprctl output create headless test 128 | ``` 129 | 130 | And to remove it: 131 | 132 | ```sh 133 | hyprctl output remove test 134 | ``` 135 | 136 | ### switchxkblayout 137 | 138 | Sets the xkb layout index for a keyboard. 139 | 140 | For example, if you set: 141 | 142 | ```ini 143 | device { 144 | name = my-epic-keyboard-v1 145 | kb_layout = us,pl,de 146 | } 147 | ``` 148 | 149 | You can use this command to switch between them. 150 | 151 | ```sh 152 | hyprctl switchxkblayout [DEVICE] [CMD] 153 | ``` 154 | 155 | where `CMD` is either `next` for next, `prev` for previous, or `ID` for a 156 | specific one (in the above case, `us`: 0, `pl`: 1, `de`: 2). You can find the 157 | `DEVICE` using `hyprctl devices` command. 158 | 159 | `DEVICE` can also be `current` or `all`, self-explanatory. Current is the `main` keyboard from `devices`. 160 | 161 | Example command for a typical keyboard: 162 | 163 | ```sh 164 | hyprctl switchxkblayout at-translated-set-2-keyboard next 165 | ``` 166 | 167 | {{< callout type=info >}} 168 | 169 | If you want a single variant i.e. pl/dvorak on one layout but us/qwerty on the 170 | other, xkb parameters can still be blank, however the amount of comma-separated 171 | parameters have to match. Alternatively, a single parameter can be specified for 172 | it to apply to all three. 173 | 174 | ```ini 175 | input { 176 | kb_layout = pl,us,ru 177 | kb_variant = dvorak,, 178 | kb_options = caps:ctrl_modifier 179 | } 180 | ``` 181 | 182 | {{< /callout >}} 183 | 184 | ### seterror 185 | 186 | Sets the hyprctl error string. Will reset when Hyprland's config is reloaded. 187 | 188 | ```sh 189 | hyprctl seterror 'rgba(66ee66ff)' hello world this is my problem 190 | ``` 191 | 192 | To disable: 193 | 194 | ```sh 195 | hyprctl seterror disable 196 | ``` 197 | 198 | ### notify 199 | 200 | Sends a notification using the built-in Hyprland notification system. 201 | 202 | ```sh 203 | hyprctl notify [ICON] [TIME_MS] [COLOR] [MESSAGE] 204 | ``` 205 | 206 | For example: 207 | 208 | ```sh 209 | hyprctl notify -1 10000 "rgb(ff1ea3)" "Hello everyone!" 210 | ``` 211 | 212 | Icon of `-1` means "No icon" 213 | 214 | Color of `0` means "Default color for icon" 215 | 216 | Icon list: 217 | 218 | ```sh 219 | WARNING = 0 220 | INFO = 1 221 | HINT = 2 222 | ERROR = 3 223 | CONFUSED = 4 224 | OK = 5 225 | ``` 226 | 227 | Optionally, you can specify a font size of the notification like so: 228 | 229 | ```sh 230 | hyprctl notify -1 10000 "rgb(ff0000)" "fontsize:35 This text is big" 231 | ``` 232 | 233 | The default font-size is 13. 234 | 235 | ### dismissnotify 236 | 237 | Dismisses all or up to AMOUNT notifications. 238 | 239 | ```sh 240 | hyprctl dismissnotify # dismiss all notifications 241 | hyprctl dismissnotify 2 # dismiss the oldest 2 notifications 242 | hyprctl dismissnotify -1 # dismiss all notifications (same as no arguments) 243 | ``` 244 | 245 | ## Info 246 | 247 | ```plain 248 | version - prints the Hyprland version along with flags, commit and branch of build. 249 | monitors - lists active outputs with their properties, 'monitors all' lists active and inactive outputs 250 | workspaces - lists all workspaces with their properties 251 | activeworkspace - gets the active workspace and its properties 252 | workspacerules - gets the list of defined workspace rules 253 | clients - lists all windows with their properties 254 | devices - lists all connected keyboards and mice 255 | decorations [window] - lists all decorations and their info 256 | binds - lists all registered binds 257 | activewindow - gets the active window name and its properties 258 | layers - lists all the layers 259 | splash - prints the current random splash 260 | getoption [option] - gets the config option status (values) 261 | cursorpos - gets the current cursor position in global layout coordinates 262 | animations - gets the currently configured info about animations and beziers 263 | instances - lists all running instances of Hyprland with their info 264 | layouts - lists all layouts available (including from plugins) 265 | configerrors - lists all current config parsing errors 266 | rollinglog - prints tail of the log. Also supports -f/--follow option 267 | locked - prints whether the current session is locked. 268 | descriptions - returns a JSON with all config options, their descriptions and types. 269 | submap - prints the current submap the keybinds are in 270 | ``` 271 | 272 | For the getoption command, the option name should be written as 273 | `section:option`, e.g.: 274 | 275 | ```sh 276 | hyprctl getoption general:border_size 277 | 278 | # For nested sections: 279 | hyprctl getoption input:touchpad:disable_while_typing 280 | ``` 281 | 282 | See [Variables](../Variables) for sections and options you can use. 283 | 284 | ## Batch 285 | 286 | You can also use `--batch` to specify a batch of commands to execute. 287 | 288 | e.g. 289 | 290 | ```sh 291 | hyprctl --batch "keyword general:border_size 2 ; keyword general:gaps_out 20" 292 | ``` 293 | 294 | `;` separates the commands 295 | 296 | ## Flags 297 | 298 | You can specify flags for the request like this: 299 | 300 | ```sh 301 | hyprctl -j monitors 302 | ``` 303 | 304 | flag list: 305 | 306 | ```txt 307 | j -> output in JSON 308 | i -> select instance (id or index in hyprctl instances) 309 | ``` 310 | -------------------------------------------------------------------------------- /parser/data/sources/Workspace-Rules.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 8 3 | title: Workspace Rules 4 | --- 5 | 6 | You can set workspace rules to achieve workspace-specific behaviors. For 7 | instance, you can define a workspace where all windows are drawn without borders 8 | or gaps. 9 | 10 | For layout-specific rules, see the specific layout page. For example: 11 | [Master Layout->Workspace Rules](../Master-Layout#workspace-rules). 12 | 13 | ### Workspace selectors 14 | 15 | Workspaces that have already been created can be targeted by workspace 16 | selectors, e.g. `r[2-4] w[t1]`. 17 | 18 | Selectors have props separated by a space. No spaces are allowed inside props 19 | themselves. 20 | 21 | Props: 22 | 23 | - `r[A-B]` - ID range from A to B inclusive 24 | - `s[bool]` - Whether the workspace is special or not 25 | - `n[bool]`, `n[s:string]`, `n[e:string]` - named actions. `n[bool]` -> 26 | whether a workspace is a named workspace, `s` and `e` are starts and ends 27 | with respectively 28 | - `m[monitor]` - Monitor selector 29 | - `w[(flags)A-B]`, `w[(flags)X]` - Prop for window counts on the workspace. 30 | A-B is an inclusive range, X is a specific number. Flags can be omitted. 31 | It can be `t` for tiled-only, `f` for floating-only, `g` to count groups 32 | instead of windows, `v` to count only visible windows, and `p` to count 33 | only pinned windows. 34 | - `f[-1]`, `f[0]`, `f[1]`, `f[2]` - fullscreen state of the workspace. `-1`: no 35 | fullscreen, `0`: fullscreen, `1`: maximized, `2`, fullscreen without 36 | fullscreen state sent to the window. 37 | 38 | ### Syntax 39 | 40 | ```ini 41 | workspace = WORKSPACE, RULES 42 | ``` 43 | 44 | - WORKSPACE is a valid workspace identifier (see 45 | [Dispatchers->Workspaces](../Dispatchers#workspaces)). This field is 46 | mandatory. This _can be_ a workspace selector, but please note 47 | workspace selectors can only match _existing_ workspaces. 48 | - RULES is one (or more) rule(s) as described here in [rules](#rules). 49 | 50 | ### Examples 51 | 52 | ```ini 53 | workspace = name:myworkspace, gapsin:0, gapsout:0 54 | workspace = 3, rounding:false, bordersize:0 55 | workspace = w[tg1-4], shadow:false 56 | ``` 57 | 58 | #### Smart gaps 59 | 60 | To replicate "smart gaps" / "no gaps when only" from other WMs/Compositors, use this bad boy: 61 | 62 | ```ini 63 | workspace = w[tv1], gapsout:0, gapsin:0 64 | workspace = f[1], gapsout:0, gapsin:0 65 | windowrule = bordersize 0, floating:0, onworkspace:w[tv1] 66 | windowrule = rounding 0, floating:0, onworkspace:w[tv1] 67 | windowrule = bordersize 0, floating:0, onworkspace:f[1] 68 | windowrule = rounding 0, floating:0, onworkspace:f[1] 69 | ``` 70 | 71 | #### Smart gaps (ignoring special workspaces) 72 | 73 | You can combine workspace selectors for more fine-grained control, for example, to ignore special workspaces: 74 | 75 | ```ini 76 | workspace = w[tv1]s[false], gapsout:0, gapsin:0 77 | workspace = f[1]s[false], gapsout:0, gapsin:0 78 | windowrule = bordersize 0, floating:0, onworkspace:w[tv1]s[false] 79 | windowrule = rounding 0, floating:0, onworkspace:w[tv1]s[false] 80 | windowrule = bordersize 0, floating:0, onworkspace:f[1]s[false] 81 | windowrule = rounding 0, floating:0, onworkspace:f[1]s[false] 82 | ``` 83 | 84 | ## Rules 85 | 86 | | Rule | Description | type | 87 | | --- | --- | --- | 88 | | monitor:[m] | Binds a workspace to a monitor. See [syntax](#syntax) and [Monitors](../Monitors). | string | 89 | | default:[b] | Whether this workspace should be the default workspace for the given monitor | bool | 90 | | gapsin:[x] | Set the gaps between windows (equivalent to [General->gaps_in](../Variables#general)) | int | 91 | | gapsout:[x] | Set the gaps between windows and monitor edges (equivalent to [General->gaps_out](../Variables#general)) | int | 92 | | bordersize:[x] | Set the border size around windows (equivalent to [General->border_size](../Variables#general)) | int | 93 | | border:[b] | Whether to draw borders or not | bool | 94 | | shadow:[b] | Whether to draw shadows or not | bool | 95 | | rounding:[b] | Whether to draw rounded windows or not | bool | 96 | | decorate:[b] | Whether to draw window decorations or not | bool | 97 | | persistent:[b] | Keep this workspace alive even if empty and inactive | bool | 98 | | on-created-empty:[c] | A command to be executed once a workspace is created empty (i.e. not created by moving a window to it). See the [command syntax](../Dispatchers#executing-with-rules) | string | 99 | | defaultName:[s] | A default name for the workspace. | string | 100 | 101 | ### Example Rules 102 | 103 | ```ini 104 | workspace = 3, rounding:false, decorate:false 105 | workspace = name:coding, rounding:false, decorate:false, gapsin:0, gapsout:0, border:false, monitor:DP-1 106 | workspace = 8,bordersize:8 107 | workspace = name:Hello, monitor:DP-1, default:true 108 | workspace = name:gaming, monitor:desc:Chimei Innolux Corporation 0x150C, default:true 109 | workspace = 5, on-created-empty:[float] firefox 110 | workspace = special:scratchpad, on-created-empty:foot 111 | ``` 112 | -------------------------------------------------------------------------------- /parser/data/sources/XWayland.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 15 3 | title: XWayland 4 | --- 5 | 6 | XWayland is the bridging mechanism between legacy Xorg programs and Wayland 7 | compositors. 8 | 9 | ## HiDPI XWayland 10 | 11 | XWayland currently looks pixelated on HiDPI screens, due to Xorg's inability to 12 | scale. 13 | 14 | This problem is mitigated by the 15 | [`xwayland:force_zero_scaling`](../Variables/#xwayland) option, 16 | which forces XWayland windows not to be scaled. 17 | 18 | This will get rid of the pixelated look, but will not scale applications 19 | properly. To do this, each toolkit has its own mechanism. 20 | 21 | ```ini 22 | # change monitor to high resolution, the last argument is the scale factor 23 | monitor = , highres, auto, 2 24 | 25 | # unscale XWayland 26 | xwayland { 27 | force_zero_scaling = true 28 | } 29 | 30 | # toolkit-specific scale 31 | env = GDK_SCALE,2 32 | env = XCURSOR_SIZE,32 33 | ``` 34 | 35 | The GDK_SCALE variable won't conflict with Wayland-native GTK programs. 36 | 37 | {{< callout >}} 38 | 39 | XWayland HiDPI patches are no longer supported. Do not use them. 40 | 41 | {{}} 42 | 43 | ## Abstract Unix domain socket 44 | 45 | X11 applications use Unix domain sockets to communicate with XWayland. On Linux, libX11 prefers 46 | to use the abstract Unix domain socket. This type of socket uses a separate, abstract namespace that 47 | is independent of the host filesystem. This makes abstract sockets more flexible 48 | but harder to [isolate](https://github.com/hyprwm/Hyprland/pull/8874) 49 | for some kinds of sandboxes like Flatpak. However, removing the abstract socket 50 | has [potential](https://gitlab.gnome.org/GNOME/mutter/-/issues/1613) security 51 | and compatibility issues. 52 | 53 | Keeping that in mind, we add the [`xwayland:create_abstract_socket`](../Variables/#xwayland) option. 54 | When the abstract socket is disabled, only the regular Unix domain 55 | socket will be created. 56 | 57 | _\* Abstract Unix domain sockets are available only on Linux-based systems_ 58 | -------------------------------------------------------------------------------- /parser/data/sources/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 3 3 | title: Configuring 4 | sidebar: 5 | open: true 6 | --- 7 | 8 | This section is all about the configuring (aka ricing) of your Hyprland experience. 9 | It links to other pages where necessary, and will walk you through: 10 | 11 | - The config file 12 | - Every option and function in it 13 | - Extending functionality via scripts 14 | - Some uncommon tips and tricks (e.g. switching layouts, disabling keybinds on-demand, etc) 15 | 16 | It also contains some sample configurations you can take inspiration from. 17 | 18 | Start with [the Start Page](./Start). 19 | -------------------------------------------------------------------------------- /parser/data/variables.go: -------------------------------------------------------------------------------- 1 | package parser_data 2 | 3 | func FindVariableDefinitionInSection(sectionName, variableName string) *VariableDefinition { 4 | sec := FindSectionDefinitionByName(sectionName) 5 | if sec == nil { 6 | return nil 7 | } 8 | return sec.VariableDefinition(variableName) 9 | } 10 | 11 | type VariableDefinition struct { 12 | Name string 13 | Description string 14 | Type string 15 | Default string 16 | } 17 | 18 | func (v VariableDefinition) PrettyDefault() string { 19 | if v.Default == "[[Empty]]" { 20 | return "*(empty)*" 21 | } 22 | return v.Default 23 | } 24 | 25 | func (v VariableDefinition) GoType() string { 26 | switch v.Type { 27 | case "int": 28 | return "int" 29 | case "bool": 30 | return "bool" 31 | case "float", "floatvalue": 32 | return "float32" 33 | case "color": 34 | return "color.RGBA" 35 | case "vec2": 36 | return "[2]float32" 37 | case "MOD": 38 | return "[]ModKey" 39 | case "str", "string": 40 | return "string" 41 | case "gradient": 42 | return "GradientValue" 43 | case "font_weight": 44 | return "uint8" 45 | default: 46 | panic("unknown type: " + v.Type) 47 | } 48 | 49 | } 50 | 51 | 52 | 53 | func (v VariableDefinition) ParserTypeString() string { 54 | switch v.Type { 55 | case "int": 56 | return "Integer" 57 | case "bool": 58 | return "Bool" 59 | case "float": 60 | return "Float" 61 | case "color": 62 | return "Color" 63 | case "vec2": 64 | return "Vec2" 65 | case "MOD": 66 | return "Modmask" 67 | case "str", "string": 68 | return "String" 69 | case "gradient": 70 | return "Gradient" 71 | default: 72 | panic("unknown type: " + v.Type) 73 | } 74 | } 75 | 76 | func (v VariableDefinition) PascalCaseName() string { 77 | return toPascalCase(v.Name) 78 | } 79 | -------------------------------------------------------------------------------- /parser/decode.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | parser_data "github.com/hyprland-community/hyprls/parser/data" 8 | ) 9 | 10 | func (root Section) Decode() (Configuration, error) { 11 | config := Configuration{ 12 | CustomVariables: make(map[string]string, 0), 13 | } 14 | root.WalkCustomVariables(func(v *CustomVariable) { 15 | config.CustomVariables[v.Key] = v.ValueRaw 16 | }) 17 | 18 | for _, ass := range root.Assignments { 19 | def := parser_data.FindVariableDefinitionInSection("General", ass.Key) 20 | if def == nil { 21 | availableKeys := make([]string, 0) 22 | for _, v := range parser_data.FindSectionDefinitionByName("General").Variables { 23 | availableKeys = append(availableKeys, v.Name) 24 | } 25 | return Configuration{}, fmt.Errorf("unknown variable General > %s. Available keys are %v", ass.Key, availableKeys) 26 | } 27 | fmt.Printf("adding %s=%#v to .General", def.PascalCaseName(), ass.Value.GoValue()) 28 | setValue(&config, def.PascalCaseName(), ass.Value.GoValue()) 29 | } 30 | 31 | return config, nil 32 | } 33 | 34 | func setValue(obj any, field string, value any) { 35 | ref := reflect.ValueOf(obj) 36 | 37 | // if its a pointer, resolve its value 38 | if ref.Kind() == reflect.Ptr { 39 | ref = reflect.Indirect(ref) 40 | } 41 | 42 | if ref.Kind() == reflect.Interface { 43 | ref = ref.Elem() 44 | } 45 | 46 | // should double check we now have a struct (could still be anything) 47 | if ref.Kind() != reflect.Struct { 48 | panic("cannot setValue on a non-struct") 49 | } 50 | 51 | prop := ref.FieldByName(field) 52 | prop.Set(reflect.ValueOf(value)) 53 | } 54 | -------------------------------------------------------------------------------- /parser/decode_test.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/davecgh/go-spew/spew" 7 | ) 8 | 9 | func TestHighLevelParse(t *testing.T) { 10 | parsed, err := Parse(fixture) 11 | if err != nil { 12 | t.Errorf("Error while parsing: %s", err) 13 | } 14 | 15 | config, err := parsed.Decode() 16 | if err != nil { 17 | t.Errorf("Error while decoding: %s", err) 18 | return 19 | } else { 20 | spew.Dump(config) 21 | } 22 | 23 | t.Errorf("TestHighLevelParse not implemented") 24 | } 25 | -------------------------------------------------------------------------------- /parser/fixtures/empty.hl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyprland-community/hyprls/04c579ad66e7d1e841896fb28ef8b7bfcf67af40/parser/fixtures/empty.hl -------------------------------------------------------------------------------- /parser/fixtures/test.hl: -------------------------------------------------------------------------------- 1 | ####################################################################################### 2 | # AUTOGENERATED HYPR CONFIG. 3 | # PLEASE USE THE CONFIG PROVIDED IN THE GIT REPO /examples/hypr.conf AND EDIT IT, 4 | # OR EDIT THIS ONE ACCORDING TO THE WIKI INSTRUCTIONS. 5 | # ####################################################################################### 6 | 7 | # 8 | # Please note not all available settings / options are set here. 9 | # For a full list, see the wiki 10 | # 11 | 12 | autogenerated = 0 # remove this line to remove the warning 13 | 14 | 15 | misc { 16 | # force_hypr_chan = true 17 | enable_swallow = true 18 | swallow_regex = ^kitty$ 19 | } 20 | 21 | # See https://wiki.hyprland.org/Configuring/Monitors/ 22 | source = ~/.config/hypr/monitors.conf 23 | monitor=,preferred,auto,1 24 | 25 | # See https://wiki.hyprland.org/Configuring/Keywords/ for more 26 | 27 | # Execute your favorite apps at launch 28 | exec-once = hyprpm reload -n & ~/.config/waybar/spotify-receiver & waybar & fcitx5 & discord & spotify & caprine & element-desktop & firefox & ckb-next --background & /usr/lib/polkit-kde-authentication-agent-1 & bash -c 'killall hyprpaper; hyprpaper' & 29 | 30 | 31 | 32 | # Source a file (multi-file configs) 33 | # source = ~/.config/hypr/myColors.conf 34 | 35 | # Some default env vars. 36 | env = XCURSOR_SIZE,24 37 | 38 | # For all categories, see https://wiki.hyprland.org/Configuring/Variables/ 39 | input { 40 | kb_layout = fr 41 | kb_variant = 42 | kb_model = 43 | kb_options = compose:rwin 44 | kb_rules = 45 | 46 | follow_mouse = 1 47 | 48 | touchpad { 49 | natural_scroll = yes 50 | scroll_factor = 0.2 51 | } 52 | 53 | 54 | sensitivity = 0 # -1.0 - 1.0, 0 means no modification. 55 | } 56 | 57 | general { 58 | # See https://wiki.hyprland.org/Configuring/Variables/ for more 59 | 60 | gaps_in = 5 61 | gaps_out = 20 62 | border_size = 2 63 | col.active_border = rgba(ffc93391) rgb(ff0000) 45deg 64 | col.inactive_border = rgba(300adbab) 65 | 66 | layout = dwindle 67 | } 68 | 69 | decoration { 70 | # See https://wiki.hyprland.org/Configuring/Variables/ for more 71 | 72 | rounding = 10 73 | 74 | blur { 75 | enabled = true 76 | size = 10 77 | ignore_opacity = true 78 | xray = true 79 | passes = 2 80 | # noise = 0.2 81 | } 82 | 83 | 84 | active_opacity = 0.9 85 | inactive_opacity = 0.7 86 | 87 | # drop_shadow = yes 88 | # shadow_range = 4 89 | # shadow_render_power = 3 90 | # col.shadow = rgba(1a1a1aee) 91 | } 92 | 93 | animations { 94 | enabled = yes 95 | 96 | # Some default animations, see https://wiki.hyprland.org/Configuring/Animations/ for more 97 | 98 | bezier = myBezier, 0.05, 0.9, 0.1, 1.05 99 | 100 | animation = windows, 1, 7, myBezier 101 | animation = windowsOut, 1, 7, default, popin 80% 102 | animation = border, 1, 10, default 103 | animation = borderangle, 1, 8, default 104 | animation = fade, 1, 7, default 105 | animation = workspaces, 1, 6, default 106 | } 107 | 108 | dwindle { 109 | # See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more 110 | pseudotile = yes # master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below 111 | preserve_split = yes # you probably want this 112 | force_split = 2 113 | } 114 | 115 | master { 116 | # See https://wiki.hyprland.org/Configuring/Master-Layout/ for more 117 | new_is_master = true 118 | } 119 | 120 | gestures { 121 | # See https://wiki.hyprland.org/Configuring/Variables/ for more 122 | workspace_swipe = on 123 | workspace_swipe_distance = 3000 124 | } 125 | 126 | # Example per-device config 127 | # See https://wiki.hyprland.org/Configuring/Keywords/#executing for more 128 | # device:epic-mouse-v1 { 129 | # sensitivity = -0.5 130 | # } 131 | 132 | # Example windowrule v1 133 | # windowrule = float, ^(kitty)$ 134 | # Example windowrule v2 135 | # windowrulev2 = float,class:^(kitty)$,title:^(kitty)$ 136 | # See https://wiki.hyprland.org/Configuring/Window-Rules/ for more 137 | 138 | 139 | # See https://wiki.hyprland.org/Configuring/Keywords/ for more 140 | $mainMod = SUPER 141 | $here = $HOME/.config/hypr 142 | 143 | # Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more 144 | bind = $mainMod SHIFT, Return, exec, warp-terminal 145 | bind = $mainMod, Return, exec, kitty 146 | bind = $mainMod, Q, killactive, 147 | bind = $mainMod SHIFT, C, exit, 148 | bind = $mainMod, E, exec, neovide 149 | bind = $mainMod, B, exec, firefox 150 | bind = $mainMod, P, exec, ~/.config/rofi/query 151 | bind = $mainMod SHIFT, Space, togglefloating, 152 | bind = $mainMod, D, exec, ~/.config/rofi/launchers/type-3/launcher.sh 153 | # bindr = $mainMod, Super_L, exec, pkill rofi || ~/.config/rofi/launchers/type-3/launcher.sh 154 | bind = $mainMod, Y, exec, rofimoji 155 | 156 | bind = $mainMod, V, togglesplit, # dwindle 157 | bind = $mainMod, lock, exec, waylock 158 | 159 | # Upgrade system 160 | bind = $mainMod, U, exec, [workspace 6] kitty --hold fish -c up 161 | 162 | # Move focus with mainMod + arrow keys 163 | bind = $mainMod, left, movefocus, l 164 | bind = $mainMod, h, movefocus, l 165 | bind = $mainMod, right, movefocus, r 166 | bind = $mainMod, l, movefocus, r 167 | bind = $mainMod, up, movefocus, u 168 | bind = $mainMod, k, movefocus, u 169 | bind = $mainMod, down, movefocus, d 170 | bind = $mainMod, j, movefocus, d 171 | 172 | # Switch workspaces with mainMod + [0-9] 173 | bind = $mainMod, ampersand, workspace, 1 174 | bind = $mainMod, eacute, workspace, 2 175 | bind = $mainMod, quotedbl, workspace, 3 176 | bind = $mainMod, apostrophe, workspace, 4 177 | bind = $mainMod, parenleft, workspace, 5 178 | bind = $mainMod, minus, workspace, 6 179 | bind = $mainMod, egrave, workspace, 7 180 | bind = $mainMod, underscore, workspace, 8 181 | bind = $mainMod, ccedilla, workspace, 9 182 | bind = $mainMod, agrave, workspace, 10 183 | 184 | # Move active window to a workspace with mainMod + SHIFT + [0-9] 185 | bind = $mainMod SHIFT, ampersand, movetoworkspace, 1 186 | bind = $mainMod SHIFT, eacute, movetoworkspace, 2 187 | bind = $mainMod SHIFT, quotedbl, movetoworkspace, 3 188 | bind = $mainMod SHIFT, apostrophe, movetoworkspace, 4 189 | bind = $mainMod SHIFT, parenleft, movetoworkspace, 5 190 | bind = $mainMod SHIFT, minus, movetoworkspace, 6 191 | bind = $mainMod SHIFT, egrave, movetoworkspace, 7 192 | bind = $mainMod SHIFT, underscore, movetoworkspace, 8 193 | bind = $mainMod SHIFT, ccedilla, movetoworkspace, 9 194 | bind = $mainMod SHIFT, agrave, movetoworkspace, 10 195 | 196 | # Move active workspace to other monitor 197 | bind = $mainMod CTRL, left, movecurrentworkspacetomonitor, l 198 | bind = $mainMod CTRL, right, movecurrentworkspacetomonitor, r 199 | 200 | # Scroll through existing workspaces with mainMod + scroll 201 | bind = $mainMod, mouse_down, workspace, e+1 202 | bind = $mainMod, mouse_up, workspace, e-1 203 | 204 | # Move/resize windows with mainMod + LMB/RMB and dragging 205 | bindm = $mainMod, mouse:272, movewindow 206 | bindm = $mainMod, mouse:273, resizewindow 207 | 208 | # Tabbed (grouped) windows 209 | bind = $mainMod, T, togglegroup 210 | bind = $mainMod SHIFT, tab, changegroupactive, b 211 | bind = $mainMod, tab, changegroupactive, f 212 | 213 | # Media keys 214 | binde = $mainMod, xf86monbrightnessup, exec, brillo -A 5 215 | binde = $mainMod, xf86monbrightnessdown, exec, brillo -U 5 216 | binde = , xf86monbrightnessup, exec, brillo -A 10 217 | binde = , xf86monbrightnessdown, exec, brillo -U 10 218 | binde = SHIFT, xf86monbrightnessup, exec, brillo -A 20 219 | binde = SHIFT, xf86monbrightnessdown, exec, brillo -U 20 220 | 221 | binde = , xf86audioraisevolume, exec, $here/volume_brightness.sh volume_up 222 | binde = , xf86audiolowervolume, exec, $here/volume_brightness.sh volume_down 223 | binde = , xf86audiomute, exec, $here/volume_brightness.sh volume_mute 224 | 225 | bind = , xf86audionext, exec, playerctl next 226 | bind = , xf86audioprev, exec, playerctl previous 227 | bind = , xf86audioplay, exec, playerctl play-pause 228 | bind = , xf86audiostop, exec, rofi-spotify --like-current 229 | bind = SHIFT, xf86audiostop, exec, rofi-spotify --add-to-playlist 230 | 231 | bind = , print, exec, hyprshot -m output 232 | bind = SHIFT, print, exec, hyprshot -m region 233 | 234 | bind = $mainMod ALT, u, exec, rofimoji -a unicode 235 | 236 | bind = $mainMod, F, fullscreen 237 | bind = $mainMod SHIFT, F, fullscreen, 1 238 | 239 | # Scratchpad 240 | bind = $mainMod SHIFT, equal, movetoworkspace, special 241 | bind = $mainMod, equal, togglespecialworkspace 242 | 243 | # Overview 244 | # bind = $mainMod, A, exec, hyprctl dispatch overview:toggle # (plugin: https://github.com/KZDKM/Hyprspace) 245 | 246 | bind = $mainMod, A, hyprexpo:expo, toggle # can be: toggle, off/disable or on/enable 247 | 248 | plugin { 249 | hyprexpo { 250 | columns = 3 251 | gap_size = 5 252 | bg_col = rgb(111111) 253 | workspace_method = first 1 # [center/first] [workspace] e.g. first 1 or center m+1 254 | 255 | enable_gesture = true # laptop touchpad, 4 fingers 256 | gesture_distance = 300 # how far is the "max" 257 | gesture_positive = true # positive = swipe down. Negative = swipe up. 258 | } 259 | } 260 | 261 | windowrulev2 = opacity 0.8 override 0.6 override,class:(kitty) 262 | windowrulev2 = opacity 0.8 override 0.6 override,class:(neovide) 263 | windowrulev2 = opacity 1 override 1 override,class:(obs) 264 | windowrulev2 = tile,class:(dev.warp.Warp) 265 | 266 | # Assigning apps to workspaces 267 | windowrulev2 = workspace 9 silent,class:(Spotify) 268 | windowrulev2 = workspace 10 silent,class:(Element) 269 | windowrulev2 = group set,class:(Element) 270 | windowrulev2 = workspace 10 silent,class:(Caprine) 271 | windowrulev2 = group set,class:(Caprine) 272 | windowrulev2 = workspace 10 silent,class:(discord) 273 | windowrulev2 = group set,class:(discord) 274 | windowrulev2 = workspace 3 silent,class:(^MATLAB),title:(^Figure \d: ) 275 | windowrulev2 = workspace 3 silent,class:(Backend),title:(\[dev\]) 276 | 277 | windowrulev2 = stayfocused,class:(Rofi) 278 | 279 | # Floating windows that shouldn't be 280 | windowrulev2 = tile,class:(qemu-system-x86_64) 281 | windowrulev2 = tile,class:(Pianoteq),title:(^Pianoteq) 282 | windowrulev2 = tile,class:(^MATLAB),title:(^Figure \d: ) 283 | 284 | # kdwallet popups should steal focus 285 | windowrulev2 = stayfocused,class:(kwalletd5),title:(^KDE Wallet Service$) 286 | -------------------------------------------------------------------------------- /parser/lowlevel_test.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | _ "embed" 5 | "encoding/json" 6 | "os" 7 | // "strings" 8 | "testing" 9 | 10 | // "github.com/andreyvit/diff" 11 | ) 12 | 13 | //go:embed fixtures/test.hl 14 | var fixture string 15 | 16 | //go:embed fixtures/test.json 17 | var fixtureResultSnapshot string 18 | 19 | func TestLowlevelParse(t *testing.T) { 20 | parsed, err := Parse(fixture) 21 | if err != nil { 22 | t.Errorf("Error while parsing: %s", err) 23 | } 24 | 25 | contents, _ := json.MarshalIndent(parsed, "", " ") 26 | os.WriteFile("fixtures/test.json", contents, 0644) 27 | // if strings.TrimSpace(fixtureResultSnapshot) == "update" { 28 | // os.WriteFile("fixtures/test.json", contents, 0644) 29 | // } else { 30 | // if string(contents) != fixtureResultSnapshot { 31 | // t.Errorf("Parsed result does not match snapshot:\n%v", diff.LineDiff(string(contents), fixtureResultSnapshot)) 32 | // } 33 | // } 34 | } 35 | -------------------------------------------------------------------------------- /parser/utils.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | func not[T any](f func(T) bool) func(T) bool { 4 | return func(x T) bool { 5 | return !f(x) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:recommended", "schedule:weekly"], 4 | "commitMessagePrefix": "⬆️ ", 5 | "rangeStrategy": "bump", 6 | "lockFileMaintenance": { 7 | "enabled": true, 8 | "automerge": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? ( 2 | let 3 | inherit (builtins) fetchTree fromJSON readFile; 4 | inherit ((fromJSON (readFile ./flake.lock)).nodes) nixpkgs gomod2nix; 5 | in 6 | import (fetchTree nixpkgs.locked) { 7 | overlays = [ 8 | (import "${fetchTree gomod2nix.locked}/overlay.nix") 9 | ]; 10 | } 11 | ) 12 | , mkGoEnv ? pkgs.mkGoEnv 13 | , gomod2nix ? pkgs.gomod2nix 14 | }: 15 | 16 | let 17 | goEnv = mkGoEnv { pwd = ./.; }; 18 | in 19 | pkgs.mkShell { 20 | packages = [ 21 | goEnv 22 | gomod2nix 23 | ] ++ (with pkgs; [ 24 | just 25 | jq 26 | moreutils # sponge 27 | bun 28 | ]); 29 | } 30 | -------------------------------------------------------------------------------- /state.go: -------------------------------------------------------------------------------- 1 | package hyprls 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "strings" 7 | 8 | "github.com/hyprland-community/hyprls/parser" 9 | parser_data "github.com/hyprland-community/hyprls/parser/data" 10 | "go.lsp.dev/protocol" 11 | "go.uber.org/zap" 12 | ) 13 | 14 | var logger *zap.Logger 15 | 16 | var openedFiles = make(map[protocol.URI]string) 17 | 18 | type state struct { 19 | } 20 | 21 | func (h Handler) state(ctx context.Context) state { 22 | return ctx.Value("state").(state) 23 | } 24 | 25 | func parse(uri protocol.URI) (parser.Section, error) { 26 | contents, err := file(uri) 27 | if err != nil { 28 | return parser.Section{}, err 29 | } 30 | 31 | return parser.Parse(contents) 32 | } 33 | 34 | func currentSection(root parser.Section, position protocol.Position) *parser.Section { 35 | if !within(root.LSPRange(), position) { 36 | return nil 37 | } 38 | 39 | for _, section := range root.Subsections { 40 | sec := currentSection(section, position) 41 | if sec != nil { 42 | return sec 43 | } 44 | } 45 | 46 | return &root 47 | } 48 | 49 | func currentAssignment(root parser.Section, position protocol.Position) *parser_data.VariableDefinition { 50 | if !within(root.LSPRange(), position) { 51 | return nil 52 | } 53 | 54 | for _, assignment := range root.Assignments { 55 | if assignment.Position.Line == int(position.Line) { 56 | return parser_data.FindVariableDefinitionInSection(root.Name, assignment.Key) 57 | } 58 | } 59 | 60 | return nil 61 | } 62 | 63 | func within(rang protocol.Range, position protocol.Position) bool { 64 | if position.Line < rang.Start.Line || position.Line > rang.End.Line { 65 | return false 66 | } 67 | 68 | if position.Line == rang.Start.Line && position.Character < rang.Start.Character { 69 | return false 70 | } 71 | 72 | if position.Line == rang.End.Line && position.Character > rang.End.Character { 73 | return false 74 | } 75 | 76 | return true 77 | } 78 | 79 | func file(uri protocol.URI) (string, error) { 80 | if contents, ok := openedFiles[uri]; ok { 81 | return contents, nil 82 | } 83 | 84 | contents, err := os.ReadFile(uri.Filename()) 85 | if err != nil { 86 | return "", err 87 | } 88 | 89 | openedFiles[uri] = string(contents) 90 | return string(contents), nil 91 | } 92 | 93 | func currentLine(uri protocol.URI, position protocol.Position) (string, error) { 94 | contents, err := file(uri) 95 | if err != nil { 96 | return "", err 97 | } 98 | 99 | lines := strings.Split(contents, "\n") 100 | return lines[position.Line], nil 101 | } 102 | -------------------------------------------------------------------------------- /symbols.go: -------------------------------------------------------------------------------- 1 | package hyprls 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/hyprland-community/hyprls/parser" 8 | "go.lsp.dev/protocol" 9 | ) 10 | 11 | func (h Handler) DocumentSymbol(ctx context.Context, params *protocol.DocumentSymbolParams) ([]interface{}, error) { 12 | document, err := parse(params.TextDocument.URI) 13 | if err != nil { 14 | return nil, fmt.Errorf("while parsing: %w", err) 15 | } 16 | symbols := make([]interface{}, 0) 17 | for _, symb := range gatherAllSymbols(document) { 18 | symbols = append(symbols, &symb) 19 | } 20 | return symbols, nil 21 | } 22 | 23 | func gatherAllSymbols(root parser.Section) []protocol.DocumentSymbol { 24 | symbols := make([]protocol.DocumentSymbol, 0) 25 | for _, variable := range root.Assignments { 26 | symbols = append(symbols, protocol.DocumentSymbol{ 27 | Name: variable.Key, 28 | Kind: variable.Value.Kind.LSPSymbol(), 29 | Detail: variable.ValueRaw, 30 | Range: collapsedRange(variable.Position.LSP()), 31 | SelectionRange: collapsedRange(variable.Position.LSP()), 32 | }) 33 | } 34 | for _, customVar := range root.Variables { 35 | symbols = append(symbols, protocol.DocumentSymbol{ 36 | Name: "$" + customVar.Key, 37 | Kind: protocol.SymbolKindVariable, 38 | Detail: customVar.ValueRaw, 39 | Range: collapsedRange(customVar.Position.LSP()), 40 | SelectionRange: collapsedRange(customVar.Position.LSP()), 41 | }) 42 | } 43 | for _, section := range root.Subsections { 44 | symbols = append(symbols, protocol.DocumentSymbol{ 45 | Name: section.Name, 46 | Kind: protocol.SymbolKindNamespace, 47 | Range: section.LSPRange(), 48 | SelectionRange: section.LSPRange(), 49 | Children: gatherAllSymbols(section), 50 | }) 51 | } 52 | return symbols 53 | } 54 | -------------------------------------------------------------------------------- /sync.go: -------------------------------------------------------------------------------- 1 | package hyprls 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "go.lsp.dev/protocol" 8 | "go.uber.org/zap" 9 | ) 10 | 11 | 12 | func (h Handler) DidChange(ctx context.Context, params *protocol.DidChangeTextDocumentParams) error { 13 | logger.Debug("LSP:DidChange", zap.Any("params", params)) 14 | openedFiles[params.TextDocument.URI] = params.ContentChanges[len(params.ContentChanges)-1].Text 15 | return nil 16 | } 17 | 18 | func (h Handler) DidClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error { 19 | delete(openedFiles, params.TextDocument.URI) 20 | return nil 21 | } 22 | 23 | func (h Handler) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error { 24 | file(params.TextDocument.URI) 25 | return nil 26 | } 27 | 28 | func (h Handler) DidSave(ctx context.Context, params *protocol.DidSaveTextDocumentParams) error { 29 | return errors.New("unimplemented") 30 | } 31 | -------------------------------------------------------------------------------- /unimplemented.go: -------------------------------------------------------------------------------- 1 | package hyprls 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "go.lsp.dev/protocol" 8 | ) 9 | 10 | func (h Handler) Definition(ctx context.Context, params *protocol.DefinitionParams) ([]protocol.Location, error) { 11 | return []protocol.Location{}, errors.New("unimplemented") 12 | } 13 | 14 | func (h Handler) WorkDoneProgressCancel(ctx context.Context, params *protocol.WorkDoneProgressCancelParams) error { 15 | return errors.New("unimplemented") 16 | } 17 | 18 | func (h Handler) LogTrace(ctx context.Context, params *protocol.LogTraceParams) error { 19 | return errors.New("unimplemented") 20 | } 21 | 22 | func (h Handler) SetTrace(ctx context.Context, params *protocol.SetTraceParams) error { 23 | return errors.New("unimplemented") 24 | } 25 | 26 | func (h Handler) CodeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) { 27 | return nil, errors.New("unimplemented") 28 | } 29 | 30 | func (h Handler) CodeLens(ctx context.Context, params *protocol.CodeLensParams) ([]protocol.CodeLens, error) { 31 | return nil, errors.New("unimplemented") 32 | } 33 | 34 | func (h Handler) CodeLensResolve(ctx context.Context, params *protocol.CodeLens) (*protocol.CodeLens, error) { 35 | return nil, errors.New("unimplemented") 36 | } 37 | 38 | func (h Handler) Declaration(ctx context.Context, params *protocol.DeclarationParams) ([]protocol.Location, error) { 39 | return nil, errors.New("unimplemented") 40 | } 41 | 42 | func (h Handler) DidChangeConfiguration(ctx context.Context, params *protocol.DidChangeConfigurationParams) error { 43 | return errors.New("unimplemented") 44 | } 45 | 46 | func (h Handler) DidChangeWatchedFiles(ctx context.Context, params *protocol.DidChangeWatchedFilesParams) error { 47 | return errors.New("unimplemented") 48 | } 49 | 50 | func (h Handler) DidChangeWorkspaceFolders(ctx context.Context, params *protocol.DidChangeWorkspaceFoldersParams) error { 51 | return errors.New("unimplemented") 52 | } 53 | 54 | func (h Handler) DocumentHighlight(ctx context.Context, params *protocol.DocumentHighlightParams) ([]protocol.DocumentHighlight, error) { 55 | return nil, errors.New("unimplemented") 56 | } 57 | 58 | func (h Handler) DocumentLink(ctx context.Context, params *protocol.DocumentLinkParams) ([]protocol.DocumentLink, error) { 59 | return nil, errors.New("unimplemented") 60 | } 61 | 62 | func (h Handler) DocumentLinkResolve(ctx context.Context, params *protocol.DocumentLink) (*protocol.DocumentLink, error) { 63 | return nil, errors.New("unimplemented") 64 | } 65 | 66 | func (h Handler) ExecuteCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) { 67 | return nil, errors.New("unimplemented") 68 | } 69 | 70 | func (h Handler) FoldingRanges(ctx context.Context, params *protocol.FoldingRangeParams) ([]protocol.FoldingRange, error) { 71 | return nil, errors.New("unimplemented") 72 | } 73 | 74 | func (h Handler) Formatting(ctx context.Context, params *protocol.DocumentFormattingParams) ([]protocol.TextEdit, error) { 75 | return nil, errors.New("unimplemented") 76 | } 77 | 78 | func (h Handler) Implementation(ctx context.Context, params *protocol.ImplementationParams) ([]protocol.Location, error) { 79 | return nil, errors.New("unimplemented") 80 | } 81 | 82 | func (h Handler) OnTypeFormatting(ctx context.Context, params *protocol.DocumentOnTypeFormattingParams) ([]protocol.TextEdit, error) { 83 | return nil, errors.New("unimplemented") 84 | } 85 | 86 | func (h Handler) PrepareRename(ctx context.Context, params *protocol.PrepareRenameParams) (*protocol.Range, error) { 87 | return nil, errors.New("unimplemented") 88 | } 89 | 90 | func (h Handler) RangeFormatting(ctx context.Context, params *protocol.DocumentRangeFormattingParams) ([]protocol.TextEdit, error) { 91 | return nil, errors.New("unimplemented") 92 | } 93 | 94 | func (h Handler) References(ctx context.Context, params *protocol.ReferenceParams) ([]protocol.Location, error) { 95 | return nil, errors.New("unimplemented") 96 | } 97 | 98 | func (h Handler) Rename(ctx context.Context, params *protocol.RenameParams) (*protocol.WorkspaceEdit, error) { 99 | return nil, errors.New("unimplemented") 100 | } 101 | 102 | func (h Handler) SignatureHelp(ctx context.Context, params *protocol.SignatureHelpParams) (*protocol.SignatureHelp, error) { 103 | return nil, errors.New("unimplemented") 104 | } 105 | 106 | func (h Handler) Symbols(ctx context.Context, params *protocol.WorkspaceSymbolParams) ([]protocol.SymbolInformation, error) { 107 | return nil, errors.New("unimplemented") 108 | } 109 | 110 | func (h Handler) TypeDefinition(ctx context.Context, params *protocol.TypeDefinitionParams) ([]protocol.Location, error) { 111 | return nil, errors.New("unimplemented") 112 | } 113 | 114 | func (h Handler) WillSave(ctx context.Context, params *protocol.WillSaveTextDocumentParams) error { 115 | return errors.New("unimplemented") 116 | } 117 | 118 | func (h Handler) WillSaveWaitUntil(ctx context.Context, params *protocol.WillSaveTextDocumentParams) ([]protocol.TextEdit, error) { 119 | return nil, errors.New("unimplemented") 120 | } 121 | 122 | func (h Handler) ShowDocument(ctx context.Context, params *protocol.ShowDocumentParams) (*protocol.ShowDocumentResult, error) { 123 | return nil, errors.New("unimplemented") 124 | } 125 | 126 | func (h Handler) WillCreateFiles(ctx context.Context, params *protocol.CreateFilesParams) (*protocol.WorkspaceEdit, error) { 127 | return nil, errors.New("unimplemented") 128 | } 129 | 130 | func (h Handler) DidCreateFiles(ctx context.Context, params *protocol.CreateFilesParams) error { 131 | return errors.New("unimplemented") 132 | } 133 | 134 | func (h Handler) WillRenameFiles(ctx context.Context, params *protocol.RenameFilesParams) (*protocol.WorkspaceEdit, error) { 135 | return nil, errors.New("unimplemented") 136 | } 137 | 138 | func (h Handler) DidRenameFiles(ctx context.Context, params *protocol.RenameFilesParams) error { 139 | return errors.New("unimplemented") 140 | } 141 | 142 | func (h Handler) WillDeleteFiles(ctx context.Context, params *protocol.DeleteFilesParams) (*protocol.WorkspaceEdit, error) { 143 | return nil, errors.New("unimplemented") 144 | } 145 | 146 | func (h Handler) DidDeleteFiles(ctx context.Context, params *protocol.DeleteFilesParams) error { 147 | return errors.New("unimplemented") 148 | } 149 | 150 | func (h Handler) CodeLensRefresh(ctx context.Context) error { 151 | return errors.New("unimplemented") 152 | } 153 | 154 | func (h Handler) PrepareCallHierarchy(ctx context.Context, params *protocol.CallHierarchyPrepareParams) ([]protocol.CallHierarchyItem, error) { 155 | return nil, errors.New("unimplemented") 156 | } 157 | 158 | func (h Handler) IncomingCalls(ctx context.Context, params *protocol.CallHierarchyIncomingCallsParams) ([]protocol.CallHierarchyIncomingCall, error) { 159 | return nil, errors.New("unimplemented") 160 | } 161 | 162 | func (h Handler) OutgoingCalls(ctx context.Context, params *protocol.CallHierarchyOutgoingCallsParams) ([]protocol.CallHierarchyOutgoingCall, error) { 163 | return nil, errors.New("unimplemented") 164 | } 165 | 166 | func (h Handler) SemanticTokensFull(ctx context.Context, params *protocol.SemanticTokensParams) (*protocol.SemanticTokens, error) { 167 | return nil, errors.New("unimplemented") 168 | } 169 | 170 | func (h Handler) SemanticTokensFullDelta(ctx context.Context, params *protocol.SemanticTokensDeltaParams) (interface{}, error) { 171 | return nil, errors.New("unimplemented") 172 | } 173 | 174 | func (h Handler) SemanticTokensRange(ctx context.Context, params *protocol.SemanticTokensRangeParams) (*protocol.SemanticTokens, error) { 175 | return nil, errors.New("unimplemented") 176 | } 177 | 178 | func (h Handler) SemanticTokensRefresh(ctx context.Context) error { 179 | return errors.New("unimplemented") 180 | } 181 | 182 | func (h Handler) LinkedEditingRange(ctx context.Context, params *protocol.LinkedEditingRangeParams) (*protocol.LinkedEditingRanges, error) { 183 | return nil, errors.New("unimplemented") 184 | } 185 | 186 | func (h Handler) Moniker(ctx context.Context, params *protocol.MonikerParams) ([]protocol.Moniker, error) { 187 | return nil, errors.New("unimplemented") 188 | } 189 | 190 | func (h Handler) Request(ctx context.Context, method string, params interface{}) (interface{}, error) { 191 | return nil, errors.New("unimplemented") 192 | } 193 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package hyprls 2 | 3 | import "go.lsp.dev/protocol" 4 | 5 | func collapsedRange(position protocol.Position) protocol.Range { 6 | return protocol.Range{ 7 | Start: position, 8 | End: position, 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /vscode-extension-pack/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/node 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=node 3 | 4 | ### Node ### 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | .pnpm-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # Snowpack dependency directory (https://snowpack.dev/) 50 | web_modules/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Optional stylelint cache 62 | .stylelintcache 63 | 64 | # Microbundle cache 65 | .rpt2_cache/ 66 | .rts2_cache_cjs/ 67 | .rts2_cache_es/ 68 | .rts2_cache_umd/ 69 | 70 | # Optional REPL history 71 | .node_repl_history 72 | 73 | # Output of 'npm pack' 74 | *.tgz 75 | 76 | # Yarn Integrity file 77 | .yarn-integrity 78 | 79 | # dotenv environment variable files 80 | .env 81 | .env.development.local 82 | .env.test.local 83 | .env.production.local 84 | .env.local 85 | 86 | # parcel-bundler cache (https://parceljs.org/) 87 | .cache 88 | .parcel-cache 89 | 90 | # Next.js build output 91 | .next 92 | out 93 | 94 | # Nuxt.js build / generate output 95 | .nuxt 96 | dist 97 | 98 | # Gatsby files 99 | .cache/ 100 | # Comment in the public line in if your project uses Gatsby and not Next.js 101 | # https://nextjs.org/blog/next-9-1#public-directory-support 102 | # public 103 | 104 | # vuepress build output 105 | .vuepress/dist 106 | 107 | # vuepress v2.x temp and cache directory 108 | .temp 109 | 110 | # Docusaurus cache and generated files 111 | .docusaurus 112 | 113 | # Serverless directories 114 | .serverless/ 115 | 116 | # FuseBox cache 117 | .fusebox/ 118 | 119 | # DynamoDB Local files 120 | .dynamodb/ 121 | 122 | # TernJS port file 123 | .tern-port 124 | 125 | # Stores VSCode versions used for testing VSCode extensions 126 | .vscode-test 127 | 128 | # yarn v2 129 | .yarn/cache 130 | .yarn/unplugged 131 | .yarn/build-state.yml 132 | .yarn/install-state.gz 133 | .pnp.* 134 | 135 | ### Node Patch ### 136 | # Serverless Webpack directories 137 | .webpack/ 138 | 139 | # Optional stylelint cache 140 | 141 | # SvelteKit build / generate output 142 | .svelte-kit 143 | 144 | # End of https://www.toptal.com/developers/gitignore/api/node 145 | -------------------------------------------------------------------------------- /vscode-extension-pack/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ] 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /vscode-extension-pack/.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | .gitignore 4 | vsc-extension-quickstart.md 5 | -------------------------------------------------------------------------------- /vscode-extension-pack/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "hyprland" extension pack will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [Unreleased] 8 | 9 | - Initial release 10 | -------------------------------------------------------------------------------- /vscode-extension-pack/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice (including the next 11 | paragraph) shall be included in all copies or substantial portions of the 12 | Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 17 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 19 | OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /vscode-extension-pack/README.md: -------------------------------------------------------------------------------- 1 | # hyprland extension pack 2 | 3 | Installs syntax highlighting and LSP support for [Hyprland](https://hyprland.org) config files 4 | 5 | -------------------------------------------------------------------------------- /vscode-extension-pack/bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyprland-community/hyprls/04c579ad66e7d1e841896fb28ef8b7bfcf67af40/vscode-extension-pack/bun.lockb -------------------------------------------------------------------------------- /vscode-extension-pack/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyprland-community/hyprls/04c579ad66e7d1e841896fb28ef8b7bfcf67af40/vscode-extension-pack/icon.png -------------------------------------------------------------------------------- /vscode-extension-pack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hyprland", 3 | "displayName": "Hyprland", 4 | "description": "Extension pack for syntax highlighting and rich IDE support in Hyprland configuration files", 5 | "version": "0.1.2", 6 | "engines": { 7 | "vscode": "^1.100.2" 8 | }, 9 | "categories": [ 10 | "Extension Packs" 11 | ], 12 | "extensionPack": [ 13 | "ewen-lbh.vscode-hyprls", 14 | "fireblast.hyprlang-vscode" 15 | ], 16 | "devDependencies": { 17 | "@vscode/vsce": "^3.4.2" 18 | }, 19 | "repository": "https://github.com/ewen-lbh/hyprls/tree/main/vscode-extension-pack", 20 | "license": "MIT", 21 | "publisher": "ewen-lbh", 22 | "icon": "icon.png" 23 | } 24 | -------------------------------------------------------------------------------- /vscode-extension-pack/package.json.bak: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hyprland", 3 | "displayName": "hyprland", 4 | "description": "Extension pack for syntax highlighting and rich IDE support in Hyprland configuration files", 5 | "version": "0.0.1", 6 | "engines": { 7 | "vscode": "^1.88.0" 8 | }, 9 | "categories": [ 10 | "Extension Packs" 11 | ], 12 | "extensionPack": [ 13 | "publisher.extensionName" 14 | ], 15 | "devDependencies": { 16 | "@vscode/vsce": "^2.26.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /vscode-extension-pack/vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension Pack 2 | 3 | ## What's in the folder 4 | 5 | * This folder contains all of the files necessary for your extension pack. 6 | * `package.json` - this is the manifest file that defines the list of extensions of the extension pack. 7 | 8 | ## Get up and running straight away 9 | 10 | * Press `F5` to open a new window with your extension loaded. 11 | * Open `Extensions Viewlet` and check your extensions are installed. 12 | 13 | ## Make changes 14 | 15 | * You can relaunch the extension from the debug toolbar after making changes to the files listed above. 16 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 17 | 18 | ## Install your extension 19 | 20 | * To start using your extension with Visual Studio Code copy it into the `/.vscode/extensions` folder and restart Code. 21 | * To share your extension with the world, read on https://code.visualstudio.com/docs about publishing an extension. 22 | -------------------------------------------------------------------------------- /vscode/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | client/node_modules/** 3 | client/out/** 4 | server/node_modules/** 5 | server/out/** -------------------------------------------------------------------------------- /vscode/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /**@type {import('eslint').Linter.Config} */ 2 | // eslint-disable-next-line no-undef 3 | module.exports = { 4 | root: true, 5 | parser: "@typescript-eslint/parser", 6 | plugins: ["@typescript-eslint"], 7 | extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 8 | rules: { 9 | semi: "off", 10 | "@typescript-eslint/no-unused-vars": 0, 11 | "@typescript-eslint/no-explicit-any": 0, 12 | "@typescript-eslint/explicit-module-boundary-types": 0, 13 | "@typescript-eslint/no-non-null-assertion": 0, 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /vscode/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test 4 | # Created by https://www.toptal.com/developers/gitignore/api/node 5 | # Edit at https://www.toptal.com/developers/gitignore?templates=node 6 | 7 | ### Node ### 8 | # Logs 9 | logs 10 | *.log 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | lerna-debug.log* 15 | .pnpm-debug.log* 16 | 17 | # Diagnostic reports (https://nodejs.org/api/report.html) 18 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 19 | 20 | # Runtime data 21 | pids 22 | *.pid 23 | *.seed 24 | *.pid.lock 25 | 26 | # Directory for instrumented libs generated by jscoverage/JSCover 27 | lib-cov 28 | 29 | # Coverage directory used by tools like istanbul 30 | coverage 31 | *.lcov 32 | 33 | # nyc test coverage 34 | .nyc_output 35 | 36 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 37 | .grunt 38 | 39 | # Bower dependency directory (https://bower.io/) 40 | bower_components 41 | 42 | # node-waf configuration 43 | .lock-wscript 44 | 45 | # Compiled binary addons (https://nodejs.org/api/addons.html) 46 | build/Release 47 | 48 | # Dependency directories 49 | node_modules/ 50 | jspm_packages/ 51 | 52 | # Snowpack dependency directory (https://snowpack.dev/) 53 | web_modules/ 54 | 55 | # TypeScript cache 56 | *.tsbuildinfo 57 | 58 | # Optional npm cache directory 59 | .npm 60 | 61 | # Optional eslint cache 62 | .eslintcache 63 | 64 | # Optional stylelint cache 65 | .stylelintcache 66 | 67 | # Microbundle cache 68 | .rpt2_cache/ 69 | .rts2_cache_cjs/ 70 | .rts2_cache_es/ 71 | .rts2_cache_umd/ 72 | 73 | # Optional REPL history 74 | .node_repl_history 75 | 76 | # Output of 'npm pack' 77 | *.tgz 78 | 79 | # Yarn Integrity file 80 | .yarn-integrity 81 | 82 | # dotenv environment variable files 83 | .env 84 | .env.development.local 85 | .env.test.local 86 | .env.production.local 87 | .env.local 88 | 89 | # parcel-bundler cache (https://parceljs.org/) 90 | .cache 91 | .parcel-cache 92 | 93 | # Next.js build output 94 | .next 95 | out 96 | 97 | # Nuxt.js build / generate output 98 | .nuxt 99 | dist 100 | 101 | # Gatsby files 102 | .cache/ 103 | # Comment in the public line in if your project uses Gatsby and not Next.js 104 | # https://nextjs.org/blog/next-9-1#public-directory-support 105 | # public 106 | 107 | # vuepress build output 108 | .vuepress/dist 109 | 110 | # vuepress v2.x temp and cache directory 111 | .temp 112 | 113 | # Docusaurus cache and generated files 114 | .docusaurus 115 | 116 | # Serverless directories 117 | .serverless/ 118 | 119 | # FuseBox cache 120 | .fusebox/ 121 | 122 | # DynamoDB Local files 123 | .dynamodb/ 124 | 125 | # TernJS port file 126 | .tern-port 127 | 128 | # Stores VSCode versions used for testing VSCode extensions 129 | .vscode-test 130 | 131 | # yarn v2 132 | .yarn/cache 133 | .yarn/unplugged 134 | .yarn/build-state.yml 135 | .yarn/install-state.gz 136 | .pnp.* 137 | 138 | ### Node Patch ### 139 | # Serverless Webpack directories 140 | .webpack/ 141 | 142 | # Optional stylelint cache 143 | 144 | # SvelteKit build / generate output 145 | .svelte-kit 146 | 147 | # End of https://www.toptal.com/developers/gitignore/api/node 148 | -------------------------------------------------------------------------------- /vscode/.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | **/*.ts 3 | **/*.map 4 | .gitignore 5 | **/tsconfig.json 6 | **/tsconfig.base.json 7 | contributing.md 8 | .travis.yml 9 | pkg/ 10 | scripts/ 11 | -------------------------------------------------------------------------------- /vscode/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice (including the next 11 | paragraph) shall be included in all copies or substantial portions of the 12 | Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 17 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 19 | OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /vscode/README.md: -------------------------------------------------------------------------------- 1 | # VSCode Extension for Hyprland configuraiton files 2 | 3 | ## Installation 4 | 5 | Requires [installing `hyprls`](https://github.com/ewen-lbh/hyprls) and having in on your PATH. 6 | -------------------------------------------------------------------------------- /vscode/bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyprland-community/hyprls/04c579ad66e7d1e841896fb28ef8b7bfcf67af40/vscode/bun.lockb -------------------------------------------------------------------------------- /vscode/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyprland-community/hyprls/04c579ad66e7d1e841896fb28ef8b7bfcf67af40/vscode/icon.png -------------------------------------------------------------------------------- /vscode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-hyprls", 3 | "description": "VSCode extension for HyprLS", 4 | "author": "Gwenn Le Bihan ", 5 | "license": "MIT", 6 | "version": "0.7.0", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/hyprland-community/hyprls" 10 | }, 11 | "publisher": "ewen-lbh", 12 | "categories": [], 13 | "keywords": [], 14 | "engines": { 15 | "vscode": "^1.100.2" 16 | }, 17 | "contributes": { 18 | "languages": [ 19 | { 20 | "id": "hyprlang", 21 | "aliases": [ 22 | "HyprLang" 23 | ], 24 | "extensions": [ 25 | ".hl" 26 | ], 27 | "filenamePatterns": [ 28 | "hypr*.conf" 29 | ] 30 | } 31 | ], 32 | "commands": [ 33 | { 34 | "category": "HyprLS", 35 | "title": "Restart language server", 36 | "command": "vscode-hyprls.restart-lsp" 37 | } 38 | ] 39 | }, 40 | "activationEvents": [ 41 | "onLanguage:hyprlang" 42 | ], 43 | "main": "./out/extension", 44 | "scripts": { 45 | "vscode:prepublish": "npm run compile", 46 | "compile": "tsc -b", 47 | "watch": "tsc -b -w", 48 | "lint": "eslint ./src --ext .ts,.tsx", 49 | "test": "sh ./scripts/e2e.sh" 50 | }, 51 | "devDependencies": { 52 | "@ortfo/db": "^1.6.1", 53 | "@types/mocha": "^10.0.10", 54 | "@types/node": "^22.15.29", 55 | "@types/vscode": "^1.100.0", 56 | "@typescript-eslint/eslint-plugin": "^8.32.1", 57 | "@typescript-eslint/parser": "^8.32.1", 58 | "@vscode/test-electron": "^2.5.2", 59 | "@vscode/vsce": "^3.4.2", 60 | "eslint": "^9.28.0", 61 | "mocha": "^11.5.0", 62 | "typescript": "^5.8.3" 63 | }, 64 | "dependencies": { 65 | "vscode-languageclient": "^9.0.1" 66 | }, 67 | "icon": "icon.png", 68 | "displayName": "HyprLS" 69 | } 70 | -------------------------------------------------------------------------------- /vscode/scripts/e2e.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export CODE_TESTS_PATH="$(pwd)/client/out/test" 4 | export CODE_TESTS_WORKSPACE="$(pwd)/client/testFixture" 5 | 6 | node "$(pwd)/client/out/test/runTest" -------------------------------------------------------------------------------- /vscode/src/extension.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | 6 | import { commands, ExtensionContext, workspace } from "vscode" 7 | 8 | import { 9 | LanguageClient, 10 | LanguageClientOptions, 11 | ServerOptions, 12 | TransportKind, 13 | } from "vscode-languageclient/node" 14 | 15 | let client: LanguageClient 16 | 17 | export function activate(context: ExtensionContext) { 18 | const serverModule = "hyprls" 19 | 20 | // If the extension is launched in debug mode then the debug server options are used 21 | // Otherwise the run options are used 22 | const serverOptions: ServerOptions = { 23 | run: { 24 | command: serverModule, 25 | transport: TransportKind.stdio, 26 | }, 27 | debug: { 28 | command: "/home/uwun/projects/hyprls/hyprlang-lsp", 29 | transport: TransportKind.stdio, 30 | }, 31 | } 32 | 33 | // Options to control the language client 34 | const clientOptions: LanguageClientOptions = { 35 | // Register the server for plain text documents 36 | documentSelector: [{ scheme: "file", language: "hyprlang" }], 37 | outputChannelName: "HyprLS", 38 | synchronize: { 39 | fileEvents: workspace.createFileSystemWatcher("*.hl"), 40 | }, 41 | } 42 | 43 | context.subscriptions.push( 44 | commands.registerCommand("vscode-hyprls.restart-lsp", () => { 45 | client.restart() 46 | }) 47 | ) 48 | 49 | // Create the language client and start the client. 50 | client = new LanguageClient("hyprlang", "Hypr", serverOptions, clientOptions) 51 | 52 | // Start the client. This will also launch the server 53 | client.start() 54 | } 55 | 56 | export function deactivate(): Thenable | undefined { 57 | if (!client) { 58 | return undefined 59 | } 60 | return client.stop() 61 | } 62 | -------------------------------------------------------------------------------- /vscode/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2020", 5 | "lib": ["es2020"], 6 | "outDir": "out", 7 | "rootDir": "src", 8 | "sourceMap": true, 9 | "skipLibCheck": true, 10 | "esModuleInterop": true 11 | }, 12 | "include": ["src"], 13 | "exclude": ["node_modules", ".vscode-test"] 14 | } 15 | -------------------------------------------------------------------------------- /vscode/vscode-hyprlang-0.0.1.vsix: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyprland-community/hyprls/04c579ad66e7d1e841896fb28ef8b7bfcf67af40/vscode/vscode-hyprlang-0.0.1.vsix --------------------------------------------------------------------------------