26 Commits

Author SHA1 Message Date
github-actions[bot]
335b3ad36d chore(master): release 0.9.1 2024-07-10 16:21:44 +03:00
9e628239ab docs: update docs lib version & regenerate 2024-07-10 16:18:54 +03:00
d7dd1c4dd7 fix: normal mode transforms 2024-07-10 16:18:54 +03:00
16069971e9 docs: update doc file 2024-07-10 16:18:54 +03:00
9c62c9cd9d refactor: move cache file to .cache dir 2024-07-10 16:18:54 +03:00
33eecfffab refactor: use plugin name 2024-05-07 01:00:38 +03:00
701d373e7a refactor: remove main.lua 2024-05-07 01:00:38 +03:00
1c8fa21578 refactor: file structure 2024-05-07 01:00:38 +03:00
github-actions[bot]
1c39528e7c chore(master): release 0.9.0 2024-05-05 23:06:45 +03:00
972a44cf1e doc: add command desc 2024-05-05 23:02:08 +03:00
ecd4d6bbbc fix: range commands 2024-05-05 23:02:08 +03:00
ea38606a28 fix: sorter init 2024-05-05 23:02:08 +03:00
3362c3c4f1 refactor: clean ups 2024-05-05 23:02:08 +03:00
a9d3ffc2de test: add tests 2024-05-05 23:02:08 +03:00
168552b425 fix: error on select 2024-05-05 23:02:08 +03:00
8e7fe74888 docs: update auto docs 2024-05-05 23:02:08 +03:00
4bbba5d1b3 docs: update readme 2024-05-05 23:02:08 +03:00
eaf84d857d fix: commands 2024-05-05 23:02:08 +03:00
0cb9c1e4ee docs: update readme 2024-05-05 23:02:08 +03:00
8de9125de1 refactor: make telescope dependency optional
lazy load the telescope popup, so that if it's disabled
it won't be attempted
2024-05-05 23:02:08 +03:00
bb177a3360 feat: non-telescope popup 2024-05-05 23:02:08 +03:00
67aef1de42 docs: update readme 2024-05-05 23:02:08 +03:00
github-actions[bot]
b44d58a533 chore(master): release 0.8.0 2024-05-04 11:36:27 +03:00
6863e0e9a6 feat: allow sorter selection in config 2024-05-04 11:33:43 +03:00
733f26fef6 fix: frequency sort 2024-05-04 11:33:43 +03:00
be9c119ee2 fix: user commands 2024-05-04 11:33:43 +03:00
23 changed files with 842 additions and 664 deletions

View File

@@ -1,5 +1,40 @@
# Changelog
## [0.9.1](https://github.com/chenasraf/text-transform.nvim/compare/v0.9.0...v0.9.1) (2024-07-10)
### Bug Fixes
* normal mode transforms ([d7dd1c4](https://github.com/chenasraf/text-transform.nvim/commit/d7dd1c4dd7e4ccd2da1458e5fb9a6653ad4e5e17))
## [0.9.0](https://github.com/chenasraf/text-transform.nvim/compare/v0.8.0...v0.9.0) (2024-05-05)
### Features
* non-telescope popup ([bb177a3](https://github.com/chenasraf/text-transform.nvim/commit/bb177a3360372f3fd40c82da71a13f1db59d3d9e))
### Bug Fixes
* commands ([eaf84d8](https://github.com/chenasraf/text-transform.nvim/commit/eaf84d857d860828cbb5af96887a24c2ab99f8e8))
* error on select ([168552b](https://github.com/chenasraf/text-transform.nvim/commit/168552b4259672fe918b1f3baeed787548e8fd03))
* range commands ([ecd4d6b](https://github.com/chenasraf/text-transform.nvim/commit/ecd4d6bbbcd45231f2e8ea00961f076ff56dd1e7))
* sorter init ([ea38606](https://github.com/chenasraf/text-transform.nvim/commit/ea38606a28f2308a37b301ebb4c56e2bd584b459))
## [0.8.0](https://github.com/chenasraf/text-transform.nvim/compare/v0.7.0...v0.8.0) (2024-05-04)
### Features
* allow sorter selection in config ([6863e0e](https://github.com/chenasraf/text-transform.nvim/commit/6863e0e9a66b5592fbba2aea7a07760465844cf7))
### Bug Fixes
* frequency sort ([733f26f](https://github.com/chenasraf/text-transform.nvim/commit/733f26fef61b050ae8f62582e3f9c03268f8e835))
* user commands ([be9c119](https://github.com/chenasraf/text-transform.nvim/commit/be9c119ee23f1e93a5bd537c01f55e11d2607180))
## [0.7.0](https://github.com/chenasraf/text-transform.nvim/compare/v0.6.0...v0.7.0) (2024-05-03)

170
README.md
View File

@@ -38,114 +38,51 @@ with `my_var` or vice versa? This plugin is for you!
## 🔽 Installation
<div>
<table>
<thead>
<tr>
<th>
<img width="221" height="1"/>
Package manager
</th>
<th>
<img width="661" height="1"/>
Snippet
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
[folke/lazy.nvim](https://github.com/folke/lazy.nvim)
</td>
<td>
### [Lazy](https://github.com/folke/lazy.nvim)
```lua
-- stable version
require("lazy").setup({
"chenasraf/text-transform.nvim",
-- stable version
version = "*", -- or: tag = "stable"
dependencies = {
-- for Telescope popup
'nvim-telescope/telescope.nvim',
'nvim-lua/plenary.nvim',
},
})
-- dev version
require("lazy").setup({
"chenasraf/text-transform.nvim",
branch = "develop",
dependencies = {
-- for Telescope popup
'nvim-telescope/telescope.nvim',
'nvim-lua/plenary.nvim',
},
-- dev version
-- branch = "develop",
-- Optional - for Telescope popup
dependencies = { 'nvim-telescope/telescope.nvim', 'nvim-lua/plenary.nvim' }
})
```
</td>
</tr>
<tr>
<td>
[wbthomason/packer.nvim](https://github.com/wbthomason/packer.nvim)
</td>
<td>
### [Packer](https://github.com/wbthomason/packer.nvim)
```lua
-- stable version
use { "chenasraf/text-transform.nvim",
-- stable version
tag = "stable",
requires = {
-- for Telescope popup
'nvim-telescope/telescope.nvim',
'nvim-lua/plenary.nvim',
}
}
-- dev version
use { "chenasraf/text-transform.nvim",
branch = "develop",
requires = {
-- for Telescope popup
'nvim-telescope/telescope.nvim',
'nvim-lua/plenary.nvim',
}
-- dev version
-- branch = "develop",
-- Optional - for Telescope popup
requires = { 'nvim-telescope/telescope.nvim', 'nvim-lua/plenary.nvim' }
}
```
</td>
</tr>
<tr>
<td>
[junegunn/vim-plug](https://github.com/junegunn/vim-plug)
</td>
<td>
### [Plug](https://github.com/junegunn/vim-plug)
```vim
-- Dependencies - for Telescope popup
Plug "nvim-telescope/telescope.nvim"
Plug "nvim-lua/plenary.nvim"
" Dependencies - optional for Telescope popup
Plug 'nvim-telescope/telescope.nvim'
Plug 'nvim-lua/plenary.nvim'
-- stable version
Plug "chenasraf/text-transform.nvim", {
"tag": "stable",
}
-- dev version
Plug "chenasraf/text-transform.nvim", {
"branch": "develop",
}
" stable version
Plug 'chenasraf/text-transform.nvim', { 'tag': 'stable' }
" dev version
Plug 'chenasraf/text-transform.nvim', { 'branch': 'develop' }
```
</td>
</tr>
If you decide not to use Telescope, you can ignore the dependencies. In that case, be sure to change
your config with `popup_type = 'select'` so that TextTransform never tries to load Telescope.
</tbody>
</table>
</div>
It falls back to `vim.ui.select()` instead, which may or may not still be Telescope behind the
scenes, or something else; depending on your setup.
## 🚀 Getting started
@@ -177,16 +114,16 @@ rest to use the defaults.
```lua
require("text-transform").setup({
-- Prints information about internals of the plugin. Very verbose, only useful for debugging.
--- Prints information about internals of the plugin. Very verbose, only useful for debugging.
debug = false,
-- Keymap configurations
--- Keymap configurations
keymap = {
-- Keymap to open the telescope popup. Set to `false` or `nil` to disable keymapping
-- You can always customize your own keymapping manually.
--- Keymap to open the telescope popup. Set to `false` or `nil` to disable keymapping
--- You can always customize your own keymapping manually.
telescope_popup = {
-- Opens the popup in normal mode
--- Opens the popup in normal mode
["n"] = "<Leader>~",
-- Opens the popup in visual/visual block modes
--- Opens the popup in visual/visual block modes
["v"] = "<Leader>~",
},
},
@@ -204,6 +141,14 @@ require("text-transform").setup({
snake_case = { enabled = true },
title_case = { enabled = true },
},
--- Sort the replacers in the popup.
--- Possible values: 'frequency', 'name'
sort_by = "frequency",
--- The popup type to show.
--- Possible values: 'telescope', 'select'
popup_type = 'telescope'
})
```
@@ -211,16 +156,18 @@ require("text-transform").setup({
The following commands are available for your use in your own mappings or for reference.
| Command | Description |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `:TtTelescope` \| `:TextTransform` | Pops up a Telescope window with the available converters which will directly act on the selected text. |
| `:TtCamel` | Replaces selection with `camelCase`. |
| `:TtConst` | Replaces selection with `CONST_CASE`. |
| `:TtDot` | Replaces selection with `dot.case`. |
| `:TtKebab` | Replaces selection with `kebab-case`. |
| `:TtPascal` | Replaces selection with `PascalCase`. |
| `:TtSnake` | Replaces selection with `snake_case`. |
| `:TtTitle` | Replaces selection with `Title Case`. |
| Command | Description |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `:TextTransform` | Pop up a either a Telescope window or a selection popup, depending on the `popup_type` config. |
| `:TtTelescope` | Pop up a Telescope window with all the transformers, which will directly act on the selected text or highlighted word. |
| `:TtSelect` | Pop up a selection popup with all the transformers, which will directly act on the selected text or highlighted word. |
| `:TtCamel` | Replace selection/word with `camelCase`. |
| `:TtSnake` | Replace selection/word with `snake_case`. |
| `:TtPascal` | Replace selection/word with `PascalCase`. |
| `:TtConst` | Replace selection/word with `CONST_CASE`. |
| `:TtDot` | Replace selection/word with `dot.case`. |
| `:TtKebab` | Replace selection/word with `kebab-case`. |
| `:TtTitle` | Replace selection/word with `Title Case`. |
## ⌨️⌨️ Keymaps
@@ -250,17 +197,16 @@ You can also create custom mappings to specific case conversions or to the Teles
```lua
-- Trigger telescope popup
local telescope = require('text-transform.telescope')
vim.keymap.set("n", "<leader>~~", telescope.popup, { silent = true })
vim.keymap.set("n", "<leader>~~", ":TtTelescope", { silent = true, desc = "Transform Text" })
-- Trigger case converters directly
vim.keymap.set({ "n", "v" }, "<leader>Ccc", ":TtCamel", { silent = true })
vim.keymap.set({ "n", "v" }, "<leader>Cco", ":TtConst", { silent = true })
vim.keymap.set({ "n", "v" }, "<leader>Cdo", ":TtDot", { silent = true })
vim.keymap.set({ "n", "v" }, "<leader>Cke", ":TtKebab", { silent = true })
vim.keymap.set({ "n", "v" }, "<leader>Cpa", ":TtPascal", { silent = true })
vim.keymap.set({ "n", "v" }, "<leader>Csn", ":TtSnake", { silent = true })
vim.keymap.set({ "n", "v" }, "<leader>Ctt", ":TtTitle", { silent = true })
vim.keymap.set({ "n", "v" }, "<leader>Ccc", ":TtCamel", { silent = true, desc = "To camelCase" })
vim.keymap.set({ "n", "v" }, "<leader>Csn", ":TtSnake", { silent = true, desc = "To snake_case" })
vim.keymap.set({ "n", "v" }, "<leader>Cpa", ":TtPascal", { silent = true, desc = "To PascalCase" })
vim.keymap.set({ "n", "v" }, "<leader>Cco", ":TtConst", { silent = true, desc = "To CONST_CASE" })
vim.keymap.set({ "n", "v" }, "<leader>Cdo", ":TtDot", { silent = true, desc = "To dot.case" })
vim.keymap.set({ "n", "v" }, "<leader>Cke", ":TtKebab", { silent = true, desc = "To kebab-case" })
vim.keymap.set({ "n", "v" }, "<leader>Ctt", ":TtTitle", { silent = true, desc = "To Title Case" })
```
## 💁🏻 Contributing

View File

@@ -1,14 +1,16 @@
TextTransform.enable() text-transform.txt /*TextTransform.enable()*
TextTransform.config text-transform.txt /*TextTransform.config*
TextTransform.get_visual_selection_details() text-transform.txt /*TextTransform.get_visual_selection_details()*
TextTransform.init_commands() text-transform.txt /*TextTransform.init_commands()*
TextTransform.options text-transform.txt /*TextTransform.options*
TextTransform.popup() text-transform.txt /*TextTransform.popup()*
TextTransform.merge() text-transform.txt /*TextTransform.merge()*
TextTransform.replace_columns() text-transform.txt /*TextTransform.replace_columns()*
TextTransform.replace_range() text-transform.txt /*TextTransform.replace_range()*
TextTransform.replace_selection() text-transform.txt /*TextTransform.replace_selection()*
TextTransform.replace_word() text-transform.txt /*TextTransform.replace_word()*
TextTransform.restore_positions() text-transform.txt /*TextTransform.restore_positions()*
TextTransform.save_positions() text-transform.txt /*TextTransform.save_positions()*
TextTransform.select_popup() text-transform.txt /*TextTransform.select_popup()*
TextTransform.setup() text-transform.txt /*TextTransform.setup()*
TextTransform.show_popup() text-transform.txt /*TextTransform.show_popup()*
TextTransform.telescope_popup() text-transform.txt /*TextTransform.telescope_popup()*
TextTransform.to_camel_case() text-transform.txt /*TextTransform.to_camel_case()*
TextTransform.to_const_case() text-transform.txt /*TextTransform.to_const_case()*
TextTransform.to_dot_case() text-transform.txt /*TextTransform.to_dot_case()*
@@ -17,8 +19,5 @@ TextTransform.to_pascal_case() text-transform.txt /*TextTransform.to_pascal_case
TextTransform.to_snake_case() text-transform.txt /*TextTransform.to_snake_case()*
TextTransform.to_title_case() text-transform.txt /*TextTransform.to_title_case()*
TextTransform.to_words() text-transform.txt /*TextTransform.to_words()*
TextTransform.toggle() text-transform.txt /*TextTransform.toggle()*
TextTransform.transform_words() text-transform.txt /*TextTransform.transform_words()*
find_word_boundaries() text-transform.txt /*find_word_boundaries()*
utils.dump() text-transform.txt /*utils.dump()*
utils.merge() text-transform.txt /*utils.merge()*

View File

@@ -1,30 +1,22 @@
==============================================================================
------------------------------------------------------------------------------
*TextTransform.init_commands()*
`TextTransform.init_commands`()
Initializes user commands
@private
==============================================================================
------------------------------------------------------------------------------
*TextTransform.options*
`TextTransform.options`
*TextTransform.config*
`TextTransform.config`
Your plugin configuration with its default values.
Default values:
>
TextTransform.options = {
-- Prints information about internals of the plugin. Very verbose, only useful for debugging.
>lua
TextTransform.config = {
--- Prints information about internals of the plugin. Very verbose, only useful for debugging.
debug = false,
-- Keymap configurations
--- Keymap configurations
keymap = {
-- Keymap to open the telescope popup. Set to `false` or `nil` to disable keymapping
-- You can always customize your own keymapping manually.
--- Keymap to open the telescope popup. Set to `false` or `nil` to disable keymapping
--- You can always customize your own keymapping manually.
telescope_popup = {
-- Opens the popup in normal mode
--- Opens the popup in normal mode
["n"] = "<Leader>~",
-- Opens the popup in visual/visual block modes
--- Opens the popup in visual/visual block modes
["v"] = "<Leader>~",
},
},
@@ -42,26 +34,17 @@ Default values:
snake_case = { enabled = true },
title_case = { enabled = true },
},
--- Sort the replacers in the popup.
--- Possible values: 'frequency', 'name'
sort_by = "frequency",
--- The popup type to show.
--- Possible values: 'telescope', 'select'
popup_type = "telescope",
}
local function init()
local o = TextTransform.options
D.log("config", "Initializing TextTransform with %s", utils.dump(o))
commands.init_commands()
if o.keymap.telescope_popup then
local keys = o.keymap.telescope_popup
if keys.n then
vim.keymap.set("n", keys.n, telescope.popup, { silent = true })
end
if keys.v then
vim.keymap.set("v", keys.v, telescope.popup, { silent = true })
end
end
end
<
------------------------------------------------------------------------------
*TextTransform.setup()*
`TextTransform.setup`({options})
@@ -79,9 +62,24 @@ Usage ~
*find_word_boundaries()*
`find_word_boundaries`({line}, {start_col})
Finds the boundaries of the surrounding word around `start_col` within `line`.
@param line number
@param start_col number
@return number start_col, number end_col
Parameters ~
{line} `(number)`
{start_col} `(number)`
Return ~
`(number)` start_col, number end_col
------------------------------------------------------------------------------
*TextTransform.replace_range()*
`TextTransform.replace_range`({start_line}, {start_col}, {end_line}, {end_col}, {transform_name})
Replace the range between the given positions with the given transform.
Acts on the lines between the given positions, replacing the text between the given columns.
Parameters ~
{start_line} `(number)` The starting line
{start_col} `(number)` The starting column
{end_line} `(number)` The ending line
{end_col} `(number)` The ending column
{transform_name} `(string)` The transformer name
------------------------------------------------------------------------------
*TextTransform.replace_word()*
@@ -90,8 +88,9 @@ Replace the word under the cursor with the given transform.
If `position` is provided, replace the word under the given position.
Otherwise, attempts to find the word under the cursor.
@param transform_name string The transformer name
@param position table|nil A table containing the position of the word to replace
Parameters ~
{transform_name} `(string)` The transformer name
{position} `(table|nil)` A table containing the position of the word to replace
------------------------------------------------------------------------------
*TextTransform.replace_columns()*
@@ -99,6 +98,9 @@ Otherwise, attempts to find the word under the cursor.
Replaces each column in visual block mode selection with the given transform.
Assumes that the each selection is 1 character and operates on the whole word under each cursor.
Parameters ~
{transform_name} `(string)` The transformer name
------------------------------------------------------------------------------
*TextTransform.replace_selection()*
`TextTransform.replace_selection`({transform_name})
@@ -106,7 +108,8 @@ Replaces a selection with the given transform. This function attempts to infer t
type based on the cursor positiono and visual selections, and passes information to relevant
range replacement functions.
@param transform_name string The transformer name
Parameters ~
{transform_name} `(string)` The transformer name
------------------------------------------------------------------------------
*TextTransform.get_visual_selection_details()*
@@ -119,35 +122,134 @@ the full information around the selection logic.
==============================================================================
------------------------------------------------------------------------------
*TextTransform.toggle()*
`TextTransform.toggle`()
Toggle the plugin by calling the `enable`/`disable` methods respectively.
@private
------------------------------------------------------------------------------
*TextTransform.enable()*
`TextTransform.enable`()
Enables the plugin
@private
------------------------------------------------------------------------------
*TextTransform.save_positions()*
`TextTransform.save_positions`()
Save the current cursor position, mode, and visual selection ranges
@private
------------------------------------------------------------------------------
*TextTransform.restore_positions()*
`TextTransform.restore_positions`({state})
`TextTransform.restore_positions`({positions})
Restore the cursor position, mode, and visual selection ranges saved using `save_position()`,
or a given modified state, if passed as the first argument
==============================================================================
------------------------------------------------------------------------------
*TextTransform.popup()*
`TextTransform.popup`()
*TextTransform.to_words()*
`TextTransform.to_words`({string})
Splits a string into words.
Parameters ~
{string} `(string)`
Return ~
`(table)`
------------------------------------------------------------------------------
*TextTransform.transform_words()*
`TextTransform.transform_words`({words}, {with_word_cb}, {separator})
Transforms a table of strings into a string using a callback and separator.
The callback is called with the word, the index, and the table of words.
The separator is added between each word.
Parameters ~
{words} `(string|table)` string or table of strings
{with_word_cb} `(function)` (word: string, index: number, words: table) -> string
{separator} `(string|nil)` (optional)
Return ~
`(string)`
------------------------------------------------------------------------------
*TextTransform.to_camel_case()*
`TextTransform.to_camel_case`({string})
Transforms a string into camelCase.
Parameters ~
{string} `(string)`
Return ~
`(string)`
------------------------------------------------------------------------------
*TextTransform.to_snake_case()*
`TextTransform.to_snake_case`({string})
Transfroms a string into snake_case.
Parameters ~
{string} `(any)`
Return ~
`(string)`
------------------------------------------------------------------------------
*TextTransform.to_pascal_case()*
`TextTransform.to_pascal_case`({string})
Transforms a string into PascalCase.
Parameters ~
{string} `(string)`
Return ~
`(string)`
------------------------------------------------------------------------------
*TextTransform.to_title_case()*
`TextTransform.to_title_case`({string})
Transforms a string into Title Case.
Parameters ~
{string} `(string)`
Return ~
`(string)`
------------------------------------------------------------------------------
*TextTransform.to_kebab_case()*
`TextTransform.to_kebab_case`({string})
Transforms a string into kebab-case.
Parameters ~
{string} `(string)`
Return ~
`(string)`
------------------------------------------------------------------------------
*TextTransform.to_dot_case()*
`TextTransform.to_dot_case`({string})
Transforms a string into dot.case.
Parameters ~
{string} `(string)`
Return ~
`(string)`
------------------------------------------------------------------------------
*TextTransform.to_const_case()*
`TextTransform.to_const_case`({string})
Transforms a string into CONSTANT_CASE.
Parameters ~
{string} `(string)`
Return ~
`(string)`
==============================================================================
------------------------------------------------------------------------------
*TextTransform.show_popup()*
`TextTransform.show_popup`()
Pops up a selection menu, containing the available case transformers.
When a transformer is selected, the cursor position/range/columns will be used to replace the
words around the cursor or inside the selection.
The cursor positions/ranges are saved before opening the menu and restored once a selection is
made.
==============================================================================
------------------------------------------------------------------------------
*TextTransform.select_popup()*
`TextTransform.select_popup`()
Pops up a selection menu, containing the available case transformers.
When a transformer is selected, the cursor position/range/columns will be used to replace the
words around the cursor or inside the selection.
The cursor positions/ranges are saved before opening the menu and restored once a selection is
made.
==============================================================================
------------------------------------------------------------------------------
*TextTransform.telescope_popup()*
`TextTransform.telescope_popup`()
Pops up a telescope menu, containing the available case transformers.
When a transformer is selected, the cursor position/range/columns will be used to replace the
words around the cursor or inside the selection.
@@ -158,93 +260,18 @@ made.
==============================================================================
------------------------------------------------------------------------------
*TextTransform.to_words()*
`TextTransform.to_words`({string})
Splits a string into words.
@param string string
@return table
------------------------------------------------------------------------------
*TextTransform.transform_words()*
`TextTransform.transform_words`({words}, {with_word_cb}, {separator})
Transforms a table of strings into a string using a callback and separator.
The callback is called with the word, the index, and the table of words.
The separator is added between each word.
@param words string|table string or table of strings
@param with_word_cb function (word: string, index: number, words: table) -> string
@param separator string|nil (optional)
@return string
------------------------------------------------------------------------------
*TextTransform.to_camel_case()*
`TextTransform.to_camel_case`({string})
Transforms a string into camelCase.
@param string string
@return string
------------------------------------------------------------------------------
*TextTransform.to_snake_case()*
`TextTransform.to_snake_case`({string})
Transfroms a string into snake_case.
@param string any
@return string
------------------------------------------------------------------------------
*TextTransform.to_pascal_case()*
`TextTransform.to_pascal_case`({string})
Transforms a string into PascalCase.
@param string string
@return string
------------------------------------------------------------------------------
*TextTransform.to_title_case()*
`TextTransform.to_title_case`({string})
Transforms a string into Title Case.
@param string string
@return string
------------------------------------------------------------------------------
*TextTransform.to_kebab_case()*
`TextTransform.to_kebab_case`({string})
Transforms a string into kebab-case.
@param string string
@return string
------------------------------------------------------------------------------
*TextTransform.to_dot_case()*
`TextTransform.to_dot_case`({string})
Transforms a string into dot.case.
@param string string
@return string
------------------------------------------------------------------------------
*TextTransform.to_const_case()*
`TextTransform.to_const_case`({string})
Transforms a string into CONSTANT_CASE.
@param string string
@return string
==============================================================================
------------------------------------------------------------------------------
*utils.merge()*
`utils.merge`({t1}, {t2})
*TextTransform.merge()*
`TextTransform.merge`({t1}, {t2})
Merges two tables into one. Same as `vim.tbl_extend("keep", t1, t2)`.
Mutates the first table.
TODO accept multiple tables to merge
@param t1 table
@param t2 table
@return table
------------------------------------------------------------------------------
*utils.dump()*
`utils.dump`({obj})
Dumps the object into a string.
@param obj any
@return string
Parameters ~
{t1} `(table)`
{t2} `(table)`
Return ~
`(table)`
vim:tw=78:ts=8:noet:ft=help:norl:

View File

@@ -1,9 +1,12 @@
local D = require("text-transform.utils.debug")
local util = require("text-transform.utils")
local state = require("text-transform.state")
local replacers = require("text-transform.replacers")
local telescope = require("text-transform.telescope")
local popup = require("text-transform.popup")
local common = require("text-transform.popup.common")
local TextTransform = {}
--- Initializes user commands
--- @private
function TextTransform.init_commands()
local map = {
TtCamel = "camel_case",
@@ -15,14 +18,54 @@ function TextTransform.init_commands()
TtTitle = "title_case",
}
for cmd, transformer_name in pairs(map) do
vim.api.nvim_create_user_command(cmd, function()
replacers.replace_selection(transformer_name)
end, {})
local cmdopts = { range = true, force = true }
local opts = function(desc)
return util.merge(cmdopts, { desc = desc })
end
vim.api.nvim_create_user_command("TtTelescope", telescope.popup, {})
vim.api.nvim_create_user_command("TextTransform", telescope.popup, {})
for cmd, transformer_name in pairs(map) do
local item
for _, t in ipairs(common.items) do
if t.value == transformer_name then
item = t.label
break
end
end
vim.api.nvim_create_user_command(cmd, function()
state.save_positions()
replacers.replace_selection(transformer_name)
vim.schedule(function()
state.restore_positions()
end)
end, opts("Change to " .. item))
end
-- specific popups
vim.api.nvim_create_user_command("TtTelescope", function()
local telescope = require("text-transform.popup.telescope")
telescope.telescope_popup()
end, opts("Change Case with Telescope"))
vim.api.nvim_create_user_command("TtSelect", function()
local select = require("text-transform.popup.select")
select.select_popup()
end, opts("Change Case with Select"))
-- auto popup by config
vim.api.nvim_create_user_command("TextTransform", popup.show_popup, opts("Change Case"))
end
function TextTransform.init_keymaps()
local keymaps = _G.TextTransform.config.keymap
D.log("init_keymaps", "Initializing keymaps, config %s", vim.inspect(_G.TextTransform))
if keymaps.telescope_popup then
local keys = keymaps.telescope_popup
if keys.n then
vim.keymap.set("n", keys.n, popup.show_popup, { silent = true, desc = "Change Case" })
end
if keys.v then
vim.keymap.set("v", keys.v, popup.show_popup, { silent = true, desc = "Change Case" })
end
end
end
return TextTransform

View File

@@ -1,24 +1,38 @@
local telescope = require("text-transform.telescope")
local commands = require("text-transform.commands")
local D = require("text-transform.util.debug")
local utils = require("text-transform.util")
local D = require("text-transform.utils.debug")
local utils = require("text-transform.utils")
local TextTransform = {}
local function ensure_config()
-- when the config is not set to the global object, we set it
if _G.TextTransform.config == nil then
_G.TextTransform.config = TextTransform.config
end
end
local function init()
ensure_config()
local o = TextTransform.config
D.log("config", "Initializing TextTransform with %s", vim.inspect(o))
commands.init_commands()
commands.init_keymaps()
end
--- Your plugin configuration with its default values.
---
--- Default values:
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
TextTransform.options = {
-- Prints information about internals of the plugin. Very verbose, only useful for debugging.
TextTransform.config = {
--- Prints information about internals of the plugin. Very verbose, only useful for debugging.
debug = false,
-- Keymap configurations
--- Keymap configurations
keymap = {
-- Keymap to open the telescope popup. Set to `false` or `nil` to disable keymapping
-- You can always customize your own keymapping manually.
--- Keymap to open the telescope popup. Set to `false` or `nil` to disable keymapping
--- You can always customize your own keymapping manually.
telescope_popup = {
-- Opens the popup in normal mode
--- Opens the popup in normal mode
["n"] = "<Leader>~",
-- Opens the popup in visual/visual block modes
--- Opens the popup in visual/visual block modes
["v"] = "<Leader>~",
},
},
@@ -36,24 +50,16 @@ TextTransform.options = {
snake_case = { enabled = true },
title_case = { enabled = true },
},
--- Sort the replacers in the popup.
--- Possible values: 'frequency', 'name'
sort_by = "frequency",
--- The popup type to show.
--- Possible values: 'telescope', 'select'
popup_type = "telescope",
}
local function init()
local o = TextTransform.options
D.log("config", "Initializing TextTransform with %s", utils.dump(o))
commands.init_commands()
if o.keymap.telescope_popup then
local keys = o.keymap.telescope_popup
if keys.n then
vim.keymap.set("n", keys.n, telescope.popup, { silent = true })
end
if keys.v then
vim.keymap.set("v", keys.v, telescope.popup, { silent = true })
end
end
end
--- Define your text-transform setup.
---
---@param options table Module config table. See |TextTransform.options|.
@@ -62,7 +68,7 @@ end
function TextTransform.setup(options)
options = options or {}
TextTransform.options = utils.merge(TextTransform.options, options)
TextTransform.config = utils.merge(TextTransform.config, options)
if vim.api.nvim_get_vvar("vim_did_enter") == 0 then
vim.defer_fn(function()
@@ -72,7 +78,7 @@ function TextTransform.setup(options)
init()
end
return TextTransform.options
return TextTransform.config
end
return TextTransform

View File

@@ -1,5 +1,8 @@
local M = require("text-transform.main")
local utils = require("text-transform.util")
local utils = require("text-transform.utils")
local tt = require("text-transform.transformers")
local replacers = require("text-transform.replacers")
local state = require("text-transform.state")
local popup = require("text-transform.popup")
local TextTransform = {}
@@ -7,6 +10,14 @@ function TextTransform.setup(opts)
_G.TextTransform.config = require("text-transform.config").setup(opts)
end
_G.TextTransform = utils.merge(M, TextTransform)
local function merge(table)
TextTransform = utils.merge(TextTransform, table)
end
merge(tt)
merge(replacers)
merge(state)
merge(popup)
_G.TextTransform = TextTransform
return _G.TextTransform

View File

@@ -1,18 +0,0 @@
local utils = require("text-transform.util")
local tt = require("text-transform.transformers")
local replacers = require("text-transform.replacers")
local state = require("text-transform.state")
local telescope = require("text-transform.telescope")
local TextTransform = {}
local function merge(table)
TextTransform = utils.merge(TextTransform, table)
end
merge(tt)
merge(replacers)
merge(state)
merge(telescope)
return TextTransform

View File

@@ -0,0 +1,67 @@
local D = require("text-transform.utils.debug")
local state = require("text-transform.state")
local replacers = require("text-transform.replacers")
local popup_common = {}
popup_common.items = {
{ label = "camelCase", value = "camel_case" },
{ label = "snake_case", value = "snake_case" },
{ label = "PascalCase", value = "pascal_case" },
{ label = "kebab-case", value = "kebab_case" },
{ label = "dot.case", value = "dot_case" },
{ label = "Title Case", value = "title_case" },
{ label = "CONST_CASE", value = "const_case" },
}
popup_common.default_frequency = {
camel_case = 1,
snake_case = 1,
pascal_case = 1,
kebab_case = 1,
dot_case = 1,
title_case = 1,
const_case = 1,
}
local cache_dir = vim.fn.stdpath("cache")
local frequency_file = cache_dir .. "/text-transform-frequency.json"
local frequency
function popup_common.load_frequency()
if frequency then
return frequency
end
if vim.fn.filereadable(frequency_file) == 0 then
frequency = popup_common.default_frequency
vim.fn.writefile({ vim.fn.json_encode(frequency) }, frequency_file)
else
frequency = vim.fn.json_decode(vim.fn.readfile(frequency_file))
end
D.log("telescope", "frequency loaded: %s", vim.inspect(frequency))
return frequency
end
function popup_common.inc_frequency(name)
frequency[name] = (frequency[name] or 0) + 1
D.log("telescope", "new frequency: %s %d", name, frequency[name])
vim.fn.writefile({ vim.fn.json_encode(frequency) }, frequency_file)
end
function popup_common.entry_maker(entry)
return {
value = entry.value,
display = entry.label,
ordinal = entry.label,
frequency = frequency[entry.value] or 1,
}
end
function popup_common.select(selection)
vim.schedule(function()
replacers.replace_selection(selection.value)
state.restore_positions()
end)
end
return popup_common

View File

@@ -0,0 +1,20 @@
local TextTransform = {}
--- Pops up a selection menu, containing the available case transformers.
--- When a transformer is selected, the cursor position/range/columns will be used to replace the
--- words around the cursor or inside the selection.
---
--- The cursor positions/ranges are saved before opening the menu and restored once a selection is
--- made.
function TextTransform.show_popup()
local config = _G.TextTransform.config
if config.popup_type == "telescope" then
local telescope = require("text-transform.popup.telescope")
telescope.telescope_popup()
else
local select = require("text-transform.popup.select")
select.select_popup()
end
end
return TextTransform

View File

@@ -0,0 +1,30 @@
local common = require("text-transform.popup.common")
local state = require("text-transform.state")
local TextTransform = {}
--- Pops up a selection menu, containing the available case transformers.
--- When a transformer is selected, the cursor position/range/columns will be used to replace the
--- words around the cursor or inside the selection.
---
--- The cursor positions/ranges are saved before opening the menu and restored once a selection is
--- made.
function TextTransform.select_popup()
common.load_frequency()
state.save_positions()
vim.ui.select(common.items, {
prompt = "Change Case",
format_item = function(item)
return item.label
end,
}, function(choice)
if not choice then
return
end
local item = common.entry_maker(choice)
common.select(item)
end)
end
return TextTransform

View File

@@ -0,0 +1,104 @@
local common = require("text-transform.popup.common")
local D = require("text-transform.utils.debug")
local state = require("text-transform.state")
local pickers = require("telescope.pickers")
local finders = require("telescope.finders")
local telescope_conf = require("telescope.config").values
local actions = require("telescope.actions")
local action_state = require("telescope.actions.state")
local dropdown = require("telescope.themes").get_dropdown({})
local Sorter = require("telescope.sorters").Sorter
local TextTransform = {}
local frequency_sorter = Sorter:new({
---@diagnostic disable-next-line: unused-local
scoring_function = function(self, prompt, line)
local generic_sorter = telescope_conf.generic_sorter()
generic_sorter:init()
local entry
for _, item in ipairs(common.items) do
if item.label == line then
entry = common.entry_maker(item)
break
end
end
D.log("frequency_sorter", "entry %s", vim.inspect(entry))
D.log("frequency_sorter", "prompt %s line %s", prompt, line)
-- Basic filtering based on prompt matching, non-matching items score below 0 to exclude them
local basic_score = (generic_sorter:scoring_function(prompt, line) or 1)
D.log("frequency_sorter", "%s basic_score: %s", entry.value, basic_score)
if basic_score < 0 then
return basic_score
end
-- D.log("frequency_sorter", "entry: %s", vim.inspect(entry))
-- D.log("frequency_sorter", "prompt: %s", prompt)
-- Calculate score based on frequency, higher frequency should have lower score
local freq_score = (entry.frequency or 1) * 10
D.log("frequency_sorter", "freq_score: %s", freq_score)
local final_score = 999999999 - freq_score + basic_score
D.log("frequency_sorter", "%s final_score: %s", line, final_score)
-- Combine scores, with frequency having the primary influence if present
return final_score
end,
})
local generic_sorter = telescope_conf.generic_sorter()
local sorter_map = {
frequency = frequency_sorter,
name = generic_sorter,
}
--- Pops up a telescope menu, containing the available case transformers.
--- When a transformer is selected, the cursor position/range/columns will be used to replace the
--- words around the cursor or inside the selection.
---
--- The cursor positions/ranges are saved before opening the menu and restored once a selection is
--- made.
function TextTransform.telescope_popup()
state.save_positions()
local filtered = {}
local config = _G.TextTransform.config
local sorter = sorter_map[config.sort_by] or generic_sorter
if config.sort_by == "frequency" then
common.load_frequency()
end
for _, item in ipairs(common.items) do
if config.replacers[item.value] and not config.replacers[item.value].enabled then
goto continue
end
table.insert(filtered, item)
::continue::
end
local picker = pickers.new(dropdown, {
prompt_title = "Change Case",
finder = finders.new_table({
results = common.items,
entry_maker = common.entry_maker,
}),
sorter = sorter,
attach_mappings = function(prompt_bufnr)
actions.select_default:replace(function()
local selection = action_state.get_selected_entry()
common.inc_frequency(selection.value)
actions.close(prompt_bufnr)
common.select(selection)
end)
return true
end,
})
vim.schedule(function()
picker:find()
end)
end
return TextTransform

View File

@@ -1,14 +1,13 @@
local D = require("text-transform.util.debug")
local state = require("text-transform.state")
local utils = require("text-transform.util")
local D = require("text-transform.utils.debug")
local S = require("text-transform.state")
local t = require("text-transform.transformers")
local TextTransform = {}
--- Finds the boundaries of the surrounding word around `start_col` within `line`.
--- @param line number
--- @param start_col number
--- @return number start_col, number end_col
---@param line number
---@param start_col number
---@return number start_col, number end_col
local function find_word_boundaries(line, start_col)
local line_text = vim.fn.getline(line)
-- dashes, underscores, and periods are considered part of a word
@@ -19,16 +18,28 @@ local function find_word_boundaries(line, start_col)
local word_end_col = vim.fn.match(line_text:sub(word_start_col), non_word_pat)
+ word_start_col
- 1
D.log("replacers", "Found word boundaries: %s", vim.inspect({ word_start_col, word_end_col }))
D.log("replacers", "Word text: %s", line_text:sub(word_start_col, word_end_col))
D.log("replacers", "Line text: %s", line_text)
D.log(
"find_word_boundaries",
"Found word boundaries: %s",
vim.inspect({ word_start_col, word_end_col })
)
D.log("find_word_boundaries", "Word text: %s", line_text:sub(word_start_col, word_end_col))
D.log("find_word_boundaries", "Line text: %s", line_text)
return word_start_col, word_end_col
end
--- Replace the range between the given positions with the given transform.
--- Acts on the lines between the given positions, replacing the text between the given columns.
---
---@param start_line number The starting line
---@param start_col number The starting column
---@param end_line number The ending line
---@param end_col number The ending column
---@param transform_name string The transformer name
function TextTransform.replace_range(start_line, start_col, end_line, end_col, transform_name)
D.log("replacers", "Replacing range with %s", transform_name)
D.log("replace_range", "Replacing range with %s", transform_name)
local transform = t["to_" .. transform_name]
local lines = vim.fn.getline(start_line, end_line) --- @type any
local lines = vim.fn.getline(start_line, end_line) ---@type any
local transformed = {}
if #lines == 1 then
local line = lines[1]
@@ -55,10 +66,10 @@ end
--- If `position` is provided, replace the word under the given position.
--- Otherwise, attempts to find the word under the cursor.
---
--- @param transform_name string The transformer name
--- @param position table|nil A table containing the position of the word to replace
---@param transform_name string The transformer name
---@param position table|nil A table containing the position of the word to replace
function TextTransform.replace_word(transform_name, position)
D.log("replacers", "Replacing word with %s", transform_name)
D.log("replace_word", "Replacing word with %s", transform_name)
local word, line, col, start_col, end_col
if not position then
word = vim.fn.expand("<cword>")
@@ -67,11 +78,11 @@ function TextTransform.replace_word(transform_name, position)
start_col, end_col = find_word_boundaries(line, col)
word = vim.fn.getline(line):sub(start_col, end_col)
end
D.log("replacers", "Found word %s", word)
D.log("replacers", "Using transformer %s", transform_name)
D.log("replace_word", "Found word %s", word)
D.log("replace_word", "Using transformer %s", transform_name)
local transformer = t["to_" .. transform_name]
local transformed = transformer(word)
D.log("replacers", "New value %s", transformed)
D.log("replace_word", "New value %s", transformed)
if not position then
vim.cmd("normal ciw" .. transformed)
else
@@ -81,9 +92,11 @@ end
--- Replaces each column in visual block mode selection with the given transform.
--- Assumes that the each selection is 1 character and operates on the whole word under each cursor.
---
---@param transform_name string The transformer name
function TextTransform.replace_columns(transform_name)
local selections = TextTransform.get_visual_selection_details()
D.log("replacers", "Replacing columns with %s", transform_name)
D.log("replace_columns", "Replacing columns with %s", transform_name)
for _, sel in ipairs(selections) do
TextTransform.replace_word(transform_name, { 0, sel.start_line, sel.start_col, 0 })
end
@@ -93,22 +106,24 @@ end
--- type based on the cursor positiono and visual selections, and passes information to relevant
--- range replacement functions.
---
--- @param transform_name string The transformer name
---@param transform_name string The transformer name
function TextTransform.replace_selection(transform_name)
D.log("replacers", "Replacing selection with %s", transform_name)
D.log("replace_selection", "Replacing selection with %s", transform_name)
-- determine if cursor is a 1-width column across multiple lines or a normal selection
-- local start_line, start_col, end_line, end_col = unpack(vim.fn.getpos("'<"))
local selections = TextTransform.get_visual_selection_details()
D.log("replacers", "Selections: %s", utils.dump(selections))
D.log("replace_selection", "Selections: %s", vim.inspect(selections))
local is_multiline = #selections > 1
local is_column = is_multiline and selections[1].start_col == selections[#selections].end_col
local is_single_cursor = not is_multiline
and not is_column
and selections
and selections[1]
and selections[1].start_col == selections[1].end_col
D.log(
"replacers",
"replace_selection",
"is_multiline: %s, is_column: %s, is_word: %s",
is_multiline,
is_column,
@@ -138,16 +153,32 @@ end
--- This allows to treat all ranges equally and allows to work on each selection without knowing
--- the full information around the selection logic.
function TextTransform.get_visual_selection_details()
if not S.state.positions.pos then
D.log("get_visual_selection_details", "No positions saved")
return {}
end
D.log(
"replacers",
"get_visual_selection_details",
"Getting visual selection details - mode: %s, is_visual: %s, is_block: %s",
state.positions.mode,
utils.is_visual_mode(),
utils.is_block_visual_mode()
S.state.mode,
S.is_visual_mode(),
S.is_block_visual_mode()
)
-- Get the start and end positions of the selection
local start_pos = S.state.positions.visual_start
local end_pos = S.state.positions.visual_end
local start_line, start_col = start_pos[2], start_pos[3]
local end_line, end_col = end_pos[2], end_pos[3]
-- Check if currently in visual mode; if not, return the cursor position
if not utils.is_visual_mode() and not utils.is_block_visual_mode() then
local pos = state.positions.pos
if
not S.is_visual_mode()
and not S.is_block_visual_mode()
and not S.has_range(start_pos, end_pos)
then
D.log("get_visual_selection_details", "Returning single cursor position: " .. vim.inspect(S))
local pos = S.state.positions.pos
return {
{
start_line = pos[2],
@@ -158,12 +189,6 @@ function TextTransform.get_visual_selection_details()
}
end
-- Get the start and end positions of the selection
local start_pos = state.positions.visual_start
local end_pos = state.positions.visual_end
local start_line, start_col = start_pos[2], start_pos[3]
local end_line, end_col = end_pos[2], end_pos[3]
-- Swap if selection is made upwards or backwards
if start_line > end_line or (start_line == end_line and start_col > end_col) then
start_line, end_line = end_line, start_line
@@ -171,7 +196,7 @@ function TextTransform.get_visual_selection_details()
end
-- If it's block visual mode, return table for each row
if utils.is_block_visual_mode() then
if S.is_block_visual_mode() or S.has_range(start_pos, end_pos) then
local block_selection = {}
for line = start_line, end_line do
if start_col == end_col then
@@ -185,9 +210,15 @@ function TextTransform.get_visual_selection_details()
end_col = start_col,
})
end
D.log(
"get_visual_selection_details",
"Returning block selection: %s",
vim.inspect(block_selection)
)
return block_selection
else
-- Normal visual mode, return single table entry
D.log("get_visual_selection_details", "Returning normal selection")
return {
{
start_line = start_line,

View File

@@ -1,59 +1,16 @@
local D = require("text-transform.util.debug")
local function ensure_config()
-- when the config is not set to the global object, we set it
if _G.TextTransform.config == nil then
_G.TextTransform.config = require("text-transform.config").options
end
end
local D = require("text-transform.utils.debug")
-- methods
local TextTransform = {
-- Boolean determining if the plugin is enabled or not.
enabled = false,
-- A table containing cursor position and visual selection details,
-- saved using `save_position()` and can be restored using `restore_positions()`
positions = nil,
-- The current state of the plugin
state = {
-- A table containing cursor position and visual selection details,
-- saved using `save_position()` and can be restored using `restore_positions()`
--@type {buf: number, mode: string, pos: table, visual_start: table, visual_end: table}
positions = {},
},
}
--- Toggle the plugin by calling the `enable`/`disable` methods respectively.
--- @private
function TextTransform.toggle()
if TextTransform.enabled then
return TextTransform.disable()
end
return TextTransform.enable()
end
--- Enables the plugin
--- @private
function TextTransform.enable()
ensure_config()
if TextTransform.enabled then
return TextTransform
end
TextTransform.enabled = true
return TextTransform
end
---Disables the plugin and reset the internal state.
---@private
function TextTransform.disable()
ensure_config()
if not TextTransform.enabled then
return TextTransform
end
-- reset the state
TextTransform.enabled = false
TextTransform.positions = nil
return TextTransform
end
local function get_mode_type(mode)
-- classify mode as either visual, line, block or normal
local mode_map = {
@@ -64,42 +21,67 @@ local function get_mode_type(mode)
return mode_map[mode] or "normal"
end
function TextTransform.has_range(visual_start, visual_end)
return visual_start and visual_end and visual_start[2] ~= visual_end[2]
end
local function capture_part(start_sel, end_sel, return_type)
local l, sel
if return_type == "start" then
l = math.min(start_sel[2], end_sel[2])
sel = start_sel
else
l = math.max(start_sel[2], end_sel[2])
sel = end_sel
end
return { sel[1], l, sel[3], sel[4] }
end
function TextTransform.is_block_visual_mode()
return TextTransform.state.positions.mode == "block"
-- return vim.fn.mode() == "V" or vim.fn.mode() == "\22"
end
function TextTransform.is_visual_mode()
return TextTransform.state.positions.mode == "visual"
-- return vim.fn.mode() == 'v'
end
--- Save the current cursor position, mode, and visual selection ranges
--- @private
function TextTransform.save_positions()
local buf = vim.api.nvim_get_current_buf()
local mode_info = vim.api.nvim_get_mode()
local mode = get_mode_type(mode_info.mode)
local pos = vim.fn.getcurpos()
-- leave mode
-- leave mode, required to get the positions - they only register on mode leave
-- in case of visual mode
local esc = vim.api.nvim_replace_termcodes("<esc>", true, false, true)
vim.api.nvim_feedkeys(esc, "x", true)
local visual_start = vim.fn.getpos("'<")
local visual_end = vim.fn.getpos("'>")
D.log("popup_menu", "Saved mode %s, cursor %s", mode, vim.inspect(pos))
D.log("save_positions", "Saved mode %s, cursor %s", mode, vim.inspect(pos))
if mode == "visual" or mode == "line" or mode == "block" then
if mode == "block" then -- for block visual mode
D.log("popup_menu", "Visual mode is block, %s", vim.inspect({ visual_start, visual_end }))
if TextTransform.has_range(visual_start, visual_end) then -- for ranges
D.log(
"save_positions",
"Visual range, mode is %s, %s",
mode,
vim.inspect({ visual_start, visual_end })
)
-- Adjust the positions to correctly capture the entire block
visual_start = {
visual_start[1],
math.min(visual_start[2], visual_end[2]),
visual_start[3],
visual_start[4],
}
visual_end =
{ visual_end[1], math.max(visual_start[2], visual_end[2]), visual_end[3], visual_end[4] }
visual_start = capture_part(visual_start, visual_end, "start")
visual_end = capture_part(visual_start, visual_end, "end")
end
D.log(
"popup_menu",
"state",
"Saved visual mode %s, cursor %s",
mode,
vim.inspect({ visual_start, visual_end })
)
end
local state = {
local positions = {
buf = buf,
mode = mode,
pos = pos,
@@ -107,32 +89,38 @@ function TextTransform.save_positions()
visual_end = visual_end,
}
D.log("popup_menu", "State: %s", vim.inspect(state))
TextTransform.positions = state
return state
D.log("save_positions", "State: %s", vim.inspect(positions))
TextTransform.state.positions = positions
return positions
end
--- Restore the cursor position, mode, and visual selection ranges saved using `save_position()`,
--- or a given modified state, if passed as the first argument
function TextTransform.restore_positions(state)
state = state or TextTransform.positions
vim.api.nvim_set_current_buf(state.buf)
vim.fn.setpos(".", state.pos)
D.log("popup_menu", "Restored mode %s, cursor %s", state.mode, vim.inspect(state.pos))
function TextTransform.restore_positions(positions)
positions = positions or TextTransform.state.positions
vim.api.nvim_set_current_buf(positions.buf)
vim.fn.setpos(".", positions.pos)
D.log(
"restore_positions",
"Restored mode %s, cursor %s",
positions.mode,
vim.inspect(positions.pos)
)
-- Attempt to restore visual mode accurately
if
(state.mode == "visual" or state.mode == "block")
and state.visual_start
and state.visual_end
(positions.mode == "visual" or positions.mode == "block")
and positions.visual_start
and positions.visual_end
then
vim.fn.setpos("'<", state.visual_start)
vim.fn.setpos("'>", state.visual_end)
vim.fn.setpos("'<", positions.visual_start)
vim.fn.setpos("'>", positions.visual_end)
local command = "normal! gv"
vim.cmd(command)
D.log("popup_menu", [[Restored visual mode %s using "%s"]], state.mode, command)
D.log("restore_positions", [[Restored visual mode %s using "%s"]], positions.mode, command)
end
TextTransform.positions = nil
TextTransform.state.positions = {}
end
return TextTransform

View File

@@ -1,153 +0,0 @@
local D = require("text-transform.util.debug")
local state = require("text-transform.state")
local replacers = require("text-transform.replacers")
local pickers = require("telescope.pickers")
local finders = require("telescope.finders")
local telescope_conf = require("telescope.config").values
local actions = require("telescope.actions")
local action_state = require("telescope.actions.state")
local dropdown = require("telescope.themes").get_dropdown({})
local Sorter = require("telescope.sorters").Sorter
local generic_sorter = telescope_conf.generic_sorter()
local TextTransform = {}
local items = {
{ label = "camelCase", value = "camel_case" },
{ label = "snake_case", value = "snake_case" },
{ label = "PascalCase", value = "pascal_case" },
{ label = "kebab-case", value = "kebab_case" },
{ label = "dot.case", value = "dot_case" },
{ label = "Title Case", value = "title_case" },
{ label = "CONST_CASE", value = "const_case" },
}
local default_frequency = {
camel_case = 1,
snake_case = 1,
pascal_case = 1,
kebab_case = 1,
dot_case = 1,
title_case = 1,
const_case = 1,
}
local frequency_file = vim.fn.stdpath("config") .. "/text-transform-frequency.json"
local frequency
local function load_frequency()
if frequency then
return frequency
end
if vim.fn.filereadable(frequency_file) == 0 then
frequency = default_frequency
vim.fn.writefile({ vim.fn.json_encode(frequency) }, frequency_file)
else
frequency = vim.fn.json_decode(vim.fn.readfile(frequency_file))
end
D.log("telescope", "frequency loaded: %s", vim.inspect(frequency))
return frequency
end
local function inc_frequency(name)
frequency[name] = (frequency[name] or 0) + 1
D.log("telescope", "new frequency: %s %d", name, frequency[name])
vim.fn.writefile({ vim.fn.json_encode(frequency) }, frequency_file)
end
local function entry_maker(entry)
return {
value = entry.value,
display = entry.label,
ordinal = entry.label,
frequency = frequency[entry.value] or 1,
}
end
local frequency_sorter = Sorter:new({
---@diagnostic disable-next-line: unused-local
scoring_function = function(self, prompt, line)
local entry
for _, item in ipairs(items) do
if item.label == line then
entry = entry_maker(item)
break
end
end
-- Basic filtering based on prompt matching, non-matching items score below 0 to exclude them
local basic_score = generic_sorter:score(prompt, entry) or 0
-- D.log("telescope", "basic_score: %s", basic_score)
if basic_score < 0 then
return basic_score
end
-- D.log("telescope", "entry: %s", vim.inspect(entry))
-- D.log("telescope", "prompt: %s", prompt)
-- Calculate score based on frequency, higher frequency should have lower score
local freq_score = (entry.frequency or 1) -- Multiply by -1 because we want higher frequency to have lower score
-- D.log("telescope", "freq_score: %s", freq_score)
D.log("telescope", "%s final_score: %s", line, 99999999 - freq_score * 100 + basic_score)
-- Combine scores, with frequency having the primary influence if present
return 99999999 - freq_score * 100 + basic_score -- Division to ensure frequency has a higher weight
end,
})
---@diagnostic disable-next-line: unused-local
-- for _i, k in pairs(default_ordered_keys) do
-- local v = map[k]
-- vim.cmd("amenu TransformsWord." .. k .. " :lua TextTransform.replace_word('" .. v .. "')<CR>")
-- vim.cmd(
-- "amenu TransformsSelection." .. k .. " :lua TextTransform.replace_columns('" .. v .. "')<CR>"
-- )
-- end
--- Pops up a telescope menu, containing the available case transformers.
--- When a transformer is selected, the cursor position/range/columns will be used to replace the
--- words around the cursor or inside the selection.
---
--- The cursor positions/ranges are saved before opening the menu and restored once a selection is
--- made.
function TextTransform.popup()
state.save_positions()
load_frequency()
local filtered = {}
print(vim.inspect(_G.TextTransform.config))
local config = _G.TextTransform.config
for _, item in ipairs(items) do
if not config.replacers[item.value] or not config.replacers[item.value].enabled then
goto continue
end
table.insert(filtered, item)
::continue::
end
local picker = pickers.new(dropdown, {
prompt_title = "Change Case",
finder = finders.new_table({
results = items,
entry_maker = entry_maker,
}),
sorter = frequency_sorter,
attach_mappings = function(prompt_bufnr)
actions.select_default:replace(function()
local selection = action_state.get_selected_entry()
inc_frequency(selection.value)
actions.close(prompt_bufnr)
vim.schedule(function()
replacers.replace_selection(selection.value)
state.restore_positions()
end)
end)
return true
end,
})
vim.schedule(function()
picker:find()
end)
end
return TextTransform

View File

@@ -1,13 +1,13 @@
local D = require("text-transform.util.debug")
local utils = require("text-transform.util")
local D = require("text-transform.utils.debug")
-- local utils = require("text-transform.utils")
local TextTransform = {}
TextTransform.WORD_BOUNDRY = "[%_%-%s%.]"
--- Splits a string into words.
--- @param string string
--- @return table
---@param string string
---@return table
function TextTransform.to_words(string)
local words = {}
local word = ""
@@ -47,12 +47,12 @@ function TextTransform.to_words(string)
-- Append current character to the current word
word = word .. char
end
D.log("transformers", "i %d char %s word %s words %s", i, char, word, utils.dump(words))
-- D.log("to_words", "i %d char %s word %s words %s", i, char, word, utils.dump(words))
end
if word ~= "" then
table.insert(words, word:lower())
end
D.log("transformers", "words %s", vim.inspect(words))
D.log("to_words", "words %s", vim.inspect(words))
return words
end
@@ -60,10 +60,10 @@ end
--- The callback is called with the word, the index, and the table of words.
--- The separator is added between each word.
---
--- @param words string|table string or table of strings
--- @param with_word_cb function (word: string, index: number, words: table) -> string
--- @param separator string|nil (optional)
--- @return string
---@param words string|table string or table of strings
---@param with_word_cb function (word: string, index: number, words: table) -> string
---@param separator string|nil (optional)
---@return string
function TextTransform.transform_words(words, with_word_cb, separator)
if type(words) ~= "table" then
words = TextTransform.to_words(words)
@@ -75,14 +75,14 @@ function TextTransform.transform_words(words, with_word_cb, separator)
new_word = separator .. new_word
end
out = out .. new_word
D.log("transformers", "word %s (%d) new_word %s out %s", word, i, new_word, out)
D.log("transform_words", "word %s (%d) new_word %s out %s", word, i, new_word, out)
end
return out
end
--- Transforms a string into camelCase.
--- @param string string
--- @return string
---@param string string
---@return string
function TextTransform.to_camel_case(string)
return TextTransform.transform_words(string, function(word, i)
if i == 1 then
@@ -93,8 +93,8 @@ function TextTransform.to_camel_case(string)
end
--- Transfroms a string into snake_case.
--- @param string any
--- @return string
---@param string any
---@return string
function TextTransform.to_snake_case(string)
return TextTransform.transform_words(string, function(word, i)
if i == 1 then
@@ -105,16 +105,16 @@ function TextTransform.to_snake_case(string)
end
--- Transforms a string into PascalCase.
--- @param string string
--- @return string
---@param string string
---@return string
function TextTransform.to_pascal_case(string)
local cc = TextTransform.to_camel_case(string)
return cc:sub(1, 1):upper() .. cc:sub(2)
end
--- Transforms a string into Title Case.
--- @param string string
--- @return string
---@param string string
---@return string
function TextTransform.to_title_case(string)
return TextTransform.transform_words(string, function(word)
return word:sub(1, 1):upper() .. word:sub(2):lower()
@@ -122,8 +122,8 @@ function TextTransform.to_title_case(string)
end
--- Transforms a string into kebab-case.
--- @param string string
--- @return string
---@param string string
---@return string
function TextTransform.to_kebab_case(string)
return TextTransform.transform_words(string, function(word)
return word:lower()
@@ -131,8 +131,8 @@ function TextTransform.to_kebab_case(string)
end
--- Transforms a string into dot.case.
--- @param string string
--- @return string
---@param string string
---@return string
function TextTransform.to_dot_case(string)
return TextTransform.transform_words(string, function(word)
return word:lower()
@@ -140,8 +140,8 @@ function TextTransform.to_dot_case(string)
end
--- Transforms a string into CONSTANT_CASE.
--- @param string string
--- @return string
---@param string string
---@return string
function TextTransform.to_const_case(string)
return TextTransform.transform_words(string, function(word)
return word:upper()

View File

@@ -1,33 +0,0 @@
local state = require("text-transform.state")
local utils = {}
--- Merges two tables into one. Same as `vim.tbl_extend("keep", t1, t2)`.
--- Mutates the first table.
---
--- TODO accept multiple tables to merge
---
--- @param t1 table
--- @param t2 table
--- @return table
function utils.merge(t1, t2)
return vim.tbl_extend("force", t1, t2)
end
--- Dumps the object into a string.
--- @param obj any
--- @return string
function utils.dump(obj)
return vim.inspect(obj)
end
function utils.is_block_visual_mode()
return state.positions.mode == "block"
-- return vim.fn.mode() == "V" or vim.fn.mode() == "\22"
end
function utils.is_visual_mode()
return state.positions.mode == "visual"
-- return vim.fn.mode() == 'v'
end
return utils

View File

@@ -6,12 +6,6 @@ local function is_debug()
and _G.TextTransform.config.debug
end
---prints only if debug is true.
---
---@param scope string: the scope from where this function is called.
---@param str string: the formatted string.
---@param ... any: the arguments of the formatted string.
---@private
function D.log(scope, str, ...)
if not is_debug() then
return
@@ -26,20 +20,15 @@ function D.log(scope, str, ...)
print(
string.format(
"[text-transform:%s %s in %s] > %s",
"%s [text-transform:%s in %s] > %s",
os.date("%H:%M:%S"),
line,
scope,
line,
string.format(str, ...)
)
)
end
---prints the table if debug is true.
---
---@param table table: the table to print.
---@param indent number?: the default indent value, starts at 0.
---@private
function D.tprint(table, indent)
if not is_debug() then
return

View File

@@ -0,0 +1,19 @@
local TextTransform = {}
--- Merges two tables into one. Same as `vim.tbl_extend("keep", t1, t2)`.
--- Mutates the first table.
---
--- TODO accept multiple tables to merge
---
---@param t1 table
---@param t2 table
---@return table
function TextTransform.merge(t1, t2)
return vim.tbl_extend("force", t1, t2)
end
function TextTransform.has_range(visual_start, visual_end)
return visual_start and visual_end and visual_start[2] ~= visual_end[2]
end
return TextTransform

View File

@@ -1,3 +1,4 @@
local MiniTest = require("mini.test")
-- partially imported from https://github.com/echasnovski/mini.nvim
local Helpers = {}
@@ -162,4 +163,9 @@ Helpers.new_child_neovim = function()
return child
end
function Helpers.init_plugin(child, config)
config = config or ""
child.lua([[require('text-transform').setup(]] .. config .. [[)]])
end
return Helpers

View File

@@ -32,11 +32,6 @@ T["setup()"]["sets exposed methods and default options value"] = function()
-- global object that holds your plugin information
eq_type_global(child, "_G.TextTransform", "table")
-- public methods
eq_type_global(child, "_G.TextTransform.toggle", "function")
eq_type_global(child, "_G.TextTransform.disable", "function")
eq_type_global(child, "_G.TextTransform.enable", "function")
-- config
eq_type_global(child, "_G.TextTransform.config", "table")
@@ -55,14 +50,17 @@ T["setup()"]["sets exposed methods and default options value"] = function()
end
T["setup()"]["overrides default values"] = function()
child.lua([[require('text-transform').setup({
helpers.init_plugin(
child,
[[{
-- write all the options with a value different than the default ones
debug = true,
keymap = {
["v"] = "<leader>c",
["n"] = "<leader>c",
},
})]])
}]]
)
-- assert the value, and the type
eq_type_config(child, "debug", "boolean")

63
tests/test_popups.lua Normal file
View File

@@ -0,0 +1,63 @@
local helpers = dofile("tests/helpers.lua")
local MiniTest = require("mini.test")
-- See https://github.com/echasnovski/mini.nvim/blob/main/lua/mini/test.lua for more documentation
local child = helpers.new_child_neovim()
local eq_global, eq_config, eq_state =
helpers.expect.global_equality, helpers.expect.config_equality, helpers.expect.state_equality
local eq_type_global, eq_type_config, eq_type_state =
helpers.expect.global_type_equality,
helpers.expect.config_type_equality,
helpers.expect.state_type_equality
local T = MiniTest.new_set({
hooks = {
-- This will be executed before every (even nested) case
pre_case = function()
-- Restart child process with custom 'init.lua' script
child.restart({ "-u", "scripts/minimal_init.lua" })
end,
-- This will be executed one after all tests from this set are finished
post_once = child.stop,
},
})
-- Tests related to the `setup` method.
T["popups"] = MiniTest.new_set()
T["popups"]["exposes show_popup"] = function()
helpers.init_plugin(child)
eq_type_global(child, "_G.TextTransform", "table")
eq_type_global(child, "_G.TextTransform.show_popup", "function")
eq_type_global(child, "_G.TextTransform.telescope_popup", "nil")
end
T["popups"]["telescope exposes telescope_popup"] = function()
helpers.init_plugin(child)
eq_type_global(child, "_G.TextTransform", "table")
eq_type_global(child, "_G.TextTransform.telescope_popup", "nil")
child.lua([[Telescope = require('text-transform.popup.telescope')]])
eq_type_global(child, "Telescope.telescope_popup", "function")
end
T["popups"]["select exposes select_popup"] = function()
helpers.init_plugin(child)
eq_type_global(child, "_G.TextTransform", "table")
eq_type_global(child, "_G.TextTransform.select_popup", "nil")
child.lua([[Select = require('text-transform.popup.select')]])
eq_type_global(child, "Select.select_popup", "function")
end
return T

View File

@@ -24,7 +24,7 @@ local T = MiniTest.new_set({
})
local function test_string(child, str)
child.lua([[require('text-transform').setup()]])
helpers.init_plugin(child)
child.lua([[result = require('text-transform').to_words("]] .. str .. [[")]])
end