-- vim:foldmethod=marker

local function get_python_venv_path() --{{{1
    return vim.fn.stdpath('config') .. '/.venv/bin/python'
end

vim.g.python3_host_prog = get_python_venv_path()

-- GENERAL SETTINGS {{{1
vim.cmd [=[
    set autowriteall
    set exrc
    set secure
    set clipboard=unnamedplus
    set tabstop=4
    set shiftwidth=0
    set rnu
    set nu
    set path=**
    set nowrap
    set shiftround
    set expandtab
    set nohlsearch
    set incsearch
    set guicursor=n-v-c:block-Cursor
    set cursorline
    set noswapfile
    set list
    set tags+=~/tags

    nnoremap ,co :copen<CR>
    nnoremap ,cc :cclose<CR>
    nnoremap ,cq :call setqflist([])<CR>:cclose<CR>
    nnoremap <c-n> :cnext<CR>zz
    nnoremap <c-p> :cprevious<CR>zz
    nnoremap ,cu :colder<CR>
    nnoremap ,cr :cnewer<CR>
    nnoremap ,h H
    nnoremap ,l L
    nnoremap H ^
    nnoremap L $
    xnoremap H ^
    xnoremap L $
    nnoremap <C-a> <Nop>
    nnoremap <C-x> <Nop>
    nnoremap ,a <C-a>
    nnoremap ,x <C-x>
    nnoremap ,rl :checktime<CR>
    nnoremap ,m :wa<CR>:make<CR>

    nnoremap ,cD :call setqflist(filter(getqflist(), 'v:val != getqflist()[getqflist({"idx": 0}).idx - 1]'))<CR>

    nnoremap ,t <c-w>v<c-w>l:terminal<CR>a

    " Don't include curdir, it just causes pain.
    set viewoptions=folds,cursor
    autocmd BufWinLeave *.* silent! mkview 
    autocmd BufWinEnter *.* silent! loadview 

    nnoremap <c-h> <c-w>h
    nnoremap <c-j> <c-w>j
    nnoremap <c-k> <c-w>k
    nnoremap <c-l> <c-w>l

    nnoremap <c-d> <c-d>zz
    nnoremap <c-u> <c-u>zz

    tnoremap <c-w>c <c-\><c-n><c-w>c

    autocmd TextYankPost * silent! lua vim.highlight.on_yank {higroup='Visual', timeout=100}
    autocmd BufEnter *__virtual* setlocal buftype=nofile bufhidden=hide noswapfile

    " Disable STUPID default rust plugin. And others
    filetype plugin off

    " remove annoying and bad indentation
    autocmd FileType * setlocal indentexpr=
]=]

vim.keymap.set('n', ',cf', function()
    local qf = vim.fn.getqflist()
    for i, item in ipairs(qf) do
        if item.valid == 1 then
            vim.cmd('cc '..i)
            return
        end
    end
    print('no jumpable items')
end)

vim.keymap.set('n', ',cl', function()
    local qf = vim.fn.getqflist()
    for i = #qf, 1, -1 do
        local item = qf[i]

        if item.valid == 1 then
            vim.cmd('cc '..i)
            return
        end
    end
    print('no jumpable items')
end)

vim.keymap.set('n', ',ct', function()
    vim.fn.system('ctags -R .')
end)
do
    local function escaped(text)
        local _escaped = vim.fn.escape(text, "\\/.*$^~[]")
        local pattern = "\\V" .. _escaped
        return pattern
    end
    ---@param pattern string
    local function recursive_literal_vimgrep(pattern)
        vim.cmd("vimgrep /" .. pattern .. "/ **/*")
    end
    ---@param keymap string
    ---@param search_for fun(): string
    local function search_for_in_same_filetype(keymap, search_for)
        vim.keymap.set('n', keymap, function()
            recursive_literal_vimgrep(search_for())
        end, { desc = "Search for word under cursor in same filetype" })
    end

    search_for_in_same_filetype(',vs', function() return escaped(vim.fn.expand("<cword>")) end)
    search_for_in_same_filetype(',vS', function() return escaped(vim.fn.expand("<cWORD>")) end)
    local file_specific = {
        odin=function()
            search_for_in_same_filetype(',vd', function()
                local word = vim.fn.expand("<cword>")
                local pattern = "\\v"..word.." *: *[:=]"
                return pattern
            end)
        end
    }
    vim.api.nvim_create_autocmd("FileType", {
        callback=function()
            local conf = file_specific[vim.bo.filetype]
            if conf then conf() end
        end
    })
end

vim.api.nvim_create_autocmd({
    'BufRead',
    'BufNewFile'
}, {
    pattern = {'*.h'},
    callback = function()
        vim.bo.filetype = 'c'
    end
})

-- FILE SPECIFIC AND AUTOCMDS {{{1
local file_specific = {
    c = function()
        vim.bo.expandtab = false
    end,
    make = function()
        vim.bo.expandtab = false
    end,
    tsv = function()
        vim.bo.tabstop = 32
        vim.bo.expandtab = false
    end,
    odin = function()
        vim.bo.expandtab = false
    end,
    go = function()
        vim.bo.expandtab = false
    end,
    yaml = function()
        vim.bo.tabstop = 2
    end
}

vim.api.nvim_create_autocmd('BufEnter', {
    callback = function()
        local conf = file_specific[vim.bo.filetype]
        if conf then conf() end
    end,
})

-- CENTER TEXT "zen mode" {{{1
do
    -- one shared statusline looks better for the split.
    vim.o.laststatus = 3

    local ID = '4f2de2e3-a1bf-481f-919c-7f68ec6511c9'
    local function is_padding_window(win)
        local ok, _ = pcall(vim.api.nvim_buf_get_var, vim.api.nvim_win_get_buf(win), ID)
        return ok
    end

    local function calculate_padding()
        local screen_width = vim.o.columns
        local padding = math.floor((screen_width / 2 - 84 / 2) + 0.5)
        if padding <= 0 then
            return
        end
        return padding
    end

    local function get_padding_window()
        local windows = vim.api.nvim_list_wins()
        for _, win in ipairs(windows) do
            if is_padding_window(win) then
                return win
            end
        end
        return nil
    end

    local padding_buffer = _G[ID]
    if padding_buffer == nil then
        padding_buffer = vim.api.nvim_create_buf(false, true)
        vim.api.nvim_set_option_value('buftype', 'nofile', {buf = padding_buffer})
        vim.api.nvim_set_option_value('modifiable', false, {buf = padding_buffer})
        vim.api.nvim_create_autocmd('WinEnter', {
            callback = function()
                local curr_buff = vim.api.nvim_get_current_buf()
                if curr_buff == padding_buffer then
                    if #vim.api.nvim_list_wins() == 1 then
                        vim.cmd"q"
                    end
                    local padding_window = vim.api.nvim_get_current_win()

                    vim.cmd"wincmd p"
                    local prev_win = vim.api.nvim_get_current_win()
                    local padding_window_info = vim.fn.getwininfo(padding_window)[1]
                    local prev_win_info = vim.fn.getwininfo(prev_win)[1]
                    if padding_window ~= prev_win and padding_window_info.wincol ~= prev_win_info.wincol then
                        return
                    end

                    local windows = vim.api.nvim_list_wins()
                    local leftmost_non_padding_window = nil
                    local min_col = math.huge
                    for _, win in ipairs(windows) do
                        local info = vim.fn.getwininfo(win)[1]
                        if info.wincol < min_col and padding_window_info.wincol < info.wincol then
                            min_col = info.wincol
                            leftmost_non_padding_window = win
                        end
                    end
                    if leftmost_non_padding_window ~= nil then
                        vim.api.nvim_set_current_win(leftmost_non_padding_window)
                    end
                end
            end,
        })
        vim.api.nvim_create_autocmd('VimResized', {
            callback = function()
                local padding_window = get_padding_window()
                if padding_window ~= nil then
                    local padding = calculate_padding()
                    if padding == nil then
                        vim.api.nvim_win_close(padding_window, true)
                    else
                        vim.api.nvim_win_set_width(padding_window, padding)
                    end
                end
                vim.cmd"wincmd ="
            end,
        })
        vim.api.nvim_buf_set_var(padding_buffer, ID, true)
        _G[ID] = padding_buffer
    end

    local function toggle_padding()
        local padding_window = get_padding_window()
        if padding_window == nil then
            vim.cmd"only"
            local padding = calculate_padding()
            if padding == nil then
                return
            end
            local win = vim.api.nvim_open_win(padding_buffer, false, {
                split = 'left',
                win = -1,
                width = padding,
            })
            vim.api.nvim_set_option_value('number', false, {win = win})
            vim.api.nvim_set_option_value('relativenumber', false, {win = win})
            vim.api.nvim_set_option_value('cursorline', false, {win = win})
            vim.api.nvim_set_option_value('winfixwidth', true, {win = win})
            vim.api.nvim_set_option_value("fillchars", "eob: ", { win = win })
            vim.cmd"wincmd ="
        else
            vim.api.nvim_win_close(padding_window, true)
        end
    end

    vim.keymap.set('n', ',z', toggle_padding)
end

-- auto select compiler {{{1
do
    local compiler_mapping = {
        dotnet={'*.csproj', '*.sln'},
        basedpyright={'requirements.txt', 'pyproject.toml'},
        go={'go.mod'},
        gcc={'*.c'},
        ['g++']={'*.cpp'},
        cargo={'Cargo.toml'},
    }

    ---@param dir string?
    ---@param glob_pattern string
    ---@return boolean
    local function file_or_folder_exists(dir, glob_pattern)
        if dir == nil then return false end
        return vim.fn.globpath(dir, glob_pattern, false, false, false) ~= ''
    end

    local all_folders_till_root = nil
    do
        local cwd = vim.fn.getcwd()
        local folders = {cwd} -- array of all folders from cwd to root
        for dir in vim.fs.parents(cwd) do
            table.insert(folders, dir)
        end

        ---@param _ any
        ---@param i integer
        ---@return integer?
        ---@return string?
        local function iter(_, i)
            if file_or_folder_exists(folders[i], '.git') then
                return nil
            end
            i = i + 1
            local f = folders[i]
            if f == nil then
                return nil
            end
            return i, f
        end

        ---return iterator starting from cwd and going upwards until the project
        ---root is found by looking for the .git folder.
        all_folders_till_root = function()
            return iter, nil, 0
        end
    end

    ---@param glob_pattern string
    ---@return boolean
    local function look_for_file(glob_pattern)
        for _, dir in all_folders_till_root() do
            if file_or_folder_exists(dir, glob_pattern) then
                return true
            end
        end
        return false
    end

    local function auto_select_compiler()
        for compiler, glob_patterns in pairs(compiler_mapping) do
            for _, glob_pattern in ipairs(glob_patterns) do
                if look_for_file(glob_pattern) then
                    vim.cmd('compiler! '..compiler)
                end
            end
        end
        if look_for_file('justfile') then
            vim.go.makeprg = 'just'
        end
    end

    auto_select_compiler()
    vim.api.nvim_create_autocmd('DirChanged', {
        callback = auto_select_compiler,
    })
end


-- LAZY.NVIM BOOTSTRAP {{{1
local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim'
if not (vim.uv or vim.loop).fs_stat(lazypath) then
  vim.fn.system({
    'git',
    'clone',
    '--filter=blob:none',
    'https://github.com/folke/lazy.nvim.git',
    '--branch=stable', -- latest stable release
    lazypath,
  })
end
vim.opt.rtp:prepend(lazypath)

require'lazy'.setup{ --{{{1
    { "romus204/tree-sitter-manager.nvim", --{{{2
    dependencies = {}, -- tree-sitter CLI must be installed system-wide
    config = function()
        require("tree-sitter-manager").setup({
            auto_install = true
        })
    end
    },
    { 'stevearc/oil.nvim', --{{{2
        ---@module 'oil'
        ---@type oil.SetupOpts
        opts = {},
        dependencies = { { 'echasnovski/mini.icons', opts = {} } },
        lazy = false,
        config = function ()
            require('oil').setup({
                default_file_explorer = true,
                columns = {
                    'icon',
                    'permissions',
                    'size',
                    'mtime',
                },
                buf_options = {
                    buflisted = false,
                    bufhidden = 'hide',
                },
                win_options = {
                    wrap = false,
                    signcolumn = 'no',
                    cursorcolumn = false,
                    foldcolumn = '0',
                    spell = false,
                    list = false,
                    conceallevel = 3,
                    concealcursor = 'nvic',
                },
                delete_to_trash = false,
                skip_confirm_for_simple_edits = false,
                prompt_save_on_select_new_entry = true,
                cleanup_delay_ms = 2000,
                lsp_file_methods = {
                    enabled = true,
                    timeout_ms = 1000,
                    autosave_changes = false,
                },
                constrain_cursor = 'editable',
                watch_for_changes = true,
                keymaps = {
                    ['g?'] = { 'actions.show_help', mode = 'n' },
                    ['<C-y>'] = { 'actions.yank_entry', opts = { modify=":." }, mode = 'n' }, -- :. makes it a relative path
                    ['<C-h>'] = false, ['<C-j>'] = false, ['<C-k>'] = false, ['<C-l>'] = false, -- I use those keys for navigation
                    ['<CR>'] =  'actions.select',
                    ['-'] = { 'actions.parent', mode = 'n' },
                    ['_'] = { 'actions.open_cwd', mode = 'n' },
                    [',cd'] = { 'actions.cd', mode = 'n' },
                    [',CD'] = { 'actions.cd', opts = { scope = 'tab' }, mode = 'n' },
                    ['gx'] = 'actions.open_external',
                    ['g.'] = { 'actions.toggle_hidden', mode = 'n' },
                },
                use_default_keymaps = true,
                view_options = {
                    show_hidden = true,
                    is_hidden_file = function(name, _)
                        local m = name:match('^%.')
                        return m ~= nil
                    end,
                    is_always_hidden = function(_, _)
                        return false
                    end,
                    natural_order = 'fast',
                    case_insensitive = false,
                    sort = {
                        { 'type', 'asc' },
                        { 'name', 'asc' },
                    },
                    highlight_filename = function(_, _, _, _)
                        return nil
                    end,
                },
                extra_scp_args = {},
                float = {
                    padding = 2,
                    max_width = 0,
                    max_height = 0,
                    border = 'rounded',
                    win_options = {
                        winblend = 0,
                    },
                    get_win_title = nil,
                    preview_split = 'auto',
                    override = function(conf)
                        return conf
                    end,
                },
                preview_win = {
                    update_on_cursor_moved = true,
                    preview_method = 'fast_scratch',
                    disable_preview = function(_)
                        return false
                    end,
                    win_options = {},
                },
                confirmation = {
                    max_width = 0.9,
                    min_width = { 40, 0.4 },
                    width = nil,
                    max_height = 0.9,
                    min_height = { 5, 0.1 },
                    height = nil,
                    border = 'rounded',
                    win_options = {
                        winblend = 0,
                    },
                },
                progress = {
                    max_width = 0.9,
                    min_width = { 40, 0.4 },
                    width = nil,
                    max_height = { 10, 0.9 },
                    min_height = { 5, 0.1 },
                    height = nil,
                    border = 'rounded',
                    minimized_border = 'none',
                    win_options = {
                        winblend = 0,
                    },
                },
                ssh = {
                    border = 'rounded',
                },
                keymaps_help = {
                    border = 'rounded',
                },
            })

            -- oil fix relative path
            vim.api.nvim_create_augroup('OilRelPathFix', {})
            vim.api.nvim_create_autocmd('BufLeave', {
                group = 'OilRelPathFix',
                pattern  = 'oil:///*',
                callback = function ()
                    vim.cmd('cd .')
                end
            })

            local actions = require('oil.actions')
            vim.keymap.set('n', '-', actions.parent.callback, { desc =  actions.parent.desc })
            vim.keymap.set('n', '_', actions.open_cwd.callback, { desc = actions.open_cwd.desc })
        end
    },
    { 'f-person/git-blame.nvim', --{{{2
        keys = {',g'},
        config = function ()
            require'gitblame'.setup{
                enabled = false,
            }
            vim.cmd[[
                nnoremap ,g :GitBlameToggle<CR>
            ]]
        end
    },
    { 'unblevable/quick-scope', --{{{2
        init = function()
            vim.cmd [[
                let g:qs_highlight_on_keys = ['f', 'F', 't', 'T']
            ]]
        end,
    },
    { 'michaeljsmith/vim-indent-object', --{{{2
    },
    { 'kylechui/nvim-surround', --{{{2
        version = '*', -- Use for stability; omit to use `main` branch for the latest features
        event = 'VeryLazy',
        config = function()
            require('nvim-surround').setup{}
        end
    },
    { 'echasnovski/mini.align', --{{{2
        version = false,
        config = function()
            require'mini.align'.setup()
        end,
    },
    { 'miikanissi/modus-themes.nvim', --{{{2
        lazy = false,
        priority = 1000,
        config = function()
            vim.o.termguicolors = true
            vim.cmd.colorscheme('modus_vivendi')
        end,
    },
    { 'folke/lazydev.nvim', --{{{2
        ft = 'lua', -- only load on lua files
        opts = {
            library = {
                { path = '${3rd}/luv/library', words = { 'vim%.uv' } },
                { path = '${3rd}/love2d/library', words = { 'love' } },
            },
        },
    },
    { "seblyng/roslyn.nvim", --{{{2
        dependencies = {
            'williamboman/mason.nvim',
        },
        cond = function()
            local mason_registry = require'mason-registry'
            return mason_registry.is_installed('roslyn')
        end,
        opts = {
            ---function to pick which .sln file to use when opening a cs file
            ---@param targets string[]
            choose_target=function (targets)
                if targets == nil then return end
                if #targets == 0 then return end
                table.sort(targets, function(a, b)
                    return #a < #b
                end)
                return targets[1]
            end
        },
    },
    { 'neovim/nvim-lspconfig', --{{{2
        dependencies = {
            'williamboman/mason.nvim',
            'williamboman/mason-lspconfig.nvim',
        },
        config = function()
            vim.diagnostic.config{
                severity_sort=true
            }

            require'mason'.setup{
                registries={
                    "github:mason-org/mason-registry",
                    "github:Crashdummyy/mason-registry",
                }
            }
            require'mason-lspconfig'.setup()
            vim.lsp.config.zls = {
                before_init = function(_, _)
                    vim.g.zig_fmt_autosave = false -- may not be needed anymore?
                end,
            }
            vim.lsp.config.lua_ls = {
                settings = {
                    Lua = { runtime = { version = 'LuaJIT' } }
                }
            }
            vim.lsp.config.gopls = {
                filetypes = { -- unsure if this is entirely correct...
                    'go',
                    'gomod',
                    'gowork',
                    'gotmpl',
                    'html'
                },
                settings = {
                    gopls = {
                        templateExtensions = {'html', 'gotmpl'}
                    }
                }
            }
            vim.lsp.config.basedpyright = {
                settings = {
                    basedpyright = {
                        analysis = {
                            useLibraryCodeForTypes = true
                        },
                    },
                },
            }
            vim.lsp.config.sourcekit = {
                filetypes = {"swift"}
            }
            vim.lsp.config.ols = {
                settings = {
                    verbose=true,
                }
            }

            vim.lsp.enable('gdscript')
            vim.lsp.config('hls', {
                filetypes = { 'haskell', 'lhaskell', 'cabal' },
            })
            vim.lsp.enable('hls')
            vim.lsp.enable('sourcekit')

            vim.api.nvim_create_autocmd('LspAttach', {
                callback = function(args)
                    vim.bo[args.buf].tagfunc = nil
                end,
            })

            vim.keymap.set( 'n', ',fd', vim.lsp.buf.definition, { noremap = true, silent = true})

            vim.cmd [[
                nnoremap ,rn :lua vim.lsp.buf.rename()<CR>
                nnoremap ,ft :lua vim.lsp.buf.type_definition()<CR>
                nnoremap ,fi :lua vim.lsp.buf.implementation()<CR>
                nnoremap ,fr :lua vim.lsp.buf.references()<CR>
                nnoremap ,ca :lua vim.lsp.buf.code_action()<CR>
                nnoremap ,oe :lua vim.diagnostic.open_float()<CR>

                nnoremap ,ea :lua vim.diagnostic.setqflist()<CR>
                nnoremap ,ee :lua vim.diagnostic.setqflist{severity='ERROR'}<CR>
                nnoremap ,ew :lua vim.diagnostic.setqflist{severity='WARN'}<CR>
                nnoremap ,ei :lua vim.diagnostic.setqflist{severity='INFO'}<CR>
                nnoremap ,eh :lua vim.diagnostic.setqflist{severity='HINT'}<CR>

                nnoremap ,eA :lua vim.diagnostic.setloclist()<CR>
                nnoremap ,eE :lua vim.diagnostic.setloclist{severity='ERROR'}<CR>
                nnoremap ,eW :lua vim.diagnostic.setloclist{severity='WARN'}<CR>
                nnoremap ,eI :lua vim.diagnostic.setloclist{severity='INFO'}<CR>
                nnoremap ,eH :lua vim.diagnostic.setloclist{severity='HINT'}<CR>

                nnoremap ,fm :lua vim.lsp.buf.format()<CR>
            ]]
        end
    },
    { 'mfussenegger/nvim-dap', --{{{2
        dependencies = {
            'leoluz/nvim-dap-go',
            'mfussenegger/nvim-dap-python',
            'nicholasmata/nvim-dap-cs', -- TODO: make these disabled if the correct debuggers are not installed by default!
        },
        keys = {
            ',b',
            ',db',
            ',B',
            '<B',
            ',dh',
            ',ds',
            ',df',
        },
        config = function()
            require'dap-go'.setup()
            require'dap-python'.setup('debugpy-adapter')
            require('dap-cs').setup()

            local dap = require'dap'
            dap.adapters.godot = { type = 'server', host = '127.0.0.1', port = 6006, }
            dap.configurations.gdscript = { {type = 'godot', request = 'launch', name = 'Launch scene', project = '${workspaceFolder}',} }

            dap.adapters.lldb = {
                type = 'executable',
                command = vim.fn.exepath('codelldb'),
                name = 'lldb'
            }

            dap.configurations.c = {
                {
                    name = 'Launch',
                    type = 'lldb',
                    request = 'launch',
                    program = function()
                        return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/', 'file')
                    end,
                    cwd = '${workspaceFolder}',
                    stopOnEntry = false,
                    args = function()
                        local i = vim.fn.input('input args: ')
                        return vim.fn.split(i)
                    end,
                    runInTerminal = true,
                },
            }
            dap.configurations.cpp = dap.configurations.c
            dap.configurations.rust = dap.configurations.c
            local widgets = require'dap.ui.widgets'

            local inspections = {}
            local function map_inspection(keys, func)
                local function wrapper()
                    if inspections[keys] == nil then
                        inspections[keys] = func()
                        return
                    end
                    inspections[keys].toggle()
                end
                vim.keymap.set('n', keys, wrapper, {noremap=true})
            end
            local function refresh_inspections()
                for _, widget in pairs(inspections) do
                    if widget.win ~= nil then
                        widget.refresh()
                    end
                end
            end
            local function close_inspections()
                for _, widget in pairs(inspections) do
                    if widget.win ~= nil then
                        widget.close()
                    end
                end
            end
            map_inspection(',dh', widgets.hover)
            map_inspection(',ds', function () return widgets.centered_float(widgets.scopes) end)
            map_inspection(',df', function () return widgets.centered_float(widgets.frames) end)
            vim.keymap.set('n', '<Down>', dap.step_over)
            vim.keymap.set('n', '<Right>', dap.step_into)
            vim.keymap.set('n', '<Left>', dap.step_out)
            vim.keymap.set('n', '<Up>', dap.restart_frame)
            vim.keymap.set('n', ',b', dap.toggle_breakpoint)
            vim.keymap.set('n', ',B', dap.clear_breakpoints)
            vim.keymap.set('n', '<B', dap.clear_breakpoints)
            vim.keymap.set('n', ',db', dap.continue)
            vim.keymap.set('n', ',dc', close_inspections)
            vim.keymap.set('n', ',dr', refresh_inspections)

        end
    },
    { 'dcampos/nvim-snippy', --{{{2
        config = function()
            require'snippy'.setup{ enable_auto = true, }
            vim.cmd [[
                imap <expr> <c-l> '<Plug>(snippy-next)'
                imap <expr> <c-k> '<Plug>(snippy-previous)'
                smap <expr> <c-l> '<Plug>(snippy-next)'
                smap <expr> <c-k> '<Plug>(snippy-previous)'
                nmap g; <Plug>(snippy-cut-text)
                xmap g; <Plug>(snippy-cut-text)
            ]]
        end
    },
    { 'hrsh7th/nvim-cmp', --{{{2
        dependencies = {
            'hrsh7th/cmp-nvim-lsp',
            'hrsh7th/cmp-path',
            'dcampos/nvim-snippy',
            'dcampos/cmp-snippy',
        },
        config = function()
            local cmp = require'cmp'
            cmp.setup{
                snippet = {
                    expand = function(args)
                        require'snippy'.expand_snippet(args.body)
                    end,
                },
                mapping = {
                    ['<C-y>'] = cmp.mapping.confirm{ select = true },
                    ['<C-n>'] = cmp.mapping.select_next_item(),
                    ['<C-p>'] = cmp.mapping.select_prev_item(),
                },
                sources = cmp.config.sources(
                    {
                        { name = 'snippy',   priority = 100000000000000000000 },
                        { name = 'nvim_lsp', priority = 1000000000},
                        { name = 'path',     priority = 1},
                    }
                ),
                preselect = cmp.PreselectMode.None,
            }
        end,
    },
    { 'nvim-telescope/telescope.nvim', --{{{2
        tag = 'v0.2.2',
        dependencies = {
            'nvim-lua/plenary.nvim',
            'nvim-telescope/telescope-ui-select.nvim',
            'kyazdani42/nvim-web-devicons',
        },
        config = function()
            local actions = require'telescope.actions'
            vim.o.splitright = true
            require'telescope'.setup{
                defaults = {
                    file_ignore_patterns = {'%__virtual.cs$'},
                    mappings = {
                        i = {
                            ['<C-Q>'] = actions.smart_send_to_qflist + actions.open_qflist,
                            ['<C-j>'] = actions.select_default,
                        },
                        n = {
                            ['<C-Q>'] = actions.smart_send_to_qflist + actions.open_qflist,
                            ['<C-j>'] = actions.select_default,
                        },
                    }
                },
                pickers = {
                    help_tags = {
                        attach_mappings = function(_, map)
                            map("i", "<CR>", actions.select_vertical)
                            map("n", "<CR>", actions.select_vertical)
                            map("i", "<C-j>", actions.select_vertical)
                            map("n", "<C-j>", actions.select_vertical)
                            return true
                        end,
                    },
                },
                extensions = { ['ui-select'] = { require'telescope.themes'.get_dropdown{}, }, },
            }

            local function map(key, func, ...)
                local args = {...}
                if #args ~= 0 then
                    local original_func = func
                    func = function() original_func(unpack(args)) end
                end
                vim.keymap.set( 'n', key, func, { silent = false, remap = false, })
            end
            local builtin = require'telescope.builtin'
            map(',fcc', builtin.git_commits)
            map(',fcb', builtin.git_bcommits)
            map(',fb', builtin.git_branches)

            map(',fw', builtin.lsp_dynamic_workspace_symbols)
            map(',fa', builtin.find_files, {hidden=true, no_ignore=true, no_ignore_parent=true})
            map(',ff', builtin.find_files)
            map(',fo', builtin.oldfiles)
            map(',fg', builtin.live_grep)
            map(',fs', builtin.grep_string)
            map(',fz', builtin.current_buffer_fuzzy_find)
            map(',fh', builtin.help_tags)

            map(',fea', builtin.diagnostics)
            map(',fee', builtin.diagnostics, {severity='ERROR'})
            map(',few', builtin.diagnostics, {severity='WARN'})
            map(',fei', builtin.diagnostics, {severity='INFO'})
            map(',feh', builtin.diagnostics, {severity='HINT'})

            map(',fq', function()
                local errors = 'errors'
                local warnings = 'warnings'
                local info = 'info'
                local hints = 'hints'
                vim.ui.select(
                    {'errors', 'warnings', 'info', 'hints'},
                    { prompt="filter qflist based on type", },
                    function (type)
                        local kept = {}
                        local mapping = {
                            e=errors, E=errors,
                            w=warnings, W=warnings,
                            i=info, I=info,
                            h=hints, H=hints,
                        }
                        for _, item in ipairs(vim.fn.getqflist()) do
                            if mapping[item.type] == nil then
                                error('unknown type '..item.type..' '..item.text)
                            end
                            if mapping[item.type] == type then
                                table.insert(kept, item)
                            end
                        end
                        vim.fn.setqflist(kept)
                    end
                )
            end)

            require'telescope'.load_extension'ui-select'

        end,
    },
}

do -- split line {{{1
    local SPLIT_DELIMETERS = { -- single characters only
        [','] = true,
        [';'] = true,
        ['|'] = true,
    }
    local SPLIT_BETWEEN = { -- single characters only
        ['('] = ')',
        ['['] = ']',
        ['{'] = '}',
        ['<'] = '>',
    }
    local SPLIT_IGNORE_BETWEEN = { --single characters only
        ['"'] = '"',
        ["'"] = "'",
    }

    local split_line = function()
        local SPLIT_WHITESPACE = '	'
        if vim.o.expandtab then
            SPLIT_WHITESPACE = ''
            for _ = 1, vim.o.tabstop do
                SPLIT_WHITESPACE = SPLIT_WHITESPACE .. ' '
            end
        end

        local line = vim.api.nvim_get_current_line()
        local _, col =  unpack(vim.api.nvim_win_get_cursor(0))
        col = col + 1 -- Doing this to make it 1-indexed

        ---@type integer?
        local first_bracket_i = nil
        for i = col, #line do
            local char = line:sub(i, i)
            if SPLIT_BETWEEN[char] ~= nil then
                first_bracket_i = i
                break
            end
        end

        if not first_bracket_i then
            print('No opening brackets found after cursor on this line.')
            return
        end

        ---@type integer[]
        local split_indexes = {} -- Populate this array
        ---@type integer?
        local last_bracket_i = nil -- And find this index

        do
            ---@type string[]
            local closing_bracket_stack = {}
            local icon_to_close_ignore = ''
            local in_ignore = false

            for i = first_bracket_i, #line do
                local char = line:sub(i,i)

                if in_ignore then
                    in_ignore = not (char == icon_to_close_ignore)
                    goto continue
                end

                if SPLIT_IGNORE_BETWEEN[char] ~= nil then
                    icon_to_close_ignore = SPLIT_IGNORE_BETWEEN[char]
                    in_ignore = true
                    goto continue
                end
                -- string handling complete

                if SPLIT_BETWEEN[char] ~= nil then
                    table.insert(
                        closing_bracket_stack,
                        SPLIT_BETWEEN[char]
                    )
                end

                if char == closing_bracket_stack[#closing_bracket_stack] then
                    table.remove(closing_bracket_stack)
                end

                if #closing_bracket_stack == 1 and SPLIT_DELIMETERS[char] then
                    table.insert(split_indexes, i)
                end

                if #closing_bracket_stack == 0 then
                    last_bracket_i = i
                    break
                end
                ::continue::
            end
        end

        if not last_bracket_i then
            print('The first opening bracket found after the cursor was not closed on this line.')
            return
        end

        ---@type string
        local leading_whitespace = string.match(line, '^%s*')
        local first_line = line:sub(1, first_bracket_i)
        local last_line = leading_whitespace .. line:sub(last_bracket_i, #line)
        if #split_indexes == 0 then
            if (last_bracket_i - first_bracket_i == 1) then return end
            local row, _ = unpack(vim.api.nvim_win_get_cursor(0))
            line = line:sub(first_bracket_i+1, last_bracket_i-1)
            local leading_pattern = '^[%s'
            for k, _ in pairs(SPLIT_DELIMETERS) do
                leading_pattern = leading_pattern .. k
            end
            leading_pattern = leading_pattern .. ']*'
            line = line:gsub(leading_pattern, leading_whitespace .. SPLIT_WHITESPACE, 1)
            line = line:gsub('[%s]*$', '', 1)
            vim.api.nvim_buf_set_lines(0, row-1, row, false, {first_line})
            vim.api.nvim_buf_set_lines(0, row, row, false, {last_line})
            vim.api.nvim_buf_set_lines(0, row, row, false, {line})
            return
        end

        local middle_lines = {}
        table.insert(
            middle_lines,
            line:sub(first_bracket_i+1, split_indexes[1])
        )
        for i = 1, #split_indexes-1 do
            table.insert(
                middle_lines,
                line:sub(split_indexes[i], split_indexes[i+1])
            )
        end
        table.insert(
            middle_lines,
            line:sub(split_indexes[#split_indexes], last_bracket_i-1)
        )

        local leading_pattern = '^[%s'
        for k, _ in pairs(SPLIT_DELIMETERS) do
            leading_pattern = leading_pattern .. k
        end
        leading_pattern = leading_pattern .. ']*'

        -- Cleanup step
        for i, middle_line in ipairs(middle_lines) do
            middle_line = middle_line:gsub(leading_pattern, leading_whitespace .. SPLIT_WHITESPACE, 1)
            middle_line = middle_line:gsub('[%s]*$', '', 1)
            middle_lines[i] = middle_line
        end

        if middle_lines[#middle_lines]:match('^%s*$') ~= nil then
            table.remove(middle_lines)
        end

        local row, _ = unpack(vim.api.nvim_win_get_cursor(0))
        vim.api.nvim_buf_set_lines(0, row-1, row, false, {first_line})
        vim.api.nvim_buf_set_lines(0, row, row, false, {last_line})
        vim.api.nvim_buf_set_lines(0, row, row, false, middle_lines)
    end

    vim.keymap.set(
        'n',
        ',s',
        split_line,
        { silent = true }
    )
end

