Full Code of axieax/urlview.nvim for AI

main 813736e6891b cached
31 files
76.4 KB
20.1k tokens
1 requests
Download .txt
Repository: axieax/urlview.nvim
Branch: main
Commit: 813736e6891b
Files: 31
Total size: 76.4 KB

Directory structure:
gitextract_1w34gv51/

├── .github/
│   └── workflows/
│       └── default.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── doc/
│   └── urlview.txt
├── lua/
│   └── urlview/
│       ├── actions.lua
│       ├── command.lua
│       ├── config/
│       │   ├── constants.lua
│       │   ├── default.lua
│       │   ├── helpers.lua
│       │   └── init.lua
│       ├── init.lua
│       ├── jump.lua
│       ├── pickers.lua
│       ├── search/
│       │   ├── helpers.lua
│       │   ├── init.lua
│       │   └── validation.lua
│       └── utils.lua
├── selene.toml
├── stylua.toml
├── tests/
│   ├── init.vim
│   └── urlview/
│       ├── capture_custom_searches_spec.lua
│       ├── capture_jump_spec.lua
│       ├── capture_mock_spec.lua
│       ├── capture_multiple_spec.lua
│       ├── capture_process_spec.lua
│       ├── capture_single_spec.lua
│       ├── helpers.lua
│       └── jump_helpers.lua
└── vim.toml

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/default.yml
================================================
name: default

on:
  pull_request:
  push:
    branches: [main]

jobs:
  stylua:
    name: Check code style
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: JohnnyMorganz/stylua-action@v1
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          args: --color always --check .

  selene:
    name: Lint code
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: NTBBloodbath/selene-action@v1.0.0
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          args: --color always .

  test:
    name: Neovim test runner
    runs-on: ubuntu-latest
    strategy:
      matrix:
        neovim-version: [v0.9.0, v0.8.0, v0.7.0, stable, nightly]
    steps:
      - uses: actions/checkout@v2
        with:
          path: urlview.nvim
      - uses: actions/checkout@v2
        with:
          repository: nvim-lua/plenary.nvim
          path: plenary.nvim
      - uses: rhysd/action-setup-vim@v1
        with:
          neovim: true
          version: ${{ matrix.neovim-version }}
      - run: make test
        working-directory: urlview.nvim
        timeout-minutes: 1


================================================
FILE: .gitignore
================================================
doc/tags


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2022 Andrew Xie

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
test:
	nvim --headless --noplugin -u tests/init.vim -c "PlenaryBustedDirectory tests/urlview {minimal_init = 'tests/init.vim'}"


================================================
FILE: README.md
================================================
<h1 align="center">🔎 urlview.nvim</h1>
<p align="center"><i>Find and display URLs from a variety of search contexts</i></p>
<p align="center">
  <a href="https://github.com/neovim/neovim">
    <img alt="Neovim Version" src="https://img.shields.io/static/v1?label=&message=%3E%3D0.7&style=for-the-badge&logo=neovim&color=green&labelColor=302D41"/>
  </a>
  <a href="https://github.com/axieax/urlview.nvim/stargazers">
    <img alt="Repo Stars" src="https://img.shields.io/github/stars/axieax/urlview.nvim?style=for-the-badge&color=yellow&label=%E2%AD%90&labelColor=302D41"/>
  </a>
  <a href="https://github.com/axieax/urlview.nvim">
    <img alt="Repo Size" src="https://img.shields.io/github/repo-size/axieax/urlview.nvim?label=&color=orange&logo=hackthebox&style=for-the-badge&logoColor=lightgray&labelColor=302D41"/>
  </a>
</p>

✨ UrlView is an extensible plugin for the [Neovim](https://neovim.io) text editor which essentially:

1. Finds URLs from a variety of **search contexts** (e.g. from a buffer, file, plugin URLs)
2. Displays these URLs in a **picker**, such as the built-in `vim.ui.select`, [telescope.nvim](https://github.com/nvim-telescope/telescope.nvim), or [fzf-lua](https://github.com/ibhagwan/fzf-lua).
3. Performs **actions** on selected URLs, such as navigating to the URL in your preferred browser, or copying the link to your clipboard

🎯 Additional features and example use cases include:

- Easily visualise all the URLs in a buffer or file (e.g. links in your Markdown documents)
- Quickly accessing repo webpages for installed Neovim plugins (life-saver for config updates or browsing plugin documentation)
- Ability to register custom searchers (e.g. Jira ticket numbers), pickers and actions (please see [docs](doc/urlview.txt) or `:h urlview.search-custom`)
- Jumping to the previous or next URL in the active buffer (and opening the URL in your browser)

> Please note that currently, this plugin only detects URLs beginning with a `http(s)` or `www` prefix for buffer and file search, but there are plans to support a more general pattern (see [🗺️ Roadmap](https://github.com/axieax/urlview.nvim/issues/3)).

## 📸 Screenshots

### 📋 Buffer Links

`:UrlView` or `:UrlView buffer`

![buffer-demo](https://user-images.githubusercontent.com/62098008/161417569-e8103fc4-a009-4c4f-95a7-ea7e22cbb3df.png)

### 🔌 Plugin Links

`:UrlView lazy`, `:UrlView packer`, or `:UrlView vimplug` depending on your plugin manager of choice

![packer-demo](https://user-images.githubusercontent.com/62098008/161417652-fd514310-a926-4ec7-af28-b2cfa3aa4b19.png)

## ⚡ Requirements

- This plugin supports **Neovim v0.7** or later.
- Please find the appropriate _\*-compat_ Git tag if you need legacy support for previous Neovim versions, such as [v0.6-compat](https://github.com/axieax/urlview.nvim/tree/v0.6-compat) for nvim v0.6, although these versions will no longer receive any new updates or features.
- For Neovim versions prior to v0.6 or Vanilla [Vim](https://www.vim.org/) support, please check out [urlview.vim](https://github.com/strboul/urlview.vim) as an alternative plugin.

## 🚀 Usage

### Searching contexts

1. Use the command `:UrlView` to see all the URLs in the current buffer.

- For your convenience, feel free to setup a keybind for this using `vim.keymap.set`:

  ```lua
  vim.keymap.set("n", "\\u", "<Cmd>UrlView<CR>", { desc = "View buffer URLs" })
  vim.keymap.set("n", "\\U", "<Cmd>UrlView packer<CR>", { desc = "View Packer plugin URLs" })
  ```

- You can also hit `:UrlView <tab>` to see additional contexts that you can search from
  - e.g. `:UrlView packer` to view links for installed [packer.nvim](https://github.com/wbthomason/packer.nvim) plugins

2. You can optionally select a link to bring it up in your browser.

### Buffer URL navigation

1. You can use `[u` and `]u` (default bindings) to jump to the previous and next URL in the buffer respectively.
2. If desired, you can now press `gx` to open the URL under the cursor in your browser, with netrw.
3. This keymap can be altered under the `jump` config option.

## 📦 Installation

Install this plugin with your package manager of choice. You can lazy load this plugin by the `UrlView` command if desired.

- [packer.nvim](https://github.com/wbthomason/packer.nvim)

```lua
use("axieax/urlview.nvim")
```

- [lazy.nvim](https://github.com/folke/lazy.nvim)

```lua
"axieax/urlview.nvim"
```

## ⚙️ Configuration

This plugin supports plug-n-play, meaning you can get it up and running without any additional setup.

However, you can customise the [default options](lua/urlview/config/default.lua) using the `setup` function:

```lua
require("urlview").setup({
  -- custom configuration options --
})
```

Please check out the [documentation](doc/urlview.txt) for configuration options and details.

## 🎨 Pickers

### ✔️ Native (vim.ui.select)

You can customise the appearance of `vim.ui.select` with plugins such as [dressing.nvim](https://github.com/stevearc/dressing.nvim) and [telescope-ui-select.nvim](https://github.com/nvim-telescope/telescope-ui-select.nvim). In the demo images above, I used [dressing.nvim](https://github.com/stevearc/dressing.nvim)'s Telescope option, which allows me to further filter and fuzzy search through my entries.

### 🔭 Telescope

- Optional picker option
- Additional requirements (only if you're using this picker): [telescope.nvim](https://github.com/nvim-telescope/telescope.nvim)
- You can use Telescope as your `default_picker` using the `require("urlview").setup` function
- Alternatively, you can specify a picker dynamically with `:UrlView <ctx> picker=telescope`

### 📑 fzf-lua

- Optional picker option
- Additional requirements (only if you're using this picker): [fzf-lua](https://github.com/ibhagwan/fzf-lua)
- You can use fzf-lua as your `default_picker` using the `require("urlview").setup` function
- Alternatively, you can specify a picker dynamically with `:UrlView <ctx> picker=fzf_lua`

## 🚧 Stay Updated

More features are continually being added to this plugin (see [🗺️ Roadmap](https://github.com/axieax/urlview.nvim/issues/3)). Feel free to file an issue or create a PR for any features / fixes :)

It is recommended to subscribe to the [🙉 Breaking Changes](https://github.com/axieax/urlview.nvim/issues/37) thread to be updated on potentially breaking changes to this plugin, as well as resolution strategies.


================================================
FILE: doc/urlview.txt
================================================
*urlview.txt*             Find and display URLs from a variety of search contexts

================================================================================
Table of Contents                                             *urlview.contents*

    INTRODUCTION .......................................... |urlview|
    CONFIGURATION ......................................... |urlview.config|
    USAGE ................................................. |urlview.usage|
    SEARCH ................................................ |urlview.search|
    PICKERS ............................................... |urlview.pickers|
    ACTIONS ............................................... |urlview.actions|


================================================================================
INTRODUCTION                                                           *urlview*

✨ urlview.nvim is essentially a plugin which:

1. Finds URLs from a variety of |urlview.search| contexts
2. Displays these URLs in one of the |urlview.pickers|
3. Performs |urlview.actions| on selected URLs from the pickers


================================================================================
CONFIGURATION                                                   *urlview.config*

urlview.nvim supports plug-n-play, meaning the default config is automatically
set up. However, the default options can be configured using the
`require("urlview").setup` function, with the configuration options below.

                                                  *urlview.config-default_title*
{default_title}                                       string (default: "Links:")

Forms part of the prompt title for the picker (`<context> <default_title>`). For
example, for the buffer search context with a default_title of "Links:", the
picker title becomes "Buffer Links:". The capitalisation of the first letter
in the default_title determines the capitalisation of the search context in
the prompt title as well. For example, a default_title of "links" will result
in a prompt title of "buffer links".

                                                 *urlview.config-default_picker*
{default_picker}                                      string (default: "native")

Default picker for `:UrlView` commands. This should be any one of the pickers in
|urlview.pickers|.

                                                 *urlview.config-default_prefix*
{default_prefix}                                    string (default: "https://")

Default prefix for URLs missing a HTTP protocol (e.g. "www.google.com" becomes
"https://www.google.com"). Such a protocol is required for |urlview.actions|
to be able to navigate to URLs. Another suggested option is "http://",
although it is less secure than the default HTTPS protocol.

                                                 *urlview.config-default_action*
{default_action}                                       string (default: "netrw")

Default action to take upon selecting a URL from a picker. This should be any
one of the actions in |urlview.actions|.

                                                 *urlview.config-default_register*
{default_register}                                     string (default: "+")

Default register to use when yankying.

                                         *urlview.config-default_include_branch*
{default_include_branch}                                boolean (default: false)

Default option for whether plugin URLs should link to the branch used by your
package manager, for |urlview.search-lazy|, |urlview.search-packer| or
|urlview.search-vimplug|. When this option is enabled, navigated links will
open the plugin's repository to the specific branch specified to your plugin
manager.

                                                         *urlview.config-unique*
{unique}                                                 boolean (default: true)

Enable to ensure links shown in the picker are unique (i.e. no duplicates).

                                                         *urlview.config-sorted*
{sorted}                                                 boolean (default: true)

Enable to ensure links shown in the picker are sorted alphabetically.

                                                  *urlview.config-log_level_min*
{log_level_min}    `vim.log.levels` enum or int (default: `vim.log.levels.INFO`)

Minimum log level for output from this plugin. Lower logging levels will be
ignored. >lua

  vim.log.levels = {
    TRACE = 0,
    DEBUG = 1,
    INFO = 2,
    WARN = 3
    ERROR = 4,
    OFF = 5,  -- Neovim v0.8+
  }
<
The default value of `vim.log.levels.INFO` means that INFO, WARN and ERROR
logs will all be displayed. Similarly, a log_level_min value of
`vim.log.levels.WARN` means that only WARN and ERROR logs will be displayed.
It is recommended for this value to be at least `vim.log.levels.WARN` to
ensure warnings are appropriately logged. Setting this value to
`vim.log.levels.OFF` (requires Neovim 0.8+) or `5` will effectively suppress
all logs.

                                                           *urlview.config-jump*
{jump}                                                               table (map)

Registers keymaps for jumps to the previous or next URL in the buffer.

Fields:

    {prev}                                                string (default: "[u")
    Mapping to jump to the previous URL in the buffer. Set to "" to disable.

    {next}                                                string (default: "]u")
    Mapping to jump to the previous URL in the buffer. Set to "" to disable.


================================================================================
USAGE                                                            *urlview.usage*

For normal usage, interaction with this plugin is mostly achieved through the
`:UrlView` command. This command provides completion to assist with specifying
appropriate search contexts and respective options.

Calling `:UrlView` without any additional arguments is the same as calling
`:UrlView buffer`, which finds URLs in the current buffer with the default
options configured ( |default_title|, |default_picker|, |default_action|,
|unique|, |sorted|, etc. ).

The command expects arguments in the format `:UrlView <ctx> <options...>`,
e.g. >lua

  :UrlView buffer bufnr=1
  :UrlView file filepath=/etc/hosts picker=telescope
  :UrlView packer sorted=false
<

================================================================================
SEARCH                                                          *urlview.search*

`urlview.search` provides functions which finds and extracts URLs from a
particular search context. The default search contexts can be found by the
exposed functions in the `urlview.search` module (or `urlview/search/init.lua`).

Buffer Search Context                                    *urlview.search-buffer*

The buffer search context finds URLs in a specific buffer (current by
default). To search a particular buffer, provide the desired buffer number
with `:UrlView buffer bufnr=<bufnr>`.

File Search Context                                        *urlview.search-file*

The file search context allows a user to find URLs in a particular file. This
requires passing in the parameter `filepath` for URLs in the desired file to be
searched, for example with `:UrlView file filepath="/etc/hosts"`.

Lazy Search Context                                       *urlview.search-lazy*

This search context resolves Git repository URLs for plugins installed with
the lazy.nvim plugin manager. Invoked with `:UrlView lazy`.

Packer Search Context                                    *urlview.search-packer*

This search context resolves Git repository URLs for plugins installed with
the packer.nvim plugin manager. Invoked with `:UrlView packer`.

vim-plug Search Context                                 *urlview.search-vimplug*

This search context resolves Git repository URLs for plugins installed with
the vim-plug plugin manager. Invoked with `:UrlView vimplug`.

Registering a custom search context                      *urlview.search-custom*

Custom search contexts can be registered for searching with `:UrlView <ctx>`.
The `generate_custom_search` function from the `urlview.search.helpers` module
can be used to quickly generate a function for capturing a Lua pattern and
optionally formatting it with `string.format`. The following resource is very
helpful for understanding basic Lua patterns:

- https://riptutorial.com/lua/example/20315/lua-pattern-matching

This function can then be assigned to the `urlview.search` module for use as a
search context to be selected with the `:UrlView` command (with completion).

Here is an example which finds Jira ticket numbers in the format of "AXIE-"
followed by any number (capture field). This entire pattern gets captured and
can then be embedded into the format string in the format field, allowing
a user to directly navigate to the Jira ticket in their browser. >lua

  local search = require("urlview.search")
  local search_helpers = require("urlview.search.helpers")
  search["jira"] = search_helpers.generate_custom_search({
    capture = "AXIE%-%d+",
    format = "https://jira.axieax.com/browse/%s",
  })

This allows for captures such as "AXIE-1", "AXIE-17", "AXIE-132". Feel free to
adjust the `capture` field to suit your needs.

A custom function can also be registered for a search context, as well as
additional parameters with something like >lua

  local search = require("urlview.search")
  search["fruits"] = function(opts)
    local fruits = { "apple", "banana", "watermelon" }
    if opts.include_tomato then
      table.insert(fruits, "tomato")
    end
    return fruits
  end

Additional parameters can be passed into the custom search context using the
`:UrlView` command, for example `:UrlView fruits include_tomato` or `:UrlView
fruits include_tomato=true`. Other types also work.

Please see `urlview/search/init.lua` for more examples. If you have a useful
custom search context, feel free to share it in
https://github.com/axieax/urlview.nvim/discussions/40 for others to use (and
potentially make it a built-in search context)!


================================================================================
PICKERS                                                        *urlview.pickers*

`urlview.pickers` are used to display the results from |urlview.search|.

vim.ui.select                                           *urlview.pickers-native*

This picker uses the built-in function `vim.ui.select` to display results. It
is recommended to use a UI-extension for `vim.ui.select` for enhanced
functionality (similar to |urlview.pickers-telescope|), including customising
popup locations and behaviour, and searching (even fuzzy-searching) through
results. An example of such a plugin is dressing.nvim
(https://github.com/stevearc/dressing.nvim).

telescope                                            *urlview.pickers-telescope*

This picker uses the Telescope plugin
(https://github.com/nvim-telescope/telescope.nvim) as a picker to display
results. Please note that this requires installing Telescope as a plugin in
order for it to be used as a picker.

Registering a custom picker                             *urlview.pickers-custom*

A recommendation for registering a custom picker is to first check out a
`vim.ui.select` UI-extension such as dressing.nvim
(https://github.com/stevearc/dressing.nvim) to see if your desired picker can
be used for the native picker (e.g. nui, fzf).

In the case that you want to write your own picker, you can register it by
adding it as a function to the `urlview.pickers` module. >lua

  local pickers = require("urlview.pickers")
  pickers["fzf"] = function(items, opts)
    -- TODO
  end
<
Please check out `urlview/pickers.lua` for examples on how to do this.


================================================================================
ACTIONS                                                        *urlview.actions*

`urlview.actions` determine the behaviour when a URL is selected with one of
the |urlview.pickers|.

netrw                                                    *urlview.actions-netrw*

The `netrw` action uses the built-in netrw feature to open a given URL in your
browser. However, some users may have this feature disabled, either explicitly
or due to "netrw hijack" behaviour from file-explorer Neovim plugins. Due to
this, if urlview detects that netrw is disabled, it will use
|urlview.actions-system| as a fallback by default.

system                                                  *urlview.actions-system*

This action opens URL in your system's default browser depending on your
operating system. If your system is not supported, please raise a GitHub issue
to have it included as a built-in options. Otherwise, specify a custom action
( |urlview.actions-custom|) for your use case.

clipboard                                            *urlview.actions-clipboard*

This action copies selected URLs to the system clipboard (specifically to
Neovim's {+} register).

Registering a custom action                             *urlview.actions-custom*

There are two main types of custom actions:

1. Execute a shell command which takes in your URL as an argument

This is the main use case for custom actions - specifying a browser such as
`chromium` or `firefox` to open your URL with. By default, this executes the
provided shell command and passes in the URL as an argument, such as >bash

  $ chromium 'https://www.google.com'
<
which launches Google in the chromium browser. This can be any executable (be
sure to find the correct path to your desired application).

2. Lua function

With urlview.nvim's principle of extensibility in mind, you can also register a
custom action as a Lua function by adding it to the `urlview.actions` module,
like so: >lua

  local actions = require("urlview.actions")
  actions["spectate"] = function(raw_url)
    -- TODO
  end
<

Please refer to `urlview/actions.lua` for examples, and feel free to create a
GitHub issue or pull request to add your custom Lua function action as a
built-in action if you think others can use it as well!


 vim:tw=78:ts=8:noet:ft=help:norl:


================================================
FILE: lua/urlview/actions.lua
================================================
local M = {}

local utils = require("urlview.utils")
local config = require("urlview.config")

--- Use command to open the URL
---@param cmd string @name of executable to run
---@param args string|table @arg(s) to pass into cmd (unescaped URL string or table of args)
local function shell_exec(cmd, args)
  if cmd and vim.fn.executable(cmd) == 1 then
    -- NOTE: `vim.fn.system` shellescapes arguments
    local cmd_args = { cmd }
    vim.list_extend(cmd_args, type(args) == "table" and args or { args })
    local err = vim.fn.system(cmd_args)
    if vim.v.shell_error ~= 0 or err ~= "" then
      utils.log(
        string.format("Failed to navigate link with cmd `%s` and args `%s`\n%s", cmd, args, err),
        vim.log.levels.ERROR
      )
    end
  else
    utils.log(
      string.format("Cannot use command `%s` to navigate links (either empty or non-executable)", cmd),
      vim.log.levels.ERROR
    )
  end
end

--- Use `netrw` to navigate to a URL
---@param raw_url string @unescaped URL
function M.netrw(raw_url)
  local url = vim.fn.shellescape(raw_url)
  local ok, err = pcall(vim.cmd, string.format("call netrw#BrowseX(%s, netrw#CheckIfRemote(%s))", url, url))
  if not ok and vim.startswith(err, "Vim(call):E117: Unknown function") then
    -- lazily use system action if netrw is disabled
    M.system(raw_url)
  end
end

--- Use the user's default browser to navigate to a URL
---@param raw_url string @unescaped URL
function M.system(raw_url)
  local os = utils.os
  if os == "Darwin" then -- MacOS
    shell_exec("open", raw_url)
  elseif os == "Linux" or os == "FreeBSD" then -- Linux and FreeBSD
    shell_exec("xdg-open", raw_url)
  elseif os:match("Windows") then -- Windows
    -- HACK: `start` cmd itself doesn't exist but lives under `cmd`
    shell_exec("cmd", { "/C", "start", raw_url })
  else
    utils.log(
      "Unsupported operating system for `system` action. Please raise a GitHub issue for " .. os,
      vim.log.levels.WARN
    )
  end
end

--- Copy URL to clipboard
---@param raw_url string @unescaped URL
function M.clipboard(raw_url)
  vim.fn.setreg(config.default_register, raw_url)
  utils.log(string.format("URL %s copied to clipboard", raw_url), vim.log.levels.INFO)
end

return setmetatable(M, {
  -- execute action as command if it is not one of the above module keys
  __index = function(_, k)
    if k ~= nil then
      return function(raw_url)
        return shell_exec(k, raw_url)
      end
    end
  end,
})


================================================
FILE: lua/urlview/command.lua
================================================
local M = {}

local utils = require("urlview.utils")
local search_contexts = require("urlview.search")
local search_validation = require("urlview.search.validation")

--- Processes arguments provided through the `UrlView` command for `M.search`
local function command_search(res)
  local opts = {}
  local context = res.fargs[1]
  local option_args = vim.list_slice(res.fargs, 2)

  -- process provided options
  for _, arg in ipairs(option_args) do
    local split = vim.split(arg, "=", { plain = true })
    if #split == 1 then
      opts[arg] = true
    elseif #split == 2 then
      local key, value = unpack(split)
      -- remove beginning and trailing quotes from value if present
      local inner = value:match([[^['"](.*)['"]$]])
      if inner ~= nil then
        value = inner
      end
      -- type conversion
      if vim.tbl_contains({ "true", "false" }, value:lower()) then
        value = utils.string_to_boolean(value)
      end
      opts[key] = value
    else
      utils.log("Unable to parse argument " .. arg, vim.log.levels.ERROR)
      return
    end
  end

  require("urlview").search(context, opts)
end

local additional_opts = { "title", "picker", "action", "sorted" }

local function command_completion(_, line)
  local args = vim.split(line, "%s+")
  local nargs = #args - 2
  if nargs == 0 then
    -- search context completion
    local contexts = vim.tbl_keys(search_contexts)
    utils.alphabetical_sort(contexts)
    return contexts
  else
    -- opts completion
    local context = args[2]
    local context_opts = search_validation[context]()
    local accepted_opts = vim.list_extend(context_opts, additional_opts)
    utils.alphabetical_sort(accepted_opts)
    return vim.tbl_map(function(value)
      return value .. "="
    end, accepted_opts)
  end
end

function M.register_command()
  vim.api.nvim_create_user_command("UrlView", command_search, {
    desc = "Find URLs in the current buffer or another search context",
    complete = command_completion,
    nargs = "*",
  })
end

return M


================================================
FILE: lua/urlview/config/constants.lua
================================================
local constants = {
  -- SEE: lua pattern matching (https://riptutorial.com/lua/example/20315/lua-pattern-matching)
  -- regex equivalent: [A-Za-z0-9@:%._+~#=/\-?&]*
  pattern = "[%w@:%%._+~#=/%-?&]*",
  http_pattern = "https?://",
  www_pattern = "www%.",
}

return constants


================================================
FILE: lua/urlview/config/default.lua
================================================
local default_config = {
  -- Prompt title (`<context> <default_title>`, e.g. `Buffer Links:`)
  default_title = "Links:",
  -- Default picker to display links with
  -- Options: "native" (vim.ui.select) or "telescope"
  default_picker = "native",
  -- Set the default protocol for us to prefix URLs with if they don't start with http/https
  default_prefix = "https://",
  -- Command or method to open links with
  -- Options: "netrw", "system" (default OS browser), "clipboard"; or "firefox", "chromium" etc.
  -- By default, this is "netrw", or "system" if netrw is disabled
  default_action = "netrw",
  -- Set the register to use when yanking
  -- Default: + (system clipboard)
  default_register = "+",
  -- Whether plugin URLs should link to the branch used by your package manager
  default_include_branch = false,
  -- Ensure links shown in the picker are unique (no duplicates)
  unique = true,
  -- Ensure links shown in the picker are sorted alphabetically
  sorted = true,
  -- Minimum log level (recommended at least `vim.log.levels.WARN` for error detection warnings)
  log_level_min = vim.log.levels.INFO,
  -- Keymaps for jumping to previous / next URL in buffer
  jump = {
    prev = "[u",
    next = "]u",
  },
}

return default_config


================================================
FILE: lua/urlview/config/helpers.lua
================================================
local M = {}

local config = require("urlview.config")
local default_config = require("urlview.config.default")

function M.reset_defaults()
  config._options = default_config
end

function M.update_config(user_config)
  config._options = vim.tbl_deep_extend("force", config._options, user_config)
end

return M


================================================
FILE: lua/urlview/config/init.lua
================================================
local M = {
  _options = {},
}

return setmetatable(M, {
  -- general get and set operations refer to the internal table `_options`
  __index = function(_, k)
    return M._options[k]
  end,
  __newindex = function(_, k, v)
    M._options[k] = v
  end,
})


================================================
FILE: lua/urlview/init.lua
================================================
local M = {}

local actions = require("urlview.actions")
local command = require("urlview.command")
local config = require("urlview.config")
local config_helpers = require("urlview.config.helpers")
local jump = require("urlview.jump")
local search = require("urlview.search")
local search_validation = require("urlview.search.validation")
local pickers = require("urlview.pickers")
local utils = require("urlview.utils")

--- Searchs the provided context for links
---@param ctx string where to search (default: search buffer)
---@param opts table (map, optional)
function M.search(ctx, opts)
  ctx = utils.fallback(ctx, "buffer")
  opts = utils.fallback(opts, {})
  opts.action = utils.fallback(opts.action, config.default_action)
  local picker = utils.fallback(opts.picker, config.default_picker)
  if not opts.title then
    local should_capitalise = string.match(config.default_title, "^%u")
    local ctx_title = utils.ternary(should_capitalise, ctx:gsub("^%l", string.upper), ctx)
    opts.title = string.format("%s %s", ctx_title, config.default_title)
  end

  -- search ctx for links and display with picker
  opts = search_validation[ctx](opts)
  local links = search[ctx](opts)
  links = utils.process_links(links, opts)
  if links and not vim.tbl_isempty(links) then
    if type(opts.action) == "string" then
      opts.action = actions[opts.action]
    end
    pickers[picker](links, opts)
  else
    utils.log("No links found in context " .. ctx, vim.log.levels.INFO)
  end
end

local function autoload()
  config_helpers.reset_defaults()
  command.register_command()
end

autoload()

--- Custom setup function
--- Not required to be called unless user wants to modify the default config
---@param user_config table (optional)
function M.setup(user_config)
  user_config = utils.fallback(user_config, {})
  config_helpers.update_config(user_config)

  jump.register_mappings(config.jump)
end

return M


================================================
FILE: lua/urlview/jump.lua
================================================
local M = {}

-- NOTE: line numbers are 1-indexed, column numbers are 0-indexed

local utils = require("urlview.utils")
local search_helpers = require("urlview.search.helpers")

local END_COL = -1

--- Return the starting positions of `match` in `line`
---@param line string
---@param match string
---@param offset number @added to each position
---@return table (list) of offsetted starting indicies
function M.line_match_positions(line, match, offset)
  local res = {}
  local init = 1
  while init <= #line do
    local start, finish = line:find(match, init, true)
    if start == nil then
      return res
    end

    table.insert(res, start + offset - 1)
    init = finish
  end

  return res
end

--- Returns a starting column position not on a URL
---@param line_start number @line number at cursor
---@param col_start number @column number at cursor
---@param reversed boolean @direction
---@return number @corrected starting column
function M.correct_start_col(line_start, col_start, reversed)
  local full_line = vim.fn.getline(line_start)
  local matches = search_helpers.content(full_line)
  for _, match in ipairs(matches) do
    local positions = M.line_match_positions(full_line, match, 0)
    for _, position in ipairs(positions) do
      local url_end = position + #match
      local on_url = col_start >= position and col_start < url_end
      -- edge case for going backwards with cursor at start of URL
      if on_url and reversed and position == col_start then
        return math.max(col_start - 1, 0)
      -- generally if on a URL, move column to be after the URL
      elseif on_url then
        return url_end
      end
    end
  end
  return col_start
end

local reversed_sort_function_lookup = {
  -- reversed == true: descending sort
  [true] = function(a, b)
    return a > b
  end,
  -- reversed == false: ascending sort
  [false] = function(a, b)
    return a < b
  end,
}

--- Finds the position of the previous / next URL
---@param winnr number @id of current window
---@param reversed boolean @direction false for forward, true for backwards
---@return table|nil @position
function M.find_url(winnr, reversed)
  local line_no, col_no = unpack(vim.api.nvim_win_get_cursor(winnr))
  local total_lines = vim.api.nvim_buf_line_count(0)
  col_no = M.correct_start_col(line_no, col_no, reversed)

  local sort_function = reversed_sort_function_lookup[reversed]
  local line_last = utils.ternary(reversed, 0, total_lines + 1)
  while line_no ~= line_last do
    local full_line = vim.fn.getline(line_no)
    col_no = utils.ternary(col_no == END_COL, #full_line, col_no)
    local line = utils.ternary(reversed, full_line:sub(1, col_no), full_line:sub(col_no + 1))
    local matches = search_helpers.content(line)

    if not vim.tbl_isempty(matches) then
      -- sorted table(list) of starting column numbers for URLs in line
      -- normal order: ascending, reversed order: descending
      local indices = {}
      for _, match in ipairs(matches) do
        local offset = utils.ternary(reversed, 0, col_no)
        vim.list_extend(indices, M.line_match_positions(line, match, offset))
      end
      table.sort(indices, sort_function)
      -- find first valid (before or after current column)
      for _, index in ipairs(indices) do
        local valid = utils.ternary(reversed, index <= col_no, index >= col_no)
        if valid then
          return { line_no, index }
        end
      end
    end

    line_no = utils.ternary(reversed, line_no - 1, line_no + 1)
    col_no = utils.ternary(reversed, END_COL, 0)
  end
end

--- Forward / backward jump generator
---@param reversed boolean @direction false for forward, true for backwards
---@return function @when called, jumps to the URL in the given direction
local function goto_url(reversed)
  return function()
    local direction = utils.ternary(reversed, "previous", "next")
    local winnr = vim.api.nvim_get_current_win()
    local pos = M.find_url(winnr, reversed)
    if not pos then
      utils.log(string.format("Cannot find any %s URLs in buffer", direction), vim.log.levels.INFO)
      return
    end

    if vim.api.nvim_win_is_valid(winnr) then
      vim.cmd("normal! m'") -- add to jump list
      vim.api.nvim_win_set_cursor(winnr, pos)
    else
      utils.log(
        string.format("The %s URL was found in window number %s, which is no longer valid", direction, winnr),
        vim.log.levels.WARN
      )
    end
  end
end

--- Jump to the next URL
M.next_url = goto_url(false)

--- Jump to the previous URL
M.prev_url = goto_url(true)

--- Register URL jump mappings
---@param jump_opts table
function M.register_mappings(jump_opts)
  if type(jump_opts) ~= "table" then
    utils.log(
      "Invalid type for option `jump` (expected: table with `prev` and `next` key mappings)",
      vim.log.levels.WARN
    )
  else
    if jump_opts.prev ~= "" then
      vim.keymap.set("n", jump_opts.prev, function()
        require("urlview.jump").prev_url()
      end, { desc = "Previous URL" })
    end
    if jump_opts.next ~= "" then
      vim.keymap.set("n", jump_opts.next, function()
        require("urlview.jump").next_url()
      end, { desc = "Next URL" })
    end
  end
end

return M


================================================
FILE: lua/urlview/pickers.lua
================================================
local M = {}

local utils = require("urlview.utils")

--- Displays items using the vim.ui.select picker
---@param items table (list) of strings
---@param opts table (map) of options
function M.native(items, opts)
  local options = { prompt = opts.title }
  local function on_choice(item, _)
    if item then
      opts.action(item)
    end
  end

  vim.ui.select(items, options, on_choice)
end

--- Displays items using the fzf-lua picker
---@param items table (list) of strings
---@param opts table (map) of options
function M.fzf_lua(items, opts)
  local fzf = pcall(require, "fzf-lua")
  if not fzf then
    utils.log("fzf-lua is not installed, defaulting to native vim.ui.select picker.", vim.log.levels.INFO)
    return M.native(items, opts)
  end

  require("fzf-lua").fzf_exec(items, {
    prompt = opts.title,
    fzf_opts = { ["--multi"] = true },
    actions = {
      ["default"] = function(selected)
        for _, entry in ipairs(selected) do
          opts.action(entry)
        end
      end,
    },
  })
end

--- Displays items using the Telescope picker
---@param items table (list) of strings
---@param opts table (map) of options
function M.telescope(items, opts)
  local telescope = pcall(require, "telescope")
  if not telescope then
    utils.log("Telescope is not installed, defaulting to native vim.ui.select picker.", vim.log.levels.INFO)
    return M.native(items, opts)
  end

  local actions = require("telescope.actions")
  local action_state = require("telescope.actions.state")
  local conf = require("telescope.config").values
  local finders = require("telescope.finders")
  local pickers = require("telescope.pickers")

  pickers
    .new(opts, {
      prompt_title = opts.title,
      finder = finders.new_table({
        results = items,
      }),
      sorter = conf.generic_sorter(opts),
      attach_mappings = function(prompt_bufnr, _)
        actions.select_default:replace(function()
          local picker = action_state.get_current_picker(prompt_bufnr)
          local multi = picker:get_multi_selection()
          local single = picker:get_selection()
          actions.close(prompt_bufnr)
          if #multi > 0 then
            for _, entry in ipairs(multi) do
              opts.action(entry[1])
            end
          elseif single[1] then
            opts.action(single[1])
          end
        end)
        return true
      end,
    })
    :find()
end

return setmetatable(M, {
  -- use default `vim.ui.select` when provided an invalid picker
  __index = function(_, k)
    if k ~= nil then
      utils.log(k .. " is not a valid picker, defaulting to native vim.ui.select picker.", vim.log.levels.INFO)
      return M.native
    end
  end,
})


================================================
FILE: lua/urlview/search/helpers.lua
================================================
local M = {}

local constants = require("urlview.config.constants")
local utils = require("urlview.utils")

--- Extracts content from a given buffer
---@param bufnr number (optional)
---@return string @content of buffer
function M.get_buffer_content(bufnr)
  bufnr = utils.fallback(bufnr, 0)
  if not vim.api.nvim_buf_is_valid(bufnr) then
    utils.log(string.format("Invalid buffer number provided: %s", bufnr), vim.log.levels.ERROR)
    return ""
  end
  return table.concat(vim.api.nvim_buf_get_lines(bufnr, 0, -1, false), "\n")
end

--- Extracts content from a given file
---@param filepath string @path to file
---@return string @content of file (or empty string if file cannot be open)
function M.read_file(filepath)
  local f, err = io.open(vim.fn.expand(filepath), "r")
  if f == nil then
    utils.log(err, vim.log.levels.ERROR)
    return ""
  end
  local content = f:read("*all")
  f:close()
  return content
end

--- Extracts urls from the given content
---@param content string
---@return table (list) of strings (extracted links)
function M.content(content)
  ---@type table (map) of string (url base pattern) to string (prefix / uri protocol)
  local captures = {}

  -- NOTE: this method enforces unique matches regardless of config (before a general pattern is implemented)

  -- Extract URLs starting with http:// or https://
  for capture in content:gmatch(constants.http_pattern .. "%w" .. constants.pattern) do
    local prefix = capture:match(constants.http_pattern)
    local url = capture:gsub(constants.http_pattern, "")
    captures[url] = prefix
  end

  -- Extract URLs starting with www, excluding already extracted http(s) URLs
  for capture in content:gmatch(constants.www_pattern .. "%w" .. constants.pattern) do
    if not captures[capture] then
      captures[capture] = ""
    end
  end

  -- Combine captures
  local links = {}
  for url, prefix in pairs(captures) do
    local link = prefix .. url
    if link ~= "" then
      table.insert(links, link)
    end
  end

  return links
end

--- Extract @captures from @content and display them as @formats
---@param content string @content to extract from
---@param capture string @capture pattern to extract
---@param format string @Optional format pattern to display
---@return table @list of extracted links
function M.extract_pattern(content, capture, format)
  local captures = {}
  for c in content:gmatch(capture) do
    local fmt = format and string.format(format, c) or c
    table.insert(captures, fmt)
  end
  return captures
end

--- Extract Git links from @plugins_spec
---@param plugins_spec table @map of specs for plugins
---@param uri_key string @key in plugin_spec containing the Git URL
---@param include_branch boolean @whether to include the branch in the URL
---@return table @list of extracted links
function M.extract_plugins_spec(plugins_spec, uri_key, include_branch)
  local function filter_files(plugin_url)
    if plugin_url == nil then
      return false
    end
    local fs_stat = vim.loop.fs_stat(plugin_url)
    return not fs_stat or vim.tbl_isempty(fs_stat)
  end

  local function extract_key(plugin_spec)
    if plugin_spec[uri_key] == nil then
      return nil
    end
    local uri = plugin_spec[uri_key]:gsub("%.git$", "") -- remove `.git` suffix
    if include_branch and plugin_spec.branch then
      uri = string.format("%s/tree/%s", uri, plugin_spec.branch)
    end
    return uri
  end

  local plugins = vim.tbl_map(extract_key, vim.tbl_values(plugins_spec or {}))
  return vim.tbl_filter(filter_files, plugins)
end

--- Generates a simple search function from a template table
---@param pattern table (map) with `capture` key and optional `format` key
---@return function|nil
function M.generate_custom_search(pattern)
  if not pattern.capture then
    utils.log("Unable to generate custom search: please ensure that the table has 'capture' field", vim.log.levels.WARN)
    return nil
  end

  return function(opts)
    local content = opts.content or M.get_buffer_content(opts.bufnr)
    return M.extract_pattern(content, pattern.capture, pattern.format)
  end
end

return M


================================================
FILE: lua/urlview/search/init.lua
================================================
local M = {}

local utils = require("urlview.utils")
local search_helpers = require("urlview.search.helpers")

-- NOTE: make sure to add accepted params for `opts` to `urlview.search.validation` as well if needed

--- Extracts urls from the current buffer or a given buffer
---@param opts table (map)
---@return table (list) of strings (extracted links)
function M.buffer(opts)
  local content = search_helpers.get_buffer_content(opts.bufnr)
  return search_helpers.content(content)
end

--- Extracts urls from a given file
---@param opts table (map)
---@return table (list) of strings (extracted links)
function M.file(opts)
  local content = search_helpers.read_file(opts.filepath)
  return search_helpers.content(content)
end

--- Extracts urls of packer.nvim plugins
---@param opts table (map)
---@return table (list) of strings (extracted links)
function M.packer(opts)
  -- selene: allow(global_usage)
  local plugins = _G.packer_plugins or {}
  return search_helpers.extract_plugins_spec(plugins, "url", opts.include_branch)
end

--- Extracts urls of lazy.nvim plugins
---@param opts table (map)
---@return table (list) of strings (extracted links)
function M.lazy(opts)
  local ok, lazy = pcall(require, "lazy")
  local plugins = ok and lazy.plugins() or {}
  return search_helpers.extract_plugins_spec(plugins, "url", opts.include_branch)
end

--- Extracts urls of vim-plug plugins
---@param opts table (map)
---@return table (list) of strings (extracted links)
function M.vimplug(opts)
  local plugins = vim.g.plugs or {}
  return search_helpers.extract_plugins_spec(plugins, "uri", opts.include_branch)
end

return setmetatable(M, {
  -- error check for invalid searcher (still allow function calls, but return empty table)
  __index = function(_, k)
    if k ~= nil then
      utils.log("Cannot search context " .. k, vim.log.levels.WARN)
      return function()
        return {}
      end
    end
  end,
})


================================================
FILE: lua/urlview/search/validation.lua
================================================
local M = {}

local utils = require("urlview.utils")
local config = require("urlview.config")

--- Validates `opts` and sets default values if `opts` is provided, otherwise returns all possible accepted options
---@param opts table (map) of user options
---@param rules table (map) of vim.validate rules (with an optional fourth tuple member for default value)
---       (option_key (string) -> { type (string), optional: boolean, default: any })
---@return table (map) of updated user options if `opts` is provided, otherwise returns all possible accepted options as a table (list)
local function verify_or_accept(opts, rules)
  if not opts then
    -- return valid options if `opts` not provided
    return vim.tbl_keys(rules)
  end

  local new_opts = vim.deepcopy(opts)
  for key, value in pairs(rules) do
    if #value == 4 and value[3] and opts[key] == nil then
      local default = value[4]
      new_opts[key] = default
      rules[key][1] = default
    end
  end

  vim.validate(rules)
  return new_opts
end

-- NOTE: validation for `urlview.search.init` functions go below

--- Validation for the "buffer" search context
---@param opts table (map, optional) of user options
---@return table (map) of updated user options if `opts` is provided, otherwise returns all possible accepted options as a table (list)
function M.buffer(opts)
  return verify_or_accept(opts, {
    bufnr = { opts.bufnr, "number", true, 0 },
  })
end

--- Validation for the "file" search context
---@param opts table (map, optional) of user options
---@return table (map) of updated user options if `opts` is provided, otherwise returns all possible accepted options as a table (list)
function M.file(opts)
  return verify_or_accept(opts, {
    filepath = { opts.filepath, "string", false },
  })
end

--- Validation for the "packer" search context
---@param opts table (map, optional) of user options
---@return table (map) of updated user options if `opts` is provided, otherwise returns all possible accepted options as a table (list)
function M.packer(opts)
  return verify_or_accept(opts, {
    include_branch = { opts.include_branch, "boolean", true, config.default_include_branch },
  })
end

--- Validation for the "lazy" search context
---@param opts table (map, optional) of user options
---@return table (map) of updated user options if `opts` is provided, otherwise returns all possible accepted options as a table (list)
function M.lazy(opts)
  return verify_or_accept(opts, {
    include_branch = { opts.include_branch, "boolean", true, config.default_include_branch },
  })
end

--- Validation for the "vimplug" search context
---@param opts table (map, optional) of user options
---@return table (map) of updated user options if `opts` is provided, otherwise returns all possible accepted options as a table (list)
function M.vimplug(opts)
  return verify_or_accept(opts, {
    include_branch = { opts.include_branch, "boolean", true, config.default_include_branch },
  })
end

return setmetatable(M, {
  __index = function(_, _)
    return function(opts)
      -- if `opts` provided, return `opts`, otherwise return all possible accepted options
      return utils.fallback(opts, {})
    end
  end,
})


================================================
FILE: lua/urlview/utils.lua
================================================
local M = {}

local config = require("urlview.config")
local constants = require("urlview.config.constants")

M.os = vim.loop.os_uname().sysname

function M.alphabetical_sort(tbl)
  table.sort(tbl, function(a, b)
    return a:lower() < b:lower()
  end)
end

--- Processes links before being displayed
---@param links table @list of extracted links
---@param opts table @Optional options
---@return table @list of prepared links
function M.process_links(links, opts)
  opts = M.fallback(opts, {})
  local new_links = {}

  -- Attach missing HTTP(s) protocol
  for _, link in ipairs(links) do
    if not link:match("^" .. constants.http_pattern) then
      link = config.default_prefix .. link
    end
    table.insert(new_links, link)
  end

  -- Filter duplicate links
  -- NOTE: links with different protocols / www prefix / trailing slashes are not filtered to ensure links do not break
  if M.fallback(opts.unique, config.unique) then
    local map = {}
    for _, link in ipairs(new_links) do
      map[link] = true
    end
    new_links = vim.tbl_keys(map)
  end

  -- Sort links alphabetically (case insensitive)
  if M.fallback(opts.sorted, config.sorted) then
    M.alphabetical_sort(new_links)
  end

  return new_links
end

--- Determines whether to accept the current value or use a fallback value
---@param value any @value to check
---@param fallback_value any @fallback value to use
---@param fallback_comparison any @fallback comparison, defaults to nil
---@return any @value, or @fallback if @value is @fallback_comparison
function M.fallback(value, fallback_value, fallback_comparison)
  return (value == fallback_comparison and fallback_value) or value
end

--- Mimics the ternary operator
---@param condition boolean @condition to check
---@param if_true any @value to return if @condition is true
---@param if_false any @value to return if @condition is false
---@return any @condition ? if_true : if_false
function M.ternary(condition, if_true, if_false)
  return (condition and if_true) or if_false
end

--- Logs user warnings
---@param message string @message to log
---@param level integer|nil @log level, defaults to "warning"
function M.log(message, level)
  level = M.fallback(level, vim.log.levels.WARN)
  if level >= config.log_level_min then
    vim.notify("[urlview.nvim] " .. message, level)
  end
end

--- Converts a boolean to a string
---@param value string @value to convert
---@return boolean|nil @value as a boolean, or nil if not a boolean
function M.string_to_boolean(value)
  value = value:lower()
  local bool_map = { ["true"] = true, ["false"] = false }
  if not bool_map[value] then
    M.log("Could not convert " .. value .. " to boolean")
  end
  return bool_map[value]
end

return M


================================================
FILE: selene.toml
================================================
std = "vim"


================================================
FILE: stylua.toml
================================================
column_width = 120
line_endings = "Unix"
indent_type = "Spaces"
indent_width = 2
quote_style = "AutoPreferDouble"
no_call_parentheses = false


================================================
FILE: tests/init.vim
================================================
set noswapfile
set rtp+=../plenary.nvim
set rtp+=../urlview.nvim
runtime! plugin/plenary.vim


================================================
FILE: tests/urlview/capture_custom_searches_spec.lua
================================================
local search = require("urlview.search")
local search_helpers = require("urlview.search.helpers")
local assert_tbl_same_any_order = require("tests.urlview.helpers").assert_tbl_same_any_order

describe("custom Jira searcher (template table)", function()
  before_each(function()
    search.jira = search_helpers.generate_custom_search({
      capture = "AXIE%-%d+",
      format = "https://jira.axieax.com/browse/%s",
    })
    assert.is_not.Nil(search.jira)
  end)

  after_each(function()
    search.jira = nil
  end)

  it("capture single", function()
    local content = "AXIE-1"
    local links = search.jira({ content = content })
    assert_tbl_same_any_order({
      "https://jira.axieax.com/browse/AXIE-1",
    }, links)
  end)

  it("capture multiple", function()
    local content = [[
      AXIE-1
      AXIE-12
      AXIE-123
      AXIE-1234
      AXIE-12345
      AXIE-123456
      AXIE-1234567
      AXIE-12345678
      AXIE-123456789
      AXIE-1234567890
    ]]
    local links = search.jira({ content = content })
    assert_tbl_same_any_order({
      "https://jira.axieax.com/browse/AXIE-1",
      "https://jira.axieax.com/browse/AXIE-12",
      "https://jira.axieax.com/browse/AXIE-123",
      "https://jira.axieax.com/browse/AXIE-1234",
      "https://jira.axieax.com/browse/AXIE-12345",
      "https://jira.axieax.com/browse/AXIE-123456",
      "https://jira.axieax.com/browse/AXIE-1234567",
      "https://jira.axieax.com/browse/AXIE-12345678",
      "https://jira.axieax.com/browse/AXIE-123456789",
      "https://jira.axieax.com/browse/AXIE-1234567890",
    }, links)
  end)

  it("invalid captures ignored", function()
    local content = [[
    AXIE
    AXIE1
    AXIE--123
    AXIE%-1
    AXIE-!
    AXIE-AXIE
    AXIE-ax
    ]]
    local links = search.jira({ content = content })
    assert_tbl_same_any_order({}, links)
  end)
end)

describe("overwrite default searcher", function()
  local builtin_search_buffer = search.buffer

  before_each(function()
    search.buffer = search_helpers.generate_custom_search({
      capture = "l.ve",
      format = "i-%s-testing",
    })
  end)

  after_each(function()
    search.buffer = builtin_search_buffer
  end)

  it("capture single", function()
    local content = "i love testing"
    local result = search.buffer({ content = content })
    assert_tbl_same_any_order({
      "i-love-testing",
    }, result)
  end)

  it("capture multiple", function()
    local content = "I live to see another day, I love Neovim"
    local result = search.buffer({ content = content })
    assert_tbl_same_any_order({
      "i-love-testing",
      "i-live-testing",
    }, result)
  end)
end)

describe("custom function", function()
  before_each(function()
    search.test = function(opts)
      return { opts.a or "default", opts.b or "default", opts.c or "default" }
    end
  end)

  after_each(function()
    search.test = nil
  end)

  it("capture one", function()
    local result = search.test({ a = "a" })
    assert_tbl_same_any_order({ "a", "default", "default" }, result)
  end)

  it("capture two", function()
    local result = search.test({ a = "a", c = "c" })
    assert_tbl_same_any_order({ "a", "c", "default" }, result)
  end)

  it("capture three", function()
    local result = search.test({ a = "a", b = "b", c = "c" })
    assert_tbl_same_any_order({ "a", "b", "c" }, result)
  end)

  it("ignore extra", function()
    local result = search.test({ a = "a", b = "b", c = "c", d = "d" })
    assert_tbl_same_any_order({ "a", "b", "c" }, result)
  end)
end)

describe("register custom search", function()
  it("captures git uris", function()
    search.git = search_helpers.generate_custom_search({
      capture = "git@[^%s]+%.git",
    })

    local content = [[
      git@github.com:axieax/urlview.nvim.git
      git@github.com:axieax/typo.nvim.git
    ]]

    local links = search.git({ content = content })
    assert_tbl_same_any_order({
      "git@github.com:axieax/urlview.nvim.git",
      "git@github.com:axieax/typo.nvim.git",
    }, links)
    search.git = nil
  end)

  it("captures ssh uris iwthout the prefix", function()
    search.ssh = search_helpers.generate_custom_search({
      capture = "ssh://([^%s]+)",
    })

    local content = [[
      ssh://git@github.com:axieax/urlview.nvim.git
      ssh://git@github.com:axieax/typo.nvim.git
      ssh://192.168.1.1
      ssh://192.168.1.1:4000
    ]]

    local links = search.ssh({ content = content })
    assert_tbl_same_any_order({
      "git@github.com:axieax/urlview.nvim.git",
      "git@github.com:axieax/typo.nvim.git",
      "192.168.1.1",
      "192.168.1.1:4000",
    }, links)
    search.ssh = nil
  end)

  it("captures ftp uris", function()
    search.ftp = search_helpers.generate_custom_search({
      capture = "ftp://[^%s]+",
    })

    local content = [[
      ftp://ftp.example.com
      ftp://user@host/%2Ffoo/bar.txt
      ftp://user:password@server/pathname;type=a
    ]]

    local links = search.ftp({ content = content })
    assert_tbl_same_any_order({
      "ftp://ftp.example.com",
      "ftp://user@host/%2Ffoo/bar.txt",
      "ftp://user:password@server/pathname;type=a",
    }, links)
    search.ftp = nil
  end)
end)


================================================
FILE: tests/urlview/capture_jump_spec.lua
================================================
local jump = require("urlview.jump")
local jump_helpers = require("tests.urlview.jump_helpers")
local set_cursor = jump_helpers.set_cursor
local create_buffer = jump_helpers.create_buffer
local teardown_windows = jump_helpers.teardown_windows
local jump_forwards = jump_helpers.jump_forwards
local jump_backwards = jump_helpers.jump_backwards

-- ASSUMPTION(cursor_pos): line numbers are 1-indexed, column numbers are 0-indexed

local examples = {
  empty = "",
  invalid = "hello",
  single_line_middle = "abc https://www.google.com def",
  standard_url = "https://www.google.com",
  multi_line_just_links = [[
https://www.google.com
https://www.github.com
https://www.amazon.com
https://www.reddit.com]],
  multi_line_sandwich = [[

https://www.google.com
]],
}

describe("line_match_positions unit tests", function()
  it("multiple substrings", function()
    local line = "abc abc abc"
    local expected_no_offset = { 0, 4, 8 }
    for offset = 0, 100 do
      local res = jump.line_match_positions(line, "abc", offset)
      local expected = vim.tbl_map(function(x)
        return x + offset
      end, expected_no_offset)
      vim.deep_equal(expected, res)
    end
  end)

  it("single URL", function()
    local url = examples.standard_url
    local res = jump.line_match_positions(url, url, 0)
    vim.deep_equal({ 0 }, res)
  end)

  it("correct single index", function()
    local url = examples.standard_url
    local line = examples.single_line_middle
    local res = jump.line_match_positions(line, url, 0)
    vim.deep_equal({ 4 }, res)
  end)
end)

describe("correct starting column", function()
  it("backwards no URL", function()
    local line = examples.invalid
    create_buffer(line)
    for col = 0, #line do
      local new_col = jump.correct_start_col(1, col, true)
      assert.equals(col, new_col)
    end
  end)

  it("backwards before URL", function()
    local line = examples.single_line_middle
    create_buffer(line)
    local url_start = 4
    for col = 0, url_start - 1 do
      local new_col = jump.correct_start_col(1, col, true)
      assert.equals(col, new_col)
    end
  end)

  it("backwards on start of URL", function()
    local line = examples.single_line_middle
    local url_start = 4
    create_buffer(line)
    local new_col = jump.correct_start_col(1, url_start, true)
    assert.equals(url_start - 1, new_col)
  end)

  it("backwards on rest of URL", function()
    local line = examples.single_line_middle
    create_buffer(line)
    local url_start = 4
    local url_end = url_start + #examples.standard_url
    for col = url_start + 1, url_end - 1 do
      local new_col = jump.correct_start_col(1, col, true)
      assert.equals(url_end, new_col)
    end
  end)

  it("backwards after URL", function()
    local line = examples.single_line_middle
    local url_start = 4
    local url_end = url_start + #examples.standard_url
    for col = url_end, #line do
      local new_col = jump.correct_start_col(1, col, true)
      assert.equals(col, new_col)
    end
  end)

  it("forwards no URL", function()
    local line = examples.invalid
    create_buffer(line)
    for col = 0, #line do
      local new_col = jump.correct_start_col(1, col, false)
      assert.equals(col, new_col)
    end
  end)

  it("forwards before URL", function()
    local line = examples.single_line_middle
    create_buffer(line)
    local url_start = 4
    for col = 0, url_start - 1 do
      local new_col = jump.correct_start_col(1, col, false)
      assert.equals(col, new_col)
    end
  end)

  it("forwards on URL", function()
    local line = examples.single_line_middle
    create_buffer(line)
    local url_start = 4
    local url_end = url_start + #examples.standard_url
    for col = url_start, url_end - 1 do
      local new_col = jump.correct_start_col(1, col, false)
      assert.equals(url_end, new_col)
    end
  end)

  it("forwards after URL", function()
    local line = examples.single_line_middle
    local url_start = 4
    local url_end = url_start + #examples.standard_url
    for col = url_end, #line do
      local new_col = jump.correct_start_col(1, col, false)
      assert.equals(col, new_col)
    end
  end)
end)

describe("backwards jump", function()
  after_each(teardown_windows)

  it("empty buffer", function()
    local content = examples.empty
    create_buffer(content, { 1, 0 })
    local res = jump_backwards()
    assert.is_nil(res)
  end)

  it("no URL", function()
    local content = examples.invalid
    create_buffer(content)
    for col = 0, #content do
      set_cursor({ 1, col })
      local res = jump_backwards()
      assert.is_nil(res)
    end
  end)

  it("just URL", function()
    local content = examples.standard_url
    create_buffer(content, { 1, 0 })
    local res = jump_backwards()
    assert.is_nil(res)

    for col = 1, #content do
      set_cursor({ 1, col })
      res = jump_backwards()
      vim.deep_equal({ 1, 0 }, res)
    end
  end)

  it("invalid jump before URL and start of URL", function()
    local content = examples.single_line_middle
    local url_start = 4
    create_buffer(content)
    for col = 0, url_start do
      set_cursor({ 1, col })
      local res = jump_backwards()
      assert.is_nil(res)
    end
  end)

  it("simple jump to start of URL", function()
    local content = examples.single_line_middle
    local url_start = 4
    create_buffer(content)
    for col = url_start + 1, #content do
      set_cursor({ 1, col })
      local res = jump_backwards()
      vim.deep_equal({ 1, url_start }, res)
    end
  end)

  it("multiline jump from anywhere in line", function()
    local content = examples.multi_line_just_links
    local url_length = #examples.standard_url
    create_buffer(content)

    -- invalid jump
    set_cursor({ 1, 0 })
    local res = jump_backwards()
    assert.is_nil(res)

    -- jump to start of previous URL
    local line_last = 4
    for line = 1, line_last do
      local expected = { line, 0 }
      for col = 1, url_length do
        set_cursor({ line, col })
        res = jump_backwards()
        vim.deep_equal(expected, res)
      end
      if line ~= line_last then
        set_cursor({ line + 1, 0 })
        vim.deep_equal(expected, res)
      end
    end
  end)

  it("multiline sandwich", function()
    local content = examples.multi_line_sandwich
    local url_length = #examples.standard_url
    create_buffer(content)

    -- invalid jumps
    for line = 1, 2 do
      set_cursor({ line, 0 })
      local res = jump_backwards()
      assert.is_nil(res)
    end

    -- jumps to correct position
    local expected = { 2, 0 }
    for col = 1, url_length do
      set_cursor({ 2, col })
      local res = jump_backwards()
      vim.deep_equal(expected, res)
    end

    -- line 3 jumps to line 2
    set_cursor({ 3, 0 })
    local res = jump_backwards()
    vim.deep_equal(expected, res)
  end)

  it("jump chain", function()
    local content = examples.multi_line_just_links
    local url_length = #examples.standard_url
    create_buffer(content, { 4, url_length })

    for line = 4, 1, -1 do
      local res = jump_backwards()
      vim.deep_equal({ line, 0 }, res)
    end

    local res = jump_backwards()
    assert.is_nil(res)
  end)
end)

describe("forwards jump", function()
  after_each(teardown_windows)

  it("empty buffer", function()
    local content = examples.empty
    create_buffer(content, { 1, 0 })
    local res = jump_forwards()
    assert.is_nil(res)
  end)

  it("no URL", function()
    local content = examples.invalid
    create_buffer(content)
    for col = 0, #content do
      set_cursor({ 1, col })
      local res = jump_forwards()
      assert.is_nil(res)
    end
  end)

  it("just URL", function()
    local content = examples.standard_url
    create_buffer(content)
    for col = 0, #content do
      set_cursor({ 1, col })
      local res = jump_forwards()
      assert.is_nil(res)
    end
  end)

  it("same line single", function()
    local content = examples.single_line_middle
    local url_start = 4
    create_buffer(content)
    for col = 0, url_start - 1 do
      set_cursor({ 1, col })
      local res = jump_forwards()
      vim.deep_equal({ 1, url_start }, res)
    end
  end)

  it("on last URL + no more", function()
    local content = examples.single_line_middle
    local url_start = 4
    create_buffer(content)
    for col = url_start, #content do
      set_cursor({ 1, col })
      local res = jump_forwards()
      assert.is_nil(res)
    end
  end)

  it("multiline jump from anywhere in line", function()
    local content = examples.multi_line_just_links
    local url_length = #examples.standard_url
    create_buffer(content)
    -- jumps to next line
    for line = 1, 3 do
      for col = 0, url_length do
        set_cursor({ line, col })
        local res = jump_forwards()
        vim.deep_equal({ line + 1, 0 }, res)
      end
    end
    -- last line
    for col = 0, url_length do
      set_cursor({ 4, col })
      local res = jump_forwards()
      assert.is_nil(res)
    end
  end)

  it("multiline sandwich", function()
    local content = examples.multi_line_sandwich
    local url_length = #examples.standard_url
    create_buffer(content, { 1, 0 })

    -- line 1 jumps to line 2
    local res = jump_forwards()
    vim.deep_equal({ 2, 0 }, res)

    -- line 2 onwards invalid
    for col = 0, url_length do
      set_cursor({ 2, col })
      res = jump_forwards()
      assert.is_nil(res)
    end
    set_cursor({ 3, 0 })
    res = jump_forwards()
    assert.is_nil(res)
  end)

  it("jump chain", function()
    local content = examples.multi_line_just_links
    create_buffer(content, { 1, 0 })

    for line = 1, 3 do
      local res = jump_forwards()
      vim.deep_equal({ line + 1, 0 }, res)
    end

    local res = jump_forwards()
    assert.is_nil(res)
  end)
end)


================================================
FILE: tests/urlview/capture_mock_spec.lua
================================================
local urlview = require("urlview")
local reset_config = require("urlview.config.helpers").reset_defaults
local search = require("urlview.search")
local search_helpers = require("urlview.search.helpers")

describe("mock vim.ui.select", function()
  local default_prefix = "test-"
  before_each(function()
    urlview.setup({ default_prefix = default_prefix })
    search.test = search_helpers.generate_custom_search({
      capture = "%w+",
      format = "%s",
    })
    assert.is_not.Nil(search.test)
  end)

  after_each(function()
    search.test = nil
    reset_config()
  end)

  local original_ui_select = vim.ui.select

  it("unique, sorted", function()
    local content = "pears watermelon banana apple apple peach apricot watermelon"
    local expected = vim.tbl_map(function(v)
      return default_prefix .. v
    end, { "apple", "apricot", "banana", "peach", "pears", "watermelon" })

    vim.ui.select = function(items)
      assert.same(expected, items)
    end

    urlview.search("test", { content = content, unique = true, sorted = true })
    vim.ui.select = original_ui_select
  end)
end)

describe("mock telescope", function() end)


================================================
FILE: tests/urlview/capture_multiple_spec.lua
================================================
local urlview = require("urlview")
local extract_links_from_content = require("urlview.search.helpers").content
local reset_config = require("urlview.config.helpers").reset_defaults
local assert_tbl_same_any_order = require("tests.urlview.helpers").assert_tbl_same_any_order

describe("multiple captures", function()
  it("separate lines", function()
    local content = [[
      http://google.com
      https://www.google.com
    ]]
    local result = extract_links_from_content(content)
    assert_tbl_same_any_order({ "http://google.com", "https://www.google.com" }, result)
  end)

  it("same line", function()
    local content = "http://google.com https://www.github.com"
    local result = extract_links_from_content(content)
    assert_tbl_same_any_order({ "http://google.com", "https://www.github.com" }, result)
  end)
end)

describe("unique captures", function()
  it("same link", function()
    local content = [[
      http://google.com
      http://google.com
    ]]
    local result = extract_links_from_content(content)
    assert_tbl_same_any_order({ "http://google.com" }, result)
  end)

  it("different prefix / uri protocol, same default prefix", function()
    urlview.setup({
      default_prefix = "https://",
    })

    local content = [[
      https://www.google.com
      www.google.com
    ]]
    local result = extract_links_from_content(content)
    assert_tbl_same_any_order({ "https://www.google.com" }, result)

    reset_config()
  end)

  it("different prefix / uri protocol, prefer specified", function()
    urlview.setup({
      default_prefix = "http://",
    })

    local content = [[
      https://www.google.com
      www.google.com
    ]]
    local result = extract_links_from_content(content)
    assert_tbl_same_any_order({ "https://www.google.com" }, result)

    reset_config()
  end)

  it("different paths", function()
    local content = [[
      https://www.google.com/search?q=vim
      https://www.google.com/search?q=nvim
    ]]
    local result = extract_links_from_content(content)
    assert_tbl_same_any_order({ "https://www.google.com/search?q=vim", "https://www.google.com/search?q=nvim" }, result)
  end)
end)


================================================
FILE: tests/urlview/capture_process_spec.lua
================================================
local urlview = require("urlview")
local search = require("urlview.search")
local search_helpers = require("urlview.search.helpers")
local reset_config = require("urlview.config.helpers").reset_defaults
local assert_tbl_same_any_order = require("tests.urlview.helpers").assert_tbl_same_any_order
local prepare_links = require("urlview.utils").process_links
local extract_links_from_content = search_helpers.content

describe("HTTP(s) protocol fill in", function()
  local default_prefix = "https://"
  before_each(function()
    urlview.setup({ default_prefix = default_prefix })
  end)

  after_each(function()
    reset_config()
  end)

  it("link with http protocol", function()
    local url = "http://example.com"
    local links = extract_links_from_content(url)
    local prepared_links = prepare_links(links)
    assert.same({ url }, prepared_links)
  end)

  it("link with https protocol", function()
    local url = "https://example.com"
    local links = extract_links_from_content(url)
    local prepared_links = prepare_links(links)
    assert.same({ url }, prepared_links)
  end)

  it("link missing either protocol", function()
    local url = "www.example.com"
    local links = extract_links_from_content(url)
    local prepared_links = prepare_links(links)
    assert.same({ default_prefix .. url }, prepared_links)
  end)
end)

describe("unique links", function()
  before_each(function()
    urlview.setup({
      default_prefix = "",
    })
    search.test = search_helpers.generate_custom_search({
      capture = "%d",
      format = "%s",
    })
    assert.is_not.Nil(search.test)
  end)

  after_each(function()
    search.test = nil
    reset_config()
  end)

  local content = "1 1 2 2 3 3 4 4 5 5"

  it("keep duplicates", function()
    local links = search.test({ content = content })
    local prepared_links = prepare_links(links, { unique = false })
    assert_tbl_same_any_order({ "1", "1", "2", "2", "3", "3", "4", "4", "5", "5" }, prepared_links)
  end)

  it("filter duplicates", function()
    local links = search.test({ content = content })
    local prepared_links = prepare_links(links, { unique = true })
    assert.same({ "1", "2", "3", "4", "5" }, prepared_links)
  end)
end)

describe("sorted links", function()
  local default_prefix = "https://"
  before_each(function()
    urlview.setup({ default_prefix = default_prefix, sort = true })
  end)

  after_each(function()
    reset_config()
  end)

  it("URLs missing protocol fixed and sorted alphabetically", function()
    local content = [[
    www.google.com
    https://google.com
    https://github.com/axieax/urlview.nvim
    www.example.com
    http://github.com/helloM
    http://github.com/helloP
    http://github.com/hellon
    ]]

    local links = extract_links_from_content(content)
    local prepared_links = prepare_links(links)
    local expected = {
      "http://github.com/helloM",
      "http://github.com/hellon",
      "http://github.com/helloP",
      "https://github.com/axieax/urlview.nvim",
      "https://google.com",
      default_prefix .. "www.example.com",
      default_prefix .. "www.google.com",
    }

    assert.same(expected, prepared_links)
  end)
end)


================================================
FILE: tests/urlview/capture_single_spec.lua
================================================
local urlview = require("urlview")
local reset_config = require("urlview.config.helpers").reset_defaults
local assert_no_match = require("tests.urlview.helpers").assert_no_match
local assert_single_match = require("tests.urlview.helpers").assert_single_match

describe("no capture", function()
  it("empty string", function()
    assert_no_match("")
  end)

  it("random", function()
    assert_no_match("asdfwiueyfksdlckvj")
  end)

  it("com only", function()
    assert_no_match("test.com")
  end)

  it("com path", function()
    assert_no_match("test.com/idk")
  end)
end)

describe("url-only simple capture", function()
  local default_prefix = "https://"
  before_each(function()
    urlview.setup({
      default_prefix = default_prefix,
    })
  end)

  after_each(function()
    reset_config()
  end)

  it("http capture", function()
    local url = "http://google.com"
    assert_single_match(url, url)
  end)

  it("https capture", function()
    local url = "https://google.com"
    assert_single_match(url, url)
  end)

  it("www capture", function()
    local url = "www.google.com"
    assert_single_match(url, url)
  end)

  it("http www capture", function()
    local url = "http://www.google.com"
    assert_single_match(url, url)
  end)

  it("https www capture", function()
    local url = "https://www.google.com"
    assert_single_match(url, url)
  end)

  it("trailing slash", function()
    local url = "www.google.com/"
    assert_single_match(url, url)
  end)
end)

describe("url-only path capture", function()
  local default_prefix = "http://"
  before_each(function()
    urlview.setup({
      default_prefix = default_prefix,
    })
  end)

  after_each(function()
    reset_config()
  end)

  it("lol php capture", function()
    local url = "https://who.even.uses/index.php"
    assert_single_match(url, url)
  end)

  it("https path capture", function()
    local url = "https://google.com/path/to/idk"
    assert_single_match(url, url)
  end)

  it("www path capture", function()
    local url = "www.google.com/path/to/idk"
    assert_single_match(url, url)
  end)

  it("url-encoded path query capture", function()
    local url = "www.google.com/P%40%2Bh%20t35T%2F/1dk%3F?q=%3Da%25%3B"
    assert_single_match(url, url)
  end)

  it("query capture", function()
    local url = "https://example.com/path/to/idk?q=axie&ax"
    assert_single_match(url, url)
  end)
end)


================================================
FILE: tests/urlview/helpers.lua
================================================
local M = {}

local extract_links_from_content = require("urlview.search.helpers").content

function M.assert_no_match(content)
  local result = extract_links_from_content(content)
  assert.equals(0, #result)
end

function M.assert_single_match(url, expected_url)
  local result = extract_links_from_content(url)
  assert.same({ expected_url }, result)
end

function M.assert_tbl_same_any_order(expected, actual)
  assert.same(#expected, #actual)
  for _, e in ipairs(expected) do
    assert.truthy(vim.tbl_contains(actual, e))
  end
end

return M


================================================
FILE: tests/urlview/jump_helpers.lua
================================================
local M = {}

local jump = require("urlview.jump")
local utils = require("urlview.utils")

local active_windows = {}

function M.set_cursor(pos)
  vim.api.nvim_win_get_cursor = function()
    return pos
  end
end

function M.create_buffer(content, cursor_pos)
  local lines = vim.split(content, "\n")
  local bufnr = vim.api.nvim_create_buf(false, true)
  vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines)
  local winnr = vim.api.nvim_open_win(bufnr, false, {
    relative = "editor",
    row = 0,
    col = 0,
    width = 1,
    height = 1,
    zindex = 1,
  })
  active_windows[winnr] = true

  vim.fn.getline = function(line_no)
    return lines[line_no]
  end
  vim.api.nvim_buf_line_count = function()
    return #lines
  end

  utils.fallback(cursor_pos, { 1, 0 })
  M.set_cursor(cursor_pos)
end

function M.teardown_windows()
  local windows = vim.tbl_keys(active_windows)
  for _, winnr in ipairs(windows) do
    if vim.api.nvim_win_is_valid(winnr) then
      vim.api.nvim_win_close(winnr, true)
      active_windows[winnr] = nil
    end
  end
end

function M.jump_forwards()
  local res = jump.find_url(0, false)
  if res then
    M.set_cursor(res)
  end
  return res
end

function M.jump_backwards()
  local res = jump.find_url(0, true)
  if res then
    M.set_cursor(res)
  end
  return res
end

return M


================================================
FILE: vim.toml
================================================
# SOURCE: https://github.com/jose-elias-alvarez/null-ls.nvim/blob/main/vim.toml
[selene]
base = "lua51"
name = "vim"

[vim]
any = true

[[describe.args]]
type = "string"

[[describe.args]]
type = "function"

[[it.args]]
type = "string"

[[it.args]]
type = "function"

[[before_each.args]]
type = "function"

[[after_each.args]]
type = "function"

[assert.is_not]
any = true

[assert.matches]
any = true

[assert.has_error]
any = true

[[assert.equals.args]]
type = "any"

[[assert.equals.args]]
type = "any"

[[assert.equals.args]]
type = "any"
required = false

[[assert.same.args]]
type = "any"

[[assert.same.args]]
type = "any"

[[assert.truthy.args]]
type = "any"

[[assert.falsy.args]]
type = "any"

[[assert.spy.args]]
type = "any"

[[assert.stub.args]]
type = "any"

[[assert.is_nil.args]]
type = "any"
Download .txt
gitextract_1w34gv51/

├── .github/
│   └── workflows/
│       └── default.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── doc/
│   └── urlview.txt
├── lua/
│   └── urlview/
│       ├── actions.lua
│       ├── command.lua
│       ├── config/
│       │   ├── constants.lua
│       │   ├── default.lua
│       │   ├── helpers.lua
│       │   └── init.lua
│       ├── init.lua
│       ├── jump.lua
│       ├── pickers.lua
│       ├── search/
│       │   ├── helpers.lua
│       │   ├── init.lua
│       │   └── validation.lua
│       └── utils.lua
├── selene.toml
├── stylua.toml
├── tests/
│   ├── init.vim
│   └── urlview/
│       ├── capture_custom_searches_spec.lua
│       ├── capture_jump_spec.lua
│       ├── capture_mock_spec.lua
│       ├── capture_multiple_spec.lua
│       ├── capture_process_spec.lua
│       ├── capture_single_spec.lua
│       ├── helpers.lua
│       └── jump_helpers.lua
└── vim.toml
Condensed preview — 31 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (83K chars).
[
  {
    "path": ".github/workflows/default.yml",
    "chars": 1151,
    "preview": "name: default\n\non:\n  pull_request:\n  push:\n    branches: [main]\n\njobs:\n  stylua:\n    name: Check code style\n    runs-on:"
  },
  {
    "path": ".gitignore",
    "chars": 9,
    "preview": "doc/tags\n"
  },
  {
    "path": "LICENSE",
    "chars": 1067,
    "preview": "MIT License\n\nCopyright (c) 2022 Andrew Xie\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
  },
  {
    "path": "Makefile",
    "chars": 128,
    "preview": "test:\n\tnvim --headless --noplugin -u tests/init.vim -c \"PlenaryBustedDirectory tests/urlview {minimal_init = 'tests/init"
  },
  {
    "path": "README.md",
    "chars": 6379,
    "preview": "<h1 align=\"center\">🔎 urlview.nvim</h1>\n<p align=\"center\"><i>Find and display URLs from a variety of search contexts</i><"
  },
  {
    "path": "doc/urlview.txt",
    "chars": 14261,
    "preview": "*urlview.txt*             Find and display URLs from a variety of search contexts\n\n====================================="
  },
  {
    "path": "lua/urlview/actions.lua",
    "chars": 2463,
    "preview": "local M = {}\n\nlocal utils = require(\"urlview.utils\")\nlocal config = require(\"urlview.config\")\n\n--- Use command to open t"
  },
  {
    "path": "lua/urlview/command.lua",
    "chars": 2033,
    "preview": "local M = {}\n\nlocal utils = require(\"urlview.utils\")\nlocal search_contexts = require(\"urlview.search\")\nlocal search_vali"
  },
  {
    "path": "lua/urlview/config/constants.lua",
    "chars": 277,
    "preview": "local constants = {\n  -- SEE: lua pattern matching (https://riptutorial.com/lua/example/20315/lua-pattern-matching)\n  --"
  },
  {
    "path": "lua/urlview/config/default.lua",
    "chars": 1255,
    "preview": "local default_config = {\n  -- Prompt title (`<context> <default_title>`, e.g. `Buffer Links:`)\n  default_title = \"Links:"
  },
  {
    "path": "lua/urlview/config/helpers.lua",
    "chars": 312,
    "preview": "local M = {}\n\nlocal config = require(\"urlview.config\")\nlocal default_config = require(\"urlview.config.default\")\n\nfunctio"
  },
  {
    "path": "lua/urlview/config/init.lua",
    "chars": 256,
    "preview": "local M = {\n  _options = {},\n}\n\nreturn setmetatable(M, {\n  -- general get and set operations refer to the internal table"
  },
  {
    "path": "lua/urlview/init.lua",
    "chars": 1917,
    "preview": "local M = {}\n\nlocal actions = require(\"urlview.actions\")\nlocal command = require(\"urlview.command\")\nlocal config = requi"
  },
  {
    "path": "lua/urlview/jump.lua",
    "chars": 5199,
    "preview": "local M = {}\n\n-- NOTE: line numbers are 1-indexed, column numbers are 0-indexed\n\nlocal utils = require(\"urlview.utils\")\n"
  },
  {
    "path": "lua/urlview/pickers.lua",
    "chars": 2701,
    "preview": "local M = {}\n\nlocal utils = require(\"urlview.utils\")\n\n--- Displays items using the vim.ui.select picker\n---@param items "
  },
  {
    "path": "lua/urlview/search/helpers.lua",
    "chars": 4107,
    "preview": "local M = {}\n\nlocal constants = require(\"urlview.config.constants\")\nlocal utils = require(\"urlview.utils\")\n\n--- Extracts"
  },
  {
    "path": "lua/urlview/search/init.lua",
    "chars": 1921,
    "preview": "local M = {}\n\nlocal utils = require(\"urlview.utils\")\nlocal search_helpers = require(\"urlview.search.helpers\")\n\n-- NOTE: "
  },
  {
    "path": "lua/urlview/search/validation.lua",
    "chars": 3204,
    "preview": "local M = {}\n\nlocal utils = require(\"urlview.utils\")\nlocal config = require(\"urlview.config\")\n\n--- Validates `opts` and "
  },
  {
    "path": "lua/urlview/utils.lua",
    "chars": 2730,
    "preview": "local M = {}\n\nlocal config = require(\"urlview.config\")\nlocal constants = require(\"urlview.config.constants\")\n\nM.os = vim"
  },
  {
    "path": "selene.toml",
    "chars": 12,
    "preview": "std = \"vim\"\n"
  },
  {
    "path": "stylua.toml",
    "chars": 142,
    "preview": "column_width = 120\nline_endings = \"Unix\"\nindent_type = \"Spaces\"\nindent_width = 2\nquote_style = \"AutoPreferDouble\"\nno_cal"
  },
  {
    "path": "tests/init.vim",
    "chars": 93,
    "preview": "set noswapfile\nset rtp+=../plenary.nvim\nset rtp+=../urlview.nvim\nruntime! plugin/plenary.vim\n"
  },
  {
    "path": "tests/urlview/capture_custom_searches_spec.lua",
    "chars": 5204,
    "preview": "local search = require(\"urlview.search\")\nlocal search_helpers = require(\"urlview.search.helpers\")\nlocal assert_tbl_same_"
  },
  {
    "path": "tests/urlview/capture_jump_spec.lua",
    "chars": 9826,
    "preview": "local jump = require(\"urlview.jump\")\nlocal jump_helpers = require(\"tests.urlview.jump_helpers\")\nlocal set_cursor = jump_"
  },
  {
    "path": "tests/urlview/capture_mock_spec.lua",
    "chars": 1154,
    "preview": "local urlview = require(\"urlview\")\nlocal reset_config = require(\"urlview.config.helpers\").reset_defaults\nlocal search = "
  },
  {
    "path": "tests/urlview/capture_multiple_spec.lua",
    "chars": 2173,
    "preview": "local urlview = require(\"urlview\")\nlocal extract_links_from_content = require(\"urlview.search.helpers\").content\nlocal re"
  },
  {
    "path": "tests/urlview/capture_process_spec.lua",
    "chars": 3191,
    "preview": "local urlview = require(\"urlview\")\nlocal search = require(\"urlview.search\")\nlocal search_helpers = require(\"urlview.sear"
  },
  {
    "path": "tests/urlview/capture_single_spec.lua",
    "chars": 2405,
    "preview": "local urlview = require(\"urlview\")\nlocal reset_config = require(\"urlview.config.helpers\").reset_defaults\nlocal assert_no"
  },
  {
    "path": "tests/urlview/helpers.lua",
    "chars": 548,
    "preview": "local M = {}\n\nlocal extract_links_from_content = require(\"urlview.search.helpers\").content\n\nfunction M.assert_no_match(c"
  },
  {
    "path": "tests/urlview/jump_helpers.lua",
    "chars": 1320,
    "preview": "local M = {}\n\nlocal jump = require(\"urlview.jump\")\nlocal utils = require(\"urlview.utils\")\n\nlocal active_windows = {}\n\nfu"
  },
  {
    "path": "vim.toml",
    "chars": 811,
    "preview": "# SOURCE: https://github.com/jose-elias-alvarez/null-ls.nvim/blob/main/vim.toml\n[selene]\nbase = \"lua51\"\nname = \"vim\"\n\n[v"
  }
]

About this extraction

This page contains the full source code of the axieax/urlview.nvim GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 31 files (76.4 KB), approximately 20.1k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!