telescope.nvim icon indicating copy to clipboard operation
telescope.nvim copied to clipboard

Ability to make file name more visible in file path

Open GuillaumeLagrange opened this issue 3 years ago • 29 comments

Is your feature request related to a problem? Please describe. When I use git_files or find_files on large project with a lot of directories, it is sometimes a bit hard to spot the filename from the rest of the path.

Describe the solution you'd like I would really love to have a built-in option to bolden/put a colot emphasis on the file name in the results so I can quickly spot out the file name.

Describe alternatives you've considered I've considered

            -- Format path as "file.txt (path\to\file\)"
            path_display = function(opts, path)
              local tail = require("telescope.utils").path_tail(path)
              return string.format("%s (%s)", tail, path)
            end,

But I am not sure how to handle coloring/bolding output in lua, I am a bit unfamiliar with the language.

Additional context I would love to open a PR for this if you would have this feature, I may need a bit of help to get started though.

Thanks for the awesome work you guys have been doing !

GuillaumeLagrange avatar Jun 16 '22 13:06 GuillaumeLagrange

path_display is currently not integrated with our hightlighter. Shouldn't be hard to do, will add it later.

After that you should be able to do

-- Format path as "file.txt (path\to\file\)"
path_display = function(opts, path)
  local tail = require("telescope.utils").path_tail(path)
  return string.format("%s (%s)", tail, path), { { { 1, #tail }, "Constant" } }
end,

Conni2461 avatar Jun 26 '22 09:06 Conni2461

I too require this feature implemented. Thanks

vignesh0025 avatar Oct 11 '22 16:10 vignesh0025

Yes please. I would love this too!

DasOhmoff avatar Oct 26 '22 17:10 DasOhmoff

Bumping +1 ;)

XobSod avatar Feb 01 '23 13:02 XobSod

This feature would be very nice to have indeed!

I quite like the formatting of the file picker in VSCode where the path of the file is darker:

image

But in Telescope, the file name (first section before the dash) is not very easy to discern from the full file path:

image

JoosepAlviste avatar Feb 16 '23 18:02 JoosepAlviste

agreed, this would be ace!

tjex avatar May 09 '23 18:05 tjex

截屏2023-05-10 08 38 55

towry avatar May 10 '23 00:05 towry

nice. care to share the config snipet @towry ? I tried adding highlights for tail / path / 'constant' but wasnt sure if I was doing something wrong (highly likely) or the feature hasn't been implemented yet as the issue is still open and no other hint that it's been implemented after Connies comment.

tjex avatar May 10 '23 06:05 tjex

@tjex Here it is 😁

pickers.lua

The main part is lines between comment --- //////// item stylish., you just need copy that part into your own config, dependencies required: { 'nvim-web-devicons', 'telescope', 'plenary', 'M.get_path_and_tail' }

local M = {}

M.get_path_and_tail = function(filename)
  local utils = require('telescope.utils')
  local bufname_tail = utils.path_tail(filename)
  local path_without_tail = require('plenary.strings').truncate(filename, #filename - #bufname_tail, '')
  local path_to_display = utils.transform_path({
    path_display = { 'truncate' },
  }, path_without_tail)

  return bufname_tail, path_to_display
end

local use_find_files_instead_of_git = true

M.project_files_toggle_between_git_and_fd = function()
  use_find_files_instead_of_git = not use_find_files_instead_of_git
end

M.project_files = function(opts)
  local make_entry = require('telescope.make_entry')
  local strings = require('plenary.strings')
  local utils = require('telescope.utils')
  local entry_display = require('telescope.pickers.entry_display')
  local devicons = require('nvim-web-devicons')
  local def_icon = devicons.get_icon('fname', { default = true })
  local iconwidth = strings.strdisplaywidth(def_icon)
  local level_up = vim.v.count

  local map_i_actions = function(prompt_bufnr, map)
    map('i', '<C-o>', function()
      require('libs.telescope.picker_keymaps').open_selected_in_window(prompt_bufnr)
    end, { noremap = true, silent = true })
  end

  opts = opts or {}
  if not opts.cwd then
    opts.cwd = require('libs.telescope.utils').get_cwd_relative_to_buf(0, level_up)
  end
  opts.hidden = true

  local nicely_cwd = require('libs.runtime.path').home_to_tilde(opts.cwd)
  opts.prompt_title = opts.prompt_title or nicely_cwd

  opts.attach_mappings = function(_, map)
    map_i_actions(_, map)
    return true
  end

  --- //////// item stylish.
  local entry_make = make_entry.gen_from_file(opts)
  opts.entry_maker = function(line)
    local entry = entry_make(line)
    local displayer = entry_display.create({
      separator = ' ',
      items = {
        { width = iconwidth },
        { width = nil },
        { remaining = true },
      },
    })
    entry.display = function(et)
      -- https://github.com/nvim-telescope/telescope.nvim/blob/master/lua/telescope/make_entry.lua
      local tail_raw, path_to_display = M.get_path_and_tail(et.value)
      local tail = tail_raw .. ' '
      local icon, iconhl = utils.get_devicons(tail_raw)

      return displayer({
        { icon, iconhl },
        tail,
        { path_to_display, 'TelescopeResultsComment' },
      })
    end
    return entry
  end
  ---/// end item stylish

  if opts and opts.oldfiles then
    local cache_opts = vim.tbl_deep_extend('force', {
    }, opts)
    local cycle = require('libs.telescope.cycle')(
      function(income_opts)
        require('telescope.builtin').find_files(vim.tbl_extend('force', cache_opts, {
          results_title = '  All Files: ',
        }, income_opts))
      end
    )
    opts = vim.tbl_extend('force', {
      results_title = '  Recent files: ',
      prompt_title = '  Recent files',
      attach_mappings = function(_, map)
        map_i_actions(_, map)
        map('i', '<C-b>', cycle.next, { noremap = true, silent = true })
        return true
      end
    }, opts)
    return require('telescope.builtin').oldfiles(opts)
  end

  -- use find_files or git_files.
  local use_all_files = opts.use_all_files or false
  if (opts and opts.no_gitfiles) or use_find_files_instead_of_git then
    use_all_files = true
  end


  local ok = (function()
    if use_all_files then return false end
    opts.results_title = '  Git Files: '
    local is_git_ok = pcall(require('telescope.builtin').git_files, opts)
    return is_git_ok
  end)()
  if not ok then
    opts.results_title = '  All Files: '
    require('telescope.builtin').find_files(opts)
  end
end

function M.buffers_or_recent()
  local count = #vim.fn.getbufinfo({ buflisted = 1 })
  if count <= 1 then
    --- open recent.
    M.project_files({
      cwd_only = true,
      oldfiles = true,
    })
    return
  end
  return M.buffers()
end

function M.buffers()
  local builtin = require('telescope.builtin')
  local actions = require('telescope.actions')
  local actionstate = require('telescope.actions.state')
  local Buffer = require('libs.runtime.buffer')

  builtin.buffers({
    ignore_current_buffer = true,
    sort_mru = true,
    -- layout_strategy = 'vertical',
    layout_strategy = "bottom_pane",
    entry_maker = M.gen_from_buffer({
      bufnr_width = 2,
      sort_mru = true,
    }),
    attach_mappings = function(prompt_bufnr, map)
      local close_buf = function()
        -- local picker = actionstate.get_current_picker(prompt_bufnr)
        local selection = actionstate.get_selected_entry()
        actions.close(prompt_bufnr)
        vim.api.nvim_buf_delete(selection.bufnr, { force = false })
        local state = require('telescope.state')
        local cached_pickers = state.get_global_key('cached_pickers') or {}
        -- remove this picker cache
        table.remove(cached_pickers, 1)
      end

      local open_selected = function()
        local entry = actionstate.get_selected_entry()
        actions.close(prompt_bufnr)
        if not entry or (not entry.bufnr) then
          vim.notify("no selected entry found")
          return
        end
        local bufnr = entry.bufnr
        Buffer.set_current_buffer_focus(bufnr)
      end

      map('i', '<C-h>', close_buf)
      map('i', '<CR>', open_selected)

      return true
    end,
  })
end

function M.gen_from_buffer(opts)
  local runtimeUtils = require('libs.runtime.utils')
  local utils = require('telescope.utils')
  local strings = require('plenary.strings')
  local entry_display = require('telescope.pickers.entry_display')
  local Path = require('plenary.path')
  local make_entry = require('telescope.make_entry')

  opts = opts or {}

  local disable_devicons = opts.disable_devicons

  local icon_width = 0
  if not disable_devicons then
    local icon, _ = utils.get_devicons('fname', disable_devicons)
    icon_width = strings.strdisplaywidth(icon)
  end

  local cwd = vim.fn.expand(opts.cwd or runtimeUtils.get_root() or ".")

  local make_display = function(entry)
    -- bufnr_width + modes + icon + 3 spaces + : + lnum
    opts.__prefix = opts.bufnr_width + 4 + icon_width + 3 + 1 + #tostring(entry.lnum)
    local bufname_tail = utils.path_tail(entry.filename)
    local path_without_tail = require('plenary.strings').truncate(entry.filename, #entry.filename - #bufname_tail, '')
    local path_to_display = utils.transform_path({
      path_display = { 'truncate' },
    }, path_without_tail)
    local bufname_width = strings.strdisplaywidth(bufname_tail)
    local icon, hl_group = utils.get_devicons(entry.filename, disable_devicons)

    local displayer = entry_display.create({
      separator = ' ',
      items = {
        { width = opts.bufnr_width },
        { width = 4 },
        { width = icon_width },
        { width = bufname_width },
        { remaining = true },
      },
    })

    return displayer({
      { entry.bufnr, 'TelescopeResultsNumber' },
      { entry.indicator, 'TelescopeResultsComment' },
      { icon, hl_group },
      bufname_tail,
      { path_to_display .. ':' .. entry.lnum, 'TelescopeResultsComment' },
    })
  end

  return function(entry)
    local bufname = entry.info.name ~= '' and entry.info.name or '[No Name]'
    -- if bufname is inside the cwd, trim that part of the string
    bufname = Path:new(bufname):normalize(cwd)

    local hidden = entry.info.hidden == 1 and 'h' or 'a'
    -- local readonly = vim.api.nvim_buf_get_option(entry.bufnr, 'readonly') and '=' or ' '
    local readonly = vim.api.nvim_get_option_value('readonly', {
      buf = entry.bufnr,
    }) and '=' or ' '
    local changed = entry.info.changed == 1 and '+' or ' '
    local indicator = entry.flag .. hidden .. readonly .. changed
    local lnum = 1

    -- account for potentially stale lnum as getbufinfo might not be updated or from resuming buffers picker
    if entry.info.lnum ~= 0 then
      -- but make sure the buffer is loaded, otherwise line_count is 0
      if vim.api.nvim_buf_is_loaded(entry.bufnr) then
        local line_count = vim.api.nvim_buf_line_count(entry.bufnr)
        lnum = math.max(math.min(entry.info.lnum, line_count), 1)
      else
        lnum = entry.info.lnum
      end
    end

    return make_entry.set_default_entry_mt({
      value = bufname,
      ordinal = entry.bufnr .. ' : ' .. bufname,
      display = make_display,
      bufnr = entry.bufnr,
      filename = bufname,
      lnum = lnum,
      indicator = indicator,
    }, opts)
  end
end

return M

towry avatar May 10 '23 06:05 towry

omg I'd like to use this in all my pickers. the current default causes too much overload for my brain. @towry this looks fantastic!

uloco avatar May 16 '23 11:05 uloco

@towry Any chance you can post the code snippet here? I'm getting a 404 on clicking the link (repo is taken private?).

SandeepTuniki avatar Jul 05 '23 07:07 SandeepTuniki

@SandeepTuniki the snippet is there. https://github.com/nvim-telescope/telescope.nvim/issues/2014#issuecomment-1541423345

tjex avatar Jul 09 '23 19:07 tjex

@SandeepTuniki the snippet is there. https://github.com/nvim-telescope/telescope.nvim/issues/2014#issuecomment-1541423345

Ah yes. Looks like the comment was updated from a link to the code snippet (thanks, @towry!)

SandeepTuniki avatar Jul 10 '23 08:07 SandeepTuniki

Thank you @towry for that great snippet, it inspired me to create custom Pickers for both File finders and Grep searchers. I'll leave it here if anyone wants to change their Pickers appearance easily, simply create a lua module file inside of your nvim/lua folder called telescopePickers and replace ggv.telescopePickers with your own module location.

CleanShot 23-08-2023at 13 38 12@2x CleanShot 23-08-2023at 13 38 44@2x

Available pickers are:

live_grep

require('ggv.telescopePickers').prettyGrepPicker({ picker = 'live_grep' })

grep_string

require('ggv.telescopePickers').prettyGrepPicker({ picker = 'grep_string' })

git_files

require('ggv.telescopePickers').prettyFilesPicker({ picker = 'git_files' })

find_files

require('ggv.telescopePickers').prettyFilesPicker({ picker = 'find_files' })

oldfiles

require('ggv.telescopePickers').prettyFilesPicker({ picker = 'oldfiles' })

telescopePickers.lua

-- Declare the module
local telescopePickers = {}

-- Store Utilities we'll use frequently
local telescopeUtilities = require('telescope.utils')
local telescopeMakeEntryModule = require('telescope.make_entry')
local plenaryStrings = require('plenary.strings')
local devIcons = require('nvim-web-devicons')
local telescopeEntryDisplayModule = require('telescope.pickers.entry_display')

-- Obtain Filename icon width
-- --------------------------
-- INSIGHT: This width applies to all icons that represent a file type
local fileTypeIconWidth = plenaryStrings.strdisplaywidth(devIcons.get_icon('fname', { default = true }))

---- Helper functions ----

-- Gets the File Path and its Tail (the file name) as a Tuple
function telescopePickers.getPathAndTail(fileName)
    -- Get the Tail
    local bufferNameTail = telescopeUtilities.path_tail(fileName)

    -- Now remove the tail from the Full Path
    local pathWithoutTail = require('plenary.strings').truncate(fileName, #fileName - #bufferNameTail, '')

    -- Apply truncation and other pertaining modifications to the path according to Telescope path rules
    local pathToDisplay = telescopeUtilities.transform_path({
        path_display = { 'truncate' },
    }, pathWithoutTail)

    -- Return as Tuple
    return bufferNameTail, pathToDisplay
end

---- Picker functions ----

-- Generates a Find File picker but beautified
-- -------------------------------------------
-- This is a wrapping function used to modify the appearance of pickers that provide a Find File
-- functionality, mainly because the default one doesn't look good. It does this by changing the 'display()'
-- function that Telescope uses to display each entry in the Picker.
--
-- Adapted from: https://github.com/nvim-telescope/telescope.nvim/issues/2014#issuecomment-1541423345.
--
-- @param (table) pickerAndOptions - A table with the following format:
--                                   {
--                                      picker = '<pickerName>',
--                                      (optional) options = { ... }
--                                   }
function telescopePickers.prettyFilesPicker(pickerAndOptions)
    -- Parameter integrity check
    if type(pickerAndOptions) ~= 'table' or pickerAndOptions.picker == nil then
        print("Incorrect argument format. Correct format is: { picker = 'desiredPicker', (optional) options = { ... } }")

        -- Avoid further computation
        return
    end

    -- Ensure 'options' integrity
    options = pickerAndOptions.options or {}

    -- Use Telescope's existing function to obtain a default 'entry_maker' function
    -- ----------------------------------------------------------------------------
    -- INSIGHT: Because calling this function effectively returns an 'entry_maker' function that is ready to
    --          handle entry creation, we can later call it to obtain the final entry table, which will
    --          ultimately be used by Telescope to display the entry by executing its 'display' key function.
    --          This reduces our work by only having to replace the 'display' function in said table instead
    --          of having to manipulate the rest of the data too.
    local originalEntryMaker = telescopeMakeEntryModule.gen_from_file(options)

    -- INSIGHT: 'entry_maker' is the hardcoded name of the option Telescope reads to obtain the function that
    --          will generate each entry.
    -- INSIGHT: The paramenter 'line' is the actual data to be displayed by the picker, however, its form is
    --          raw (type 'any) and must be transformed into an entry table.
    options.entry_maker = function(line)
        -- Generate the Original Entry table
        local originalEntryTable = originalEntryMaker(line)

        -- INSIGHT: An "entry display" is an abstract concept that defines the "container" within which data
        --          will be displayed inside the picker, this means that we must define options that define
        --          its dimensions, like, for example, its width.
        local displayer = telescopeEntryDisplayModule.create({
            separator = ' ', -- Telescope will use this separator between each entry item
            items = {
                { width = fileTypeIconWidth },
                { width = nil },
                { remaining = true },
            },
        })

        -- LIFECYCLE: At this point the "displayer" has been created by the create() method, which has in turn
        --            returned a function. This means that we can now call said function by using the
        --            'displayer' variable and pass it actual entry values so that it will, in turn, output
        --            the entry for display.
        --
        -- INSIGHT: We now have to replace the 'display' key in the original entry table to modify the way it
        --          is displayed.
        -- INSIGHT: The 'entry' is the same Original Entry Table but is is passed to the 'display()' function
        --          later on the program execution, most likely when the actual display is made, which could
        --          be deferred to allow lazy loading.
        --
        -- HELP: Read the 'make_entry.lua' file for more info on how all of this works
        originalEntryTable.display = function(entry)
            -- Get the Tail and the Path to display
            local tail, pathToDisplay = telescopePickers.getPathAndTail(entry.value)

            -- Add an extra space to the tail so that it looks nicely separated from the path
            local tailForDisplay = tail .. ' '

            -- Get the Icon with its corresponding Highlight information
            local icon, iconHighlight = telescopeUtilities.get_devicons(tail)

            -- INSIGHT: This return value should be a tuple of 2, where the first value is the actual value
            --          and the second one is the highlight information, this will be done by the displayer
            --          internally and return in the correct format.
            return displayer({
                { icon, iconHighlight },
                tailForDisplay,
                { pathToDisplay, 'TelescopeResultsComment' },
            })
        end

        return originalEntryTable
    end

    -- Finally, check which file picker was requested and open it with its associated options
    if pickerAndOptions.picker == 'find_files' then
        require('telescope.builtin').find_files(options)
    elseif pickerAndOptions.picker == 'git_files' then
        require('telescope.builtin').git_files(options)
    elseif pickerAndOptions.picker == 'oldfiles' then
        require('telescope.builtin').oldfiles(options)
    elseif pickerAndOptions.picker == '' then
        print("Picker was not specified")
    else
        print("Picker is not supported by Pretty Find Files")
    end
end

-- Generates a Grep Search picker but beautified
-- ----------------------------------------------
-- This is a wrapping function used to modify the appearance of pickers that provide Grep Search
-- functionality, mainly because the default one doesn't look good. It does this by changing the 'display()'
-- function that Telescope uses to display each entry in the Picker.
--
-- @param (table) pickerAndOptions - A table with the following format:
--                                   {
--                                      picker = '<pickerName>',
--                                      (optional) options = { ... }
--                                   }
function telescopePickers.prettyGrepPicker(pickerAndOptions)
    -- Parameter integrity check
    if type(pickerAndOptions) ~= 'table' or pickerAndOptions.picker == nil then
        print("Incorrect argument format. Correct format is: { picker = 'desiredPicker', (optional) options = { ... } }")

        -- Avoid further computation
        return
    end

    -- Ensure 'options' integrity
    options = pickerAndOptions.options or {}

    -- Use Telescope's existing function to obtain a default 'entry_maker' function
    -- ----------------------------------------------------------------------------
    -- INSIGHT: Because calling this function effectively returns an 'entry_maker' function that is ready to
    --          handle entry creation, we can later call it to obtain the final entry table, which will
    --          ultimately be used by Telescope to display the entry by executing its 'display' key function.
    --          This reduces our work by only having to replace the 'display' function in said table instead
    --          of having to manipulate the rest of the data too.
    local originalEntryMaker = telescopeMakeEntryModule.gen_from_vimgrep(options)

    -- INSIGHT: 'entry_maker' is the hardcoded name of the option Telescope reads to obtain the function that
    --          will generate each entry.
    -- INSIGHT: The paramenter 'line' is the actual data to be displayed by the picker, however, its form is
    --          raw (type 'any) and must be transformed into an entry table.
    options.entry_maker = function(line)
        -- Generate the Original Entry table
        local originalEntryTable = originalEntryMaker(line)

        -- INSIGHT: An "entry display" is an abstract concept that defines the "container" within which data
        --          will be displayed inside the picker, this means that we must define options that define
        --          its dimensions, like, for example, its width.
        local displayer = telescopeEntryDisplayModule.create({
            separator = ' ', -- Telescope will use this separator between each entry item
            items = {
                { width = fileTypeIconWidth },
                { width = nil },
                { width = nil }, -- Maximum path size, keep it short
                { remaining = true },
            },
        })

        -- LIFECYCLE: At this point the "displayer" has been created by the create() method, which has in turn
        --            returned a function. This means that we can now call said function by using the
        --            'displayer' variable and pass it actual entry values so that it will, in turn, output
        --            the entry for display.
        --
        -- INSIGHT: We now have to replace the 'display' key in the original entry table to modify the way it
        --          is displayed.
        -- INSIGHT: The 'entry' is the same Original Entry Table but is is passed to the 'display()' function
        --          later on the program execution, most likely when the actual display is made, which could
        --          be deferred to allow lazy loading.
        --
        -- HELP: Read the 'make_entry.lua' file for more info on how all of this works
        originalEntryTable.display = function(entry)
            ---- Get File columns data ----
            -------------------------------

            -- Get the Tail and the Path to display
            local tail, pathToDisplay = telescopePickers.getPathAndTail(entry.filename)

            -- Get the Icon with its corresponding Highlight information
            local icon, iconHighlight = telescopeUtilities.get_devicons(tail)

            ---- Format Text for display ----
            ---------------------------------

            -- Add coordinates if required by 'options'
            local coordinates = ""

            if not options.disable_coordinates then
                if entry.lnum then
                    if entry.col then
                        coordinates = string.format(" -> %s:%s", entry.lnum, entry.col)
                    else
                        coordinates = string.format(" -> %s", entry.lnum)
                    end
                end
            end

            -- Append coordinates to tail
            tail = tail .. coordinates

            -- Add an extra space to the tail so that it looks nicely separated from the path
            local tailForDisplay = tail .. ' '

            -- Encode text if necessary
            local text = options.file_encoding and vim.iconv(entry.text, options.file_encoding, "utf8") or entry.text

            -- INSIGHT: This return value should be a tuple of 2, where the first value is the actual value
            --          and the second one is the highlight information, this will be done by the displayer
            --          internally and return in the correct format.
            return displayer({
                { icon, iconHighlight },
                tailForDisplay,
                { pathToDisplay, 'TelescopeResultsComment' },
                text
            })
        end

        return originalEntryTable
    end

    -- Finally, check which file picker was requested and open it with its associated options
    if pickerAndOptions.picker == 'live_grep' then
        require('telescope.builtin').live_grep(options)
    elseif pickerAndOptions.picker == 'grep_string' then
        require('telescope.builtin').grep_string(options)
    elseif pickerAndOptions.picker == '' then
        print("Picker was not specified")
    else
        print("Picker is not supported by Pretty Grep Picker")
    end
end

-- Return the module for use
return telescopePickers

gilitos92 avatar Aug 23 '23 20:08 gilitos92

Thank you @towry for that great snippet, it inspired me to create custom Pickers for both File finders and Grep searchers. I'll leave it here if anyone wants to change their Pickers appearance easily, simply create a lua module file inside of your nvim/lua folder called telescopePickers and replace ggv.telescopePickers with your own module location.

CleanShot 23-08-2023at 13 38 12@2x CleanShot 23-08-2023at 13 38 44@2x

Available pickers are:

live_grep

require('ggv.telescopePickers').prettyGrepPicker({ picker = 'live_grep' })

grep_string

require('ggv.telescopePickers').prettyGrepPicker({ picker = 'grep_string' })

git_files

require('ggv.telescopePickers').prettyFilesPicker({ picker = 'git_files' })

find_files

require('ggv.telescopePickers').prettyFilesPicker({ picker = 'find_files' })

oldfiles

require('ggv.telescopePickers').prettyFilesPicker({ picker = 'oldfiles' })

telescopePickers.lua

-- Declare the module
local telescopePickers = {}

-- Store Utilities we'll use frequently
local telescopeUtilities = require('telescope.utils')
local telescopeMakeEntryModule = require('telescope.make_entry')
local plenaryStrings = require('plenary.strings')
local devIcons = require('nvim-web-devicons')
local telescopeEntryDisplayModule = require('telescope.pickers.entry_display')

-- Obtain Filename icon width
-- --------------------------
-- INSIGHT: This width applies to all icons that represent a file type
local fileTypeIconWidth = plenaryStrings.strdisplaywidth(devIcons.get_icon('fname', { default = true }))

---- Helper functions ----

-- Gets the File Path and its Tail (the file name) as a Tuple
function telescopePickers.getPathAndTail(fileName)
    -- Get the Tail
    local bufferNameTail = telescopeUtilities.path_tail(fileName)

    -- Now remove the tail from the Full Path
    local pathWithoutTail = require('plenary.strings').truncate(fileName, #fileName - #bufferNameTail, '')

    -- Apply truncation and other pertaining modifications to the path according to Telescope path rules
    local pathToDisplay = telescopeUtilities.transform_path({
        path_display = { 'truncate' },
    }, pathWithoutTail)

    -- Return as Tuple
    return bufferNameTail, pathToDisplay
end

---- Picker functions ----

-- Generates a Find File picker but beautified
-- -------------------------------------------
-- This is a wrapping function used to modify the appearance of pickers that provide a Find File
-- functionality, mainly because the default one doesn't look good. It does this by changing the 'display()'
-- function that Telescope uses to display each entry in the Picker.
--
-- Adapted from: https://github.com/nvim-telescope/telescope.nvim/issues/2014#issuecomment-1541423345.
--
-- @param (table) pickerAndOptions - A table with the following format:
--                                   {
--                                      picker = '<pickerName>',
--                                      (optional) options = { ... }
--                                   }
function telescopePickers.prettyFilesPicker(pickerAndOptions)
    -- Parameter integrity check
    if type(pickerAndOptions) ~= 'table' or pickerAndOptions.picker == nil then
        print("Incorrect argument format. Correct format is: { picker = 'desiredPicker', (optional) options = { ... } }")

        -- Avoid further computation
        return
    end

    -- Ensure 'options' integrity
    options = pickerAndOptions.options or {}

    -- Use Telescope's existing function to obtain a default 'entry_maker' function
    -- ----------------------------------------------------------------------------
    -- INSIGHT: Because calling this function effectively returns an 'entry_maker' function that is ready to
    --          handle entry creation, we can later call it to obtain the final entry table, which will
    --          ultimately be used by Telescope to display the entry by executing its 'display' key function.
    --          This reduces our work by only having to replace the 'display' function in said table instead
    --          of having to manipulate the rest of the data too.
    local originalEntryMaker = telescopeMakeEntryModule.gen_from_file(options)

    -- INSIGHT: 'entry_maker' is the hardcoded name of the option Telescope reads to obtain the function that
    --          will generate each entry.
    -- INSIGHT: The paramenter 'line' is the actual data to be displayed by the picker, however, its form is
    --          raw (type 'any) and must be transformed into an entry table.
    options.entry_maker = function(line)
        -- Generate the Original Entry table
        local originalEntryTable = originalEntryMaker(line)

        -- INSIGHT: An "entry display" is an abstract concept that defines the "container" within which data
        --          will be displayed inside the picker, this means that we must define options that define
        --          its dimensions, like, for example, its width.
        local displayer = telescopeEntryDisplayModule.create({
            separator = ' ', -- Telescope will use this separator between each entry item
            items = {
                { width = fileTypeIconWidth },
                { width = nil },
                { remaining = true },
            },
        })

        -- LIFECYCLE: At this point the "displayer" has been created by the create() method, which has in turn
        --            returned a function. This means that we can now call said function by using the
        --            'displayer' variable and pass it actual entry values so that it will, in turn, output
        --            the entry for display.
        --
        -- INSIGHT: We now have to replace the 'display' key in the original entry table to modify the way it
        --          is displayed.
        -- INSIGHT: The 'entry' is the same Original Entry Table but is is passed to the 'display()' function
        --          later on the program execution, most likely when the actual display is made, which could
        --          be deferred to allow lazy loading.
        --
        -- HELP: Read the 'make_entry.lua' file for more info on how all of this works
        originalEntryTable.display = function(entry)
            -- Get the Tail and the Path to display
            local tail, pathToDisplay = telescopePickers.getPathAndTail(entry.value)

            -- Add an extra space to the tail so that it looks nicely separated from the path
            local tailForDisplay = tail .. ' '

            -- Get the Icon with its corresponding Highlight information
            local icon, iconHighlight = telescopeUtilities.get_devicons(tail)

            -- INSIGHT: This return value should be a tuple of 2, where the first value is the actual value
            --          and the second one is the highlight information, this will be done by the displayer
            --          internally and return in the correct format.
            return displayer({
                { icon, iconHighlight },
                tailForDisplay,
                { pathToDisplay, 'TelescopeResultsComment' },
            })
        end

        return originalEntryTable
    end

    -- Finally, check which file picker was requested and open it with its associated options
    if pickerAndOptions.picker == 'find_files' then
        require('telescope.builtin').find_files(options)
    elseif pickerAndOptions.picker == 'git_files' then
        require('telescope.builtin').git_files(options)
    elseif pickerAndOptions.picker == 'oldfiles' then
        require('telescope.builtin').oldfiles(options)
    elseif pickerAndOptions.picker == '' then
        print("Picker was not specified")
    else
        print("Picker is not supported by Pretty Find Files")
    end
end

-- Generates a Grep Search picker but beautified
-- ----------------------------------------------
-- This is a wrapping function used to modify the appearance of pickers that provide Grep Search
-- functionality, mainly because the default one doesn't look good. It does this by changing the 'display()'
-- function that Telescope uses to display each entry in the Picker.
--
-- @param (table) pickerAndOptions - A table with the following format:
--                                   {
--                                      picker = '<pickerName>',
--                                      (optional) options = { ... }
--                                   }
function telescopePickers.prettyGrepPicker(pickerAndOptions)
    -- Parameter integrity check
    if type(pickerAndOptions) ~= 'table' or pickerAndOptions.picker == nil then
        print("Incorrect argument format. Correct format is: { picker = 'desiredPicker', (optional) options = { ... } }")

        -- Avoid further computation
        return
    end

    -- Ensure 'options' integrity
    options = pickerAndOptions.options or {}

    -- Use Telescope's existing function to obtain a default 'entry_maker' function
    -- ----------------------------------------------------------------------------
    -- INSIGHT: Because calling this function effectively returns an 'entry_maker' function that is ready to
    --          handle entry creation, we can later call it to obtain the final entry table, which will
    --          ultimately be used by Telescope to display the entry by executing its 'display' key function.
    --          This reduces our work by only having to replace the 'display' function in said table instead
    --          of having to manipulate the rest of the data too.
    local originalEntryMaker = telescopeMakeEntryModule.gen_from_vimgrep(options)

    -- INSIGHT: 'entry_maker' is the hardcoded name of the option Telescope reads to obtain the function that
    --          will generate each entry.
    -- INSIGHT: The paramenter 'line' is the actual data to be displayed by the picker, however, its form is
    --          raw (type 'any) and must be transformed into an entry table.
    options.entry_maker = function(line)
        -- Generate the Original Entry table
        local originalEntryTable = originalEntryMaker(line)

        -- INSIGHT: An "entry display" is an abstract concept that defines the "container" within which data
        --          will be displayed inside the picker, this means that we must define options that define
        --          its dimensions, like, for example, its width.
        local displayer = telescopeEntryDisplayModule.create({
            separator = ' ', -- Telescope will use this separator between each entry item
            items = {
                { width = fileTypeIconWidth },
                { width = nil },
                { width = nil }, -- Maximum path size, keep it short
                { remaining = true },
            },
        })

        -- LIFECYCLE: At this point the "displayer" has been created by the create() method, which has in turn
        --            returned a function. This means that we can now call said function by using the
        --            'displayer' variable and pass it actual entry values so that it will, in turn, output
        --            the entry for display.
        --
        -- INSIGHT: We now have to replace the 'display' key in the original entry table to modify the way it
        --          is displayed.
        -- INSIGHT: The 'entry' is the same Original Entry Table but is is passed to the 'display()' function
        --          later on the program execution, most likely when the actual display is made, which could
        --          be deferred to allow lazy loading.
        --
        -- HELP: Read the 'make_entry.lua' file for more info on how all of this works
        originalEntryTable.display = function(entry)
            ---- Get File columns data ----
            -------------------------------

            -- Get the Tail and the Path to display
            local tail, pathToDisplay = telescopePickers.getPathAndTail(entry.filename)

            -- Get the Icon with its corresponding Highlight information
            local icon, iconHighlight = telescopeUtilities.get_devicons(tail)

            ---- Format Text for display ----
            ---------------------------------

            -- Add coordinates if required by 'options'
            local coordinates = ""

            if not options.disable_coordinates then
                if entry.lnum then
                    if entry.col then
                        coordinates = string.format(" -> %s:%s", entry.lnum, entry.col)
                    else
                        coordinates = string.format(" -> %s", entry.lnum)
                    end
                end
            end

            -- Append coordinates to tail
            tail = tail .. coordinates

            -- Add an extra space to the tail so that it looks nicely separated from the path
            local tailForDisplay = tail .. ' '

            -- Encode text if necessary
            local text = options.file_encoding and vim.iconv(entry.text, options.file_encoding, "utf8") or entry.text

            -- INSIGHT: This return value should be a tuple of 2, where the first value is the actual value
            --          and the second one is the highlight information, this will be done by the displayer
            --          internally and return in the correct format.
            return displayer({
                { icon, iconHighlight },
                tailForDisplay,
                { pathToDisplay, 'TelescopeResultsComment' },
                text
            })
        end

        return originalEntryTable
    end

    -- Finally, check which file picker was requested and open it with its associated options
    if pickerAndOptions.picker == 'live_grep' then
        require('telescope.builtin').live_grep(options)
    elseif pickerAndOptions.picker == 'grep_string' then
        require('telescope.builtin').grep_string(options)
    elseif pickerAndOptions.picker == '' then
        print("Picker was not specified")
    else
        print("Picker is not supported by Pretty Grep Picker")
    end
end

-- Return the module for use
return telescopePickers

Awesome, will be default for https://github.com/otavioschwanck/mood-nvim

otavioschwanck avatar Sep 24 '23 15:09 otavioschwanck

@towry do you have a buffers picker too?

otavioschwanck avatar Sep 24 '23 16:09 otavioschwanck

Here's an addition to @towry's for lsp_document_symbols, lsp_dynamic_workspace_symbols, and buffers.

Buffers: Screenshot 2023-10-06 at 9 27 58 AM

Document symbols: Screenshot 2023-10-06 at 9 28 07 AM

Workspace symbols: Screenshot 2023-10-06 at 9 28 19 AM

Can be used like so:

vim.keymap.set('n', '<leader>fb', function() pickers.prettyBuffersPicker() end, opts)
vim.keymap.set('n', '<leader>fs', function() pickers.prettyDocumentSymbols() end, opts)
vim.keymap.set('n', '<leader>ws', function() pickers.prettyWorkspaceSymbols() end, opts)

Append the below snippet to your telescopePickers.lua file that towry created (before the return statement). Note that this uses a local kind_icons array for the lsp symbols, but if you don't want them, you can render them some other way.

local kind_icons = {
  Text = "",
  String = "",
  Array = "",
  Object = "󰅩",
  Namespace = "",
  Method = "m",
  Function = "󰊕",
  Constructor = "",
  Field = "",
  Variable = "󰫧",
  Class = "",
  Interface = "",
  Module = "",
  Property = "",
  Unit = "",
  Value = "",
  Enum = "",
  Keyword = "",
  Snippet = "",
  Color = "",
  File = "",
  Reference = "",
  Folder = "",
  EnumMember = "",
  Constant = "",
  Struct = "",
  Event = "",
  Operator = "",
  TypeParameter = "",
  Copilot = "🤖",
  Boolean = "",
}

function telescopePickers.prettyDocumentSymbols(localOptions)
    if localOptions ~= nil and type(localOptions) ~= 'table' then
        print("Options must be a table.")
        return
    end

    options = localOptions or {}

    local originalEntryMaker = telescopeMakeEntryModule.gen_from_lsp_symbols(options)

    options.entry_maker = function(line)
        local originalEntryTable = originalEntryMaker(line)

        local displayer = telescopeEntryDisplayModule.create({
            separator = ' ',
            items = {
                { width = fileTypeIconWidth },
                { width = 20 },
                { remaining = true },
            },
        })

        originalEntryTable.display = function(entry)
            return displayer {
                string.format("%s", kind_icons[(entry.symbol_type:lower():gsub("^%l", string.upper))]),
                { entry.symbol_type:lower(), 'TelescopeResultsVariable' },
                { entry.symbol_name, 'TelescopeResultsConstant' },
            }
        end

        return originalEntryTable
    end

    require('telescope.builtin').lsp_document_symbols(options)
end

function telescopePickers.prettyWorkspaceSymbols(localOptions)
    if localOptions ~= nil and type(localOptions) ~= 'table' then
        print("Options must be a table.")
        return
    end

    options = localOptions or {}

    local originalEntryMaker = telescopeMakeEntryModule.gen_from_lsp_symbols(options)

    options.entry_maker = function(line)
        local originalEntryTable = originalEntryMaker(line)

        local displayer = telescopeEntryDisplayModule.create({
            separator = ' ',
            items = {
                { width = fileTypeIconWidth },
                { width = 15 },
                { width = 30 },
                { width = nil },
                { remaining = true },
            },
        })

        originalEntryTable.display = function(entry)
            local tail, _ = telescopePickers.getPathAndTail(entry.filename)
            local tailForDisplay = tail .. ' '
            local pathToDisplay = telescopeUtilities.transform_path({
                path_display = { shorten = { num = 2, exclude = {-2, -1} }, 'truncate' },

            }, entry.value.filename)

            return displayer {
                string.format("%s", kind_icons[(entry.symbol_type:lower():gsub("^%l", string.upper))]),
                { entry.symbol_type:lower(), 'TelescopeResultsVariable' },
                { entry.symbol_name, 'TelescopeResultsConstant' },
                tailForDisplay,
                { pathToDisplay, 'TelescopeResultsComment' },
            }
        end

        return originalEntryTable
    end

    require('telescope.builtin').lsp_dynamic_workspace_symbols(options)
end

function telescopePickers.prettyBuffersPicker(localOptions)
    if localOptions ~= nil and type(localOptions) ~= 'table' then
        print("Options must be a table.")
        return
    end

    options = localOptions or {}

    local originalEntryMaker = telescopeMakeEntryModule.gen_from_buffer(options)

    options.entry_maker = function(line)
        local originalEntryTable = originalEntryMaker(line)

        local displayer = telescopeEntryDisplayModule.create {
            separator = " ",
            items = {
              { width = fileTypeIconWidth },
              { width = nil },
              { width = nil },
              { remaining = true },
            },
          }

        originalEntryTable.display = function(entry)
            local tail, path = telescopePickers.getPathAndTail(entry.filename)
            local tailForDisplay = tail .. ' '
            local icon, iconHighlight = telescopeUtilities.get_devicons(tail)

            return displayer {
              { icon, iconHighlight },
              tailForDisplay,
              { '(' .. entry.bufnr .. ')', "TelescopeResultsNumber" },
              { path, "TelescopeResultsComment" },
            }
        end

        return originalEntryTable
    end

    require('telescope.builtin').buffers(options)
end

ClintWinter avatar Oct 06 '23 13:10 ClintWinter

maybe turn this into a proper extension and get easy stars :grin:

jamestrew avatar Oct 06 '23 23:10 jamestrew

@ClintWinter would be awesome to have a git_status too

otavioschwanck avatar Oct 13 '23 19:10 otavioschwanck

I'm using telescope a lot for lsp references and definitions and it's really hard to see what files are affected. Would be very cool if we had something similar for the lsp stuff too :)

uloco avatar Nov 03 '23 16:11 uloco

Love all the custom pickers shared here, but the snippets were all so large, making it daunting to customize them. So I came up with a much simpler solution. Works by coloring everything after two tabs in the telescope result window, though any other unique string will also work. (I checked, and apparently no telescope picker uses two consecutive tabs, so this does not seem to have any undesired side effects.)

vim.api.nvim_create_autocmd("FileType", {
	pattern = "TelescopeResults",
	callback = function(ctx)
		vim.api.nvim_buf_call(ctx.buf, function()
			vim.fn.matchadd("TelescopeParent", "\t\t.*$")
			vim.api.nvim_set_hl(0, "TelescopeParent", { link = "Comment" })
		end)
	end,
})

local function filenameFirst(_, path)
	local tail = vim.fs.basename(path)
	local parent = vim.fs.dirname(path)
	if parent == "." then return tail end
	return string.format("%s\t\t%s", tail, parent)
end

require("telescope").setup {
	pickers = {
		find_files = {
			path_display = filenameFirst,
		}
	}
}

And this is the result: Showcase telescope paths

chrisgrieser avatar Jan 01 '24 08:01 chrisgrieser

This is great @chrisgrieser, I tried the other approaches in this thread but they proved too daunting :/

For any Lazyvim users that are a bit slow like me, this might save you some time: Put this code in a new [whatever].lua file under your nvim/lua dir. It's exactly like @chrisgrieser's submission above, with it turned on for git_files as well as find_files, and the last part wrapped in a return statement (without that I was getting an error: Invalid spec module: "plugins. telescope-format'. Expected a 'table' of specs, but a 'boolean' was returned instead).

vim.api.nvim_create_autocmd("FileType", {
  pattern = "TelescopeResults",
  callback = function(ctx)
    vim.api.nvim_buf_call(ctx.buf, function()
      vim.fn.matchadd("TelescopeParent", "\t\t.*$")
      vim.api.nvim_set_hl(0, "TelescopeParent", { link = "Comment" })
    end)
  end,
})

local function filenameFirst(_, path)
  local tail = vim.fs.basename(path)
  local parent = vim.fs.dirname(path)
  if parent == "." then
    return tail
  end
  return string.format("%s\t\t%s", tail, parent)
end

return {
  require("telescope").setup({
    pickers = {
      find_files = {
        path_display = filenameFirst,
      },
      git_files = {
        path_display = filenameFirst,
      },
    },
  }),
}

iryan2 avatar Jan 02 '24 00:01 iryan2

Thanks @chrisgrieser! Works like a charm for find_files. Is there anything else needed for the rest of the pickers? I cannot seem to make git_status work:

local function filenameFirst(_, path)
  local tail = vim.fs.basename(path)
  local parent = vim.fs.dirname(path)
  if parent == "." then return tail end
  return string.format("%s\t\t%s", tail, parent)
end

vim.api.nvim_create_autocmd("FileType", {
  pattern = "TelescopeResults",
  callback = function(ctx)
    vim.api.nvim_buf_call(ctx.buf, function()
      vim.fn.matchadd("TelescopeParent", "\t\t.*$")
      vim.api.nvim_set_hl(0, "TelescopeParent", { link = "Comment" })
    end)
  end,
})

require("telescope").setup({
  pickers = {
    git_status = { path_display = filenameFirst, },
    find_files = { path_display = filenameFirst, },
  },
})

mturoci avatar Jan 05 '24 09:01 mturoci

Thanks @chrisgrieser! Works like a charm for find_files. Is there anything else needed for the rest of the pickers? I cannot seem to make git_status work:

local function filenameFirst(_, path)
  local tail = vim.fs.basename(path)
  local parent = vim.fs.dirname(path)
  if parent == "." then return tail end
  return string.format("%s\t\t%s", tail, parent)
end

vim.api.nvim_create_autocmd("FileType", {
  pattern = "TelescopeResults",
  callback = function(ctx)
    vim.api.nvim_buf_call(ctx.buf, function()
      vim.fn.matchadd("TelescopeParent", "\t\t.*$")
      vim.api.nvim_set_hl(0, "TelescopeParent", { link = "Comment" })
    end)
  end,
})

require("telescope").setup({
  pickers = {
    git_status = { path_display = filenameFirst, },
    find_files = { path_display = filenameFirst, },
  },
})

For anyone who wants @chrisgrieser s solution for every available picker, just add it to your defaults like this:

telescope.setup({
  ...
  defaults = {
    path_display = filenameFirst
  }
  ...
})

silicakes avatar Jan 10 '24 15:01 silicakes

just add it to your defaults like this:

Tried already. Doesn't work. Seems like this is a known bug though.

mturoci avatar Jan 10 '24 15:01 mturoci

just add it to your defaults like this:

Tried already. Doesn't work. Seems like this is a known bug though.

I just fixed path_display for git_status https://github.com/nvim-telescope/telescope.nvim/pull/2881

jamestrew avatar Jan 21 '24 01:01 jamestrew

Love all the custom pickers shared here, but the snippets were all so large, making it daunting to customize them. So I came up with a much simpler solution. Works by coloring everything after two tabs in the telescope result window, though any other unique string will also work. (I checked, and apparently no telescope picker uses two consecutive tabs, so this does not seem to have any undesired side effects.)

vim.api.nvim_create_autocmd("FileType", {
	pattern = "TelescopeResults",
	callback = function(ctx)
		vim.api.nvim_buf_call(ctx.buf, function()
			vim.fn.matchadd("TelescopeParent", "\t\t.*$")
			vim.api.nvim_set_hl(0, "TelescopeParent", { link = "Comment" })
		end)
	end,
})

local function filenameFirst(_, path)
	local tail = vim.fs.basename(path)
	local parent = vim.fs.dirname(path)
	if parent == "." then return tail end
	return string.format("%s\t\t%s", tail, parent)
end

require("telescope").setup {
	pickers = {
		find_files = {
			path_display = filenameFirst,
		}
	}
}

And this is the result: Showcase telescope paths

This should be one of the default options, it looks great

Skydler avatar Jan 30 '24 18:01 Skydler

Love all the custom pickers shared here, but the snippets were all so large, making it daunting to customize them. So I came up with a much simpler solution. Works by coloring everything after two tabs in the telescope result window, though any other unique string will also work. (I checked, and apparently no telescope picker uses two consecutive tabs, so this does not seem to have any undesired side effects.)

vim.api.nvim_create_autocmd("FileType", {
	pattern = "TelescopeResults",
	callback = function(ctx)
		vim.api.nvim_buf_call(ctx.buf, function()
			vim.fn.matchadd("TelescopeParent", "\t\t.*$")
			vim.api.nvim_set_hl(0, "TelescopeParent", { link = "Comment" })
		end)
	end,
})

local function filenameFirst(_, path)
	local tail = vim.fs.basename(path)
	local parent = vim.fs.dirname(path)
	if parent == "." then return tail end
	return string.format("%s\t\t%s", tail, parent)
end

require("telescope").setup {
	pickers = {
		find_files = {
			path_display = filenameFirst,
		}
	}
}

And this is the result: Showcase telescope paths

Thanks, can you please explain where this snippet should go? I'm new to both neovim and lua.

itamark-targa avatar Feb 08 '24 11:02 itamark-targa

Love all the custom pickers shared here, but the snippets were all so large, making it daunting to customize them. So I came up with a much simpler solution. Works by coloring everything after two tabs in the telescope result window, though any other unique string will also work. (I checked, and apparently no telescope picker uses two consecutive tabs, so this does not seem to have any undesired side effects.)

vim.api.nvim_create_autocmd("FileType", {
	pattern = "TelescopeResults",
	callback = function(ctx)
		vim.api.nvim_buf_call(ctx.buf, function()
			vim.fn.matchadd("TelescopeParent", "\t\t.*$")
			vim.api.nvim_set_hl(0, "TelescopeParent", { link = "Comment" })
		end)
	end,
})

local function filenameFirst(_, path)
	local tail = vim.fs.basename(path)
	local parent = vim.fs.dirname(path)
	if parent == "." then return tail end
	return string.format("%s\t\t%s", tail, parent)
end

require("telescope").setup {
	pickers = {
		find_files = {
			path_display = filenameFirst,
		}
	}
}

And this is the result: Showcase telescope paths

Thanks, can you please explain where this snippet should go? I'm new to both neovim and lua.

Hi Itamar, Feel free to check out my configuration for this:

https://github.com/silicakes/dotfiles/blob/main/.config/nvim/plugin/telescope.lua

silicakes avatar Feb 08 '24 14:02 silicakes

Love all the custom pickers shared here, but the snippets were all so large, making it daunting to customize them. So I came up with a much simpler solution. Works by coloring everything after two tabs in the telescope result window, though any other unique string will also work. (I checked, and apparently no telescope picker uses two consecutive tabs, so this does not seem to have any undesired side effects.)

vim.api.nvim_create_autocmd("FileType", {
	pattern = "TelescopeResults",
	callback = function(ctx)
		vim.api.nvim_buf_call(ctx.buf, function()
			vim.fn.matchadd("TelescopeParent", "\t\t.*$")
			vim.api.nvim_set_hl(0, "TelescopeParent", { link = "Comment" })
		end)
	end,
})

local function filenameFirst(_, path)
	local tail = vim.fs.basename(path)
	local parent = vim.fs.dirname(path)
	if parent == "." then return tail end
	return string.format("%s\t\t%s", tail, parent)
end

require("telescope").setup {
	pickers = {
		find_files = {
			path_display = filenameFirst,
		}
	}
}

And this is the result: Showcase telescope paths

would it be possible to do something similar for LSP workspace diagnostics ?

kronolynx avatar Mar 11 '24 14:03 kronolynx