otavioschwanck/telescope-alternate.nvim

Target file can be one of many file extensions / allow regexp in target template?

Closed this issue · 3 comments

In a mixed JS/TS project it's not possible to deterministically know the filename of the related file, it could be either .ts or .js.

How could one model this in a config? I tried using Lua regexp in the targets, but it seems it doesn't work.

{
  pattern = "([app|addon]+)/controllers/(.*/?)(.*).([jt]s)$",
  targets = {
      { template = "[1]/routes/[2][3].([jt]s)$", label = "Route" },
  }
},

I assume that this constraint is because having a non-deterministic relation conflicts with being able to create a new file? If so, would it make sense to have an additional config for each target to add a default pattern for new files and shift the the "no non-deterministic targets" constraint to these defaults, something like:

{
  pattern = "([app|addon]+)/controllers/(.*/?)(.*).([jt]s)$",
  targets = {
    { 
      template = "[1]/routes/[2][3].([jt]s)$", -- allows regex
      label = "Route", 
      enable_new = true, 
      template_for_new = "[1]/routes/[2][3].ts" }, -- disallows regex
     },
  },
}

The following kinda works - if no alternate file exists it gives an option to create a new .ts file.

However, if an alternate .js file exists then it shows the alternate file and also has an option to create a new .ts file:

{
  pattern = "([app|addon]+)/controllers/(.*/?)(.*).([jt]s)$",
  targets = {
    { template = "[1]/routes/[2][3].js", label = "Route JS", enable_new = true },
    { template = "[1]/routes/[2][3].ts", label = "Route TS", enable_new = false },
  },
},

(Also, as a side note, it seems that enable_new is inverted, true disabled it and false enables it...?)

The following kinda works - if no alternate file exists it gives an option to create a new .ts file.

However, if an alternate .js file exists then it shows the alternate file and also has an option to create a new .ts file:

{
  pattern = "([app|addon]+)/controllers/(.*/?)(.*).([jt]s)$",
  targets = {
    { template = "[1]/routes/[2][3].js", label = "Route JS", enable_new = true },
    { template = "[1]/routes/[2][3].ts", label = "Route TS", enable_new = false },
  },
},

(Also, as a side note, it seems that enable_new is inverted, true disabled it and false enables it...?)

fixed the problem with the inverted enable_new. Take some care after upgrade.

In a mixed JS/TS project it's not possible to deterministically know the filename of the related file, it could be either .ts or .js.

How could one model this in a config? I tried using Lua regexp in the targets, but it seems it doesn't work.

{
  pattern = "([app|addon]+)/controllers/(.*/?)(.*).([jt]s)$",
  targets = {
      { template = "[1]/routes/[2][3].([jt]s)$", label = "Route" },
  }
},

I assume that this constraint is because having a non-deterministic relation conflicts with being able to create a new file? If so, would it make sense to have an additional config for each target to add a default pattern for new files and shift the the "no non-deterministic targets" constraint to these defaults, something like:

{
  pattern = "([app|addon]+)/controllers/(.*/?)(.*).([jt]s)$",
  targets = {
    { 
      template = "[1]/routes/[2][3].([jt]s)$", -- allows regex
      label = "Route", 
      enable_new = true, 
      template_for_new = "[1]/routes/[2][3].ts" }, -- disallows regex
     },
  },
}

i liked the idea of template_new. When i have some time, i will implement.

i liked the idea of template_new. When i have some time, i will implement.

Cool, one thing to consider is that this format also might allow inferring the relationships between a group of multiple related files from one single definition and could open the possibility of greatly simplifying the config for complex projects by (almost) removing the need to redefine each side of the relationship between 2 files for a given pattern.

For example:

{
  pattern = "([app|addon]+)/controllers/(.*/?)(.*).([jt]s)$",
  targets = {
    { 
      template = "[1]/routes/[2][3].([jt]s)$",
      label = "Route", 
     },
  },
}

Seems to contain (almost) all of the information to internally rewrite the above to the traditional, statically defined config of:

{
  pattern = "([app|addon]+)/controllers/(.*/?)(.*).([jt]s)$",
  targets = {
    { 
      template = "[1]/routes/[2][3].([jt]s)$",
      label = "Route", 
     },
  },
},
{
  pattern = "([app|addon]+)/routes/(.*/?)(.*).([jt]s)$",
  targets = {
    { 
      template = "[1]/controllers/[2][3].([jt]s)$",
      label = "MISSING", 
     },
  },
}

(The only thing missing is a label for the inverse (MISSING in the example), but that could be added as a new attribute alongside pattern and targets).

For complex project definitions ([1]) could allow simplifying a complex config down to a single definition. Specifically, given the following:

  pattern = "([app|addon]+)/controllers/(.*/?)(.*).([jt]s)$",
  targets = {
    { 
      template = "[1]/routes/[2][3].([jt]s)$",
  }

You can infer that:

  • there is a 2-way relationship between a route and a controller
  • A pattern for a route can be identified by reverse engineering it's related controller path to be:
"([app|addon]+)/routes/(.*/?)(.*).([jt]s)$",

(The inference / "reverse engineering" is possible because of gmatch returning indexed results, so it's possible to find the nth Lua pattern in the original pattern string using the template's [n] placeholders.)

Thus, internally, a virtual config can be created which creates all of the inverse definitions for each of the targets as a pattern related to all other targets in the original block (not sure that makes sense 🤯).

Just some food for thought 😉.


[1] - An example of a "complex" definition might be the following, which requires 5 additional definitions for each of the targets in this group of matching files, each 10 lines long, for a total of 60 lines of config of extra maintenance burden vs these 10 lines.

{
  pattern = "([app|addon]+)/components/(.*/?)(.*).([jt]s)$",
  targets = {
    { template = "[1]/components/[2][3].hbs", label = "Template", enable_new = false },
    { template = "tests/integration/components/[2][3]-test.js", label = "Test", enable_new = false },
    { template = "[1]/components/[2][3].stories.js", label = "Storybook", enable_new = false },
    { template = "[1]/styles/component-styles/[2][3].scss", label = "SCSS", enable_new = true },
    { template = "[1]/styles/component/[2][3].scss", label = "SCSS", enable_new = true },
  },
}