69 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
github-actions[bot]
52f26035d2 chore(master): release 0.7.0 2024-05-04 02:29:22 +03:00
f83f2afafd feat: sort telescope by usage frequency 2024-05-04 02:27:10 +03:00
a775191122 docs: update readme 2024-05-04 02:27:10 +03:00
3f684bbdc0 ci: update workflows 2024-05-04 02:27:10 +03:00
646dde56f2 ci: update workflow version 2024-05-04 02:27:10 +03:00
ba385b846b ci: disable workflow concurrency 2024-05-04 02:27:10 +03:00
9393d81815 docs: update readme 2024-05-04 02:27:10 +03:00
github-actions[bot]
e376ac78fc chore(master): release 0.6.0 2024-05-02 03:25:03 +03:00
2fa82f80d6 docs: update readme 2024-05-02 03:19:04 +03:00
ad84766691 chore: cleanup 2024-05-02 03:19:04 +03:00
fbb89b1b44 feat: enable/disable specific replacers 2024-05-02 03:19:04 +03:00
github-actions[bot]
5c0132fe95 chore(master): release 0.5.1 2024-05-01 01:54:53 +03:00
657bda1478 fix: exports 2024-05-01 01:33:05 +03:00
github-actions[bot]
b500db2686 chore(master): release 0.5.0 2024-04-30 15:51:37 +03:00
1fd7e334bc docs: update readme 2024-04-30 15:34:47 +03:00
7ef73f26d1 fix: telescope 2024-04-30 15:29:07 +03:00
Chen Asraf
1f3fc3df09 docs: update demo gif 2024-04-30 15:23:13 +03:00
b31967dd32 docs: update docs
chore: comment out unused code
2024-04-30 15:10:24 +03:00
6f1c92872e test: fix tests 2024-04-30 15:00:54 +03:00
69542d7e1c fix: telescope command 2024-04-30 15:00:54 +03:00
6e409f0a44 feat: add telescope command 2024-04-30 15:00:54 +03:00
dc3d4e6379 docs: update readme 2024-04-30 15:00:54 +03:00
4628b50340 refactor: telescope exports 2024-04-30 15:00:54 +03:00
500edc66e6 feat: add commands
docs: update
2024-04-30 15:00:54 +03:00
758d90b88f docs: add doc strings 2024-04-30 15:00:54 +03:00
a669a998f4 fix: uppercase logic
fix: precommit
2024-04-30 15:00:54 +03:00
c0a354ac79 build: move precommit script to scripts/ dir 2024-04-30 15:00:54 +03:00
b536b832d9 test: fix most tests 2024-04-30 15:00:54 +03:00
fc2de91ac4 ci: add status before fail 2024-04-30 15:00:50 +03:00
a53e0d6d64 fix: word break/transform 2024-04-30 15:00:50 +03:00
bbe802e0b2 build: add precommit 2024-04-30 15:00:50 +03:00
e6735cceeb chore: formatting 2024-04-30 15:00:50 +03:00
b4257a50fe refactor: new logic, file splitting
BREAKING CHANGES: functions, configs have been renamed
2024-04-30 15:00:03 +03:00
Chen Asraf
ded86d9b0f docs: update README.md 2023-05-21 00:24:20 +03:00
Chen Asraf
7518df3364 chore(master): release 0.4.0
chore(master): release 0.4.0
2023-05-18 09:40:11 +03:00
github-actions[bot]
8f980db340 chore(master): release 0.4.0 2023-05-18 06:39:02 +00:00
Chen Asraf
7edc585d15 Merge pull request #14 from chenasraf/develop
Prepare release: v0.3.2
2023-05-18 09:38:08 +03:00
Chen Asraf
9cd27aba2b fix: word edge detection 2023-05-18 09:26:41 +03:00
Chen Asraf
43cc07cde4 fix: words split 2023-05-18 02:41:50 +03:00
Chen Asraf
eb3a7ae843 Merge branch 'master' into develop 2023-05-18 02:23:50 +03:00
Chen Asraf
5765e3d721 feat: visual block mode support 2023-05-18 02:22:11 +03:00
Chen Asraf
bcb5ee3704 test: split into separate files 2023-05-18 01:16:24 +03:00
Chen Asraf
d4ef5c0e8c Merge branch 'master' into develop 2023-05-18 01:06:22 +03:00
28 changed files with 1676 additions and 625 deletions

View File

@@ -4,20 +4,21 @@ on:
push:
branches: [develop]
pull_request:
branches: [develop]
types: [opened, synchronize]
concurrency:
group: github.head_ref
cancel-in-progress: true
# concurrency:
# group: github.head_ref
# cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
name: lint
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: JohnnyMorganz/stylua-action@v2
- uses: JohnnyMorganz/stylua-action@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: latest
@@ -27,7 +28,7 @@ jobs:
runs-on: ubuntu-latest
name: documentation
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 2
@@ -41,7 +42,12 @@ jobs:
run: make documentation-ci
- name: check docs diff
run: exit $(git status --porcelain doc | wc -l | tr -d " ")
run: |
git status doc
ex=$(git status --porcelain doc | wc -l | tr -d " ")
if [[ $ex -ne 0 ]]; then git diff doc; fi
exit $ex
tests:
needs:
- lint
@@ -53,12 +59,12 @@ jobs:
neovim_version: ['v0.7.2', 'v0.8.3', 'v0.9.0', 'nightly']
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- run: date +%F > todays-date
- name: restore cache for today's nightly.
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: _neovim
key: ${{ runner.os }}-x64-${{ hashFiles('todays-date') }}
@@ -80,9 +86,9 @@ jobs:
- tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: google-github-actions/release-please-action@v3
- uses: google-github-actions/release-please-action@v4
id: release
with:
release-type: simple

View File

@@ -4,20 +4,21 @@ on:
push:
branches: [master]
pull_request:
branches: [master]
types: [opened, synchronize]
concurrency:
group: github.head_ref
cancel-in-progress: true
# concurrency:
# group: github.head_ref
# cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
name: lint
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: JohnnyMorganz/stylua-action@v2
- uses: JohnnyMorganz/stylua-action@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: latest
@@ -27,7 +28,7 @@ jobs:
runs-on: ubuntu-latest
name: documentation
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 2
@@ -41,7 +42,11 @@ jobs:
run: make documentation-ci
- name: check docs diff
run: exit $(git status --porcelain doc | wc -l | tr -d " ")
run: |
git status doc
ex=$(git status --porcelain doc | wc -l | tr -d " ")
if [[ $ex -ne 0 ]]; then git diff doc; fi
exit $ex
tests:
needs:
@@ -54,12 +59,12 @@ jobs:
neovim_version: ['v0.7.2', 'v0.8.3', 'v0.9.0', 'nightly']
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- run: date +%F > todays-date
- name: restore cache for today's nightly.
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: _neovim
key: ${{ runner.os }}-x64-${{ hashFiles('todays-date') }}
@@ -81,9 +86,9 @@ jobs:
- tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: google-github-actions/release-please-action@v3
- uses: google-github-actions/release-please-action@v4
id: release
with:
release-type: simple

View File

@@ -1,5 +1,91 @@
# 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)
### Features
* sort telescope by usage frequency ([f83f2af](https://github.com/chenasraf/text-transform.nvim/commit/f83f2afafdfbbcb986ca1c6d3db062cd3b964c62))
## [0.6.0](https://github.com/chenasraf/text-transform.nvim/compare/v0.5.1...v0.6.0) (2024-05-02)
### Features
* enable/disable specific replacers ([fbb89b1](https://github.com/chenasraf/text-transform.nvim/commit/fbb89b1b44dfb9b57cd241b7c574665cce7dab10))
## [0.5.1](https://github.com/chenasraf/text-transform.nvim/compare/v0.5.0...v0.5.1) (2024-04-30)
### Bug Fixes
* exports ([657bda1](https://github.com/chenasraf/text-transform.nvim/commit/657bda14789b907911157c4f34e8a088c1e00d15))
## [0.5.0](https://github.com/chenasraf/text-transform.nvim/compare/v0.4.0...v0.5.0) (2024-04-30)
### Features
* add commands ([500edc6](https://github.com/chenasraf/text-transform.nvim/commit/500edc66e6ac0a649108749ad290f2a370cf4219))
* add telescope command ([6e409f0](https://github.com/chenasraf/text-transform.nvim/commit/6e409f0a44e50b3ef65f426e36b7824ee2d95d7a))
### Bug Fixes
* precommit ([a669a99](https://github.com/chenasraf/text-transform.nvim/commit/a669a998f47de6c33e9667d40ce3a219d60af69b))
* telescope ([7ef73f2](https://github.com/chenasraf/text-transform.nvim/commit/7ef73f26d13f72454d0388bdca4541f7f4aa78e2))
* telescope command ([69542d7](https://github.com/chenasraf/text-transform.nvim/commit/69542d7e1c6c88832611fb3c0f1d46102c133bd1))
* uppercase logic ([a669a99](https://github.com/chenasraf/text-transform.nvim/commit/a669a998f47de6c33e9667d40ce3a219d60af69b))
* word break/transform ([a53e0d6](https://github.com/chenasraf/text-transform.nvim/commit/a53e0d6d64051d5fea6d102529c403e4cbd0cce2))
## [0.4.0](https://github.com/chenasraf/text-transform.nvim/compare/v0.3.1...v0.4.0) (2023-05-18)
### Features
* visual block mode support ([5765e3d](https://github.com/chenasraf/text-transform.nvim/commit/5765e3d72109c473b4f9ab8839d01f8c983d0dc2))
### Bug Fixes
* word edge detection ([9cd27ab](https://github.com/chenasraf/text-transform.nvim/commit/9cd27aba2b74b072d57edadecdd63646d84a82cf))
* words split ([43cc07c](https://github.com/chenasraf/text-transform.nvim/commit/43cc07cde43e8a4caf6a9c7471f6e3e69e67c242))
## [0.3.1](https://github.com/chenasraf/text-transform.nvim/compare/v0.3.0...v0.3.1) (2023-05-17)

View File

@@ -9,4 +9,7 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ["https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=TSH3C3ABGQM22&currency_code=ILS&source=url"]
custom:
[
'https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=TSH3C3ABGQM22&currency_code=ILS&source=url',
]

View File

@@ -14,6 +14,8 @@ test:
deps:
@mkdir -p deps
git clone --depth 1 https://github.com/echasnovski/mini.nvim deps/mini.nvim
git clone --depth 1 https://github.com/nvim-lua/plenary.nvim deps/plenary.nvim
git clone --depth 1 https://github.com/nvim-telescope/telescope.nvim deps/telescope.nvim
echo "#!/usr/bin/env bash\n\nmake precommit" > .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
@@ -33,12 +35,7 @@ lint:
# precommit
precommit:
stylua .
# git add .
make lint
# make test
make documentation
git add doc
./scripts/precommit.sh
clean:
rm -rf deps

217
README.md
View File

@@ -8,17 +8,23 @@
<div align="center">
![Demonstration](https://github.com/chenasraf/text-transform.nvim/assets/167217/e73f0e27-d72d-4aa6-bfa7-6f691aba9713)
![Demo](https://github.com/chenasraf/text-transform.nvim/assets/167217/20c0106e-2c3b-4dd5-894e-23b313d8bc6b)
</div>
## ⚡️ Features
Transform the current word or selection between multiple case types. Need to easily replace myVar
with my_var or vice versa? This plugin is for you!
Transform the current word or selection between multiple case types. Need to easily replace `myVar`
with `my_var` or vice versa? This plugin is for you!
- Works on current word in **Normal Mode**
- Will replace the current word selectable by <kbd>ciw</kbd>
- Works on selection in **Visual Mode**
- Will replace only inside the selection
- Works on column selections in **Visual Block Mode**
- Will detect if the block is a single column or multiple columns
- If it's a single column, will replace the word under each cursor
- If it's a selection with length, will replace only inside the selection ranges
| Transformation | Example Inputs | Output |
| -------------- | --------------------------- | -------- |
@@ -30,73 +36,55 @@ with my_var or vice versa? This plugin is for you!
| `Title Case` | `my_var`, `my-var`, `MyVar` | `My Var` |
| `CONST_CASE` | `my_var`, `my-var`, `MyVar` | `MY_VAR` |
## 📋 Installation
## 🔽 Installation
<div align="center">
<table>
<thead>
<tr>
<th>Package manager</th>
<th>Snippet</th>
</tr>
</thead>
<tbody>
<tr>
<td>
[wbthomason/packer.nvim](https://github.com/wbthomason/packer.nvim)
</td>
<td>
### [Lazy](https://github.com/folke/lazy.nvim)
```lua
-- stable version
use { "chenasraf/text-transform.nvim", tag = "*" }
-- dev version
use { "chenasraf/text-transform.nvim" }
require("lazy").setup({
"chenasraf/text-transform.nvim",
-- stable version
version = "*", -- or: tag = "stable"
-- dev version
-- branch = "develop",
-- Optional - for Telescope popup
dependencies = { 'nvim-telescope/telescope.nvim', 'nvim-lua/plenary.nvim' }
})
```
</td>
</tr>
<tr>
<td>
[junegunn/vim-plug](https://github.com/junegunn/vim-plug)
</td>
<td>
### [Packer](https://github.com/wbthomason/packer.nvim)
```lua
-- stable version
Plug "chenasraf/text-transform.nvim", { "tag": "*" }
-- dev version
Plug "chenasraf/text-transform.nvim"
use { "chenasraf/text-transform.nvim",
-- stable version
tag = "stable",
-- dev version
-- branch = "develop",
-- Optional - for Telescope popup
requires = { 'nvim-telescope/telescope.nvim', 'nvim-lua/plenary.nvim' }
}
```
</td>
</tr>
<tr>
<td>
### [Plug](https://github.com/junegunn/vim-plug)
[folke/lazy.nvim](https://github.com/folke/lazy.nvim)
```vim
" Dependencies - optional for Telescope popup
Plug 'nvim-telescope/telescope.nvim'
Plug 'nvim-lua/plenary.nvim'
</td>
<td>
```lua
-- stable version
require("lazy").setup({{ "chenasraf/text-transform.nvim", version = "*" }})
-- dev version
require("lazy").setup({ "chenasraf/text-transform.nvim" })
" stable version
Plug 'chenasraf/text-transform.nvim', { 'tag': 'stable' }
" dev version
Plug 'chenasraf/text-transform.nvim', { 'branch': 'develop' }
```
</td>
</tr>
</tbody>
</table>
</div>
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.
## ☄ Getting started
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
To get started, [install](#-installation) the plugin via your favorite package manager.
@@ -115,42 +103,113 @@ To get started, [install](#-installation) the plugin via your favorite package m
1. Select the desired transform and you're done!
## ⚙ Configuration
<details>
<summary>Click to unfold the full list of options with their default values</summary>
## ⚙ Configuration
> **Note**: The options are also available in Neovim by calling `:h TextTransform.options`
The following are the default options when none are configured by the user.
To merge any new config into the default, you can override only the keys you need, and leave the
rest to use the defaults.
```lua
require("text-transform").setup({
-- Prints useful logs about what event are triggered, and reasons actions are executed.
--- Prints information about internals of the plugin. Very verbose, only useful for debugging.
debug = false,
-- Keymap to trigger the transform.
--- Keymap configurations
keymap = {
-- Normal mode keymap.
["n"] = "<Leader>~",
-- Visual mode keymap.
["v"] = "<Leader>~",
--- 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
["n"] = "<Leader>~",
--- Opens the popup in visual/visual block modes
["v"] = "<Leader>~",
},
},
---
--- Configurations for the text-transform replacers
--- Keys indicate the replacer name, and the value is a table with the following options:
---
--- - `enabled` (boolean): Enable or disable the replacer - disabled replacers do not show up in the popup.
replacers = {
camel_case = { enabled = true },
const_case = { enabled = true },
dot_case = { enabled = true },
kebab_case = { enabled = true },
pascal_case = { enabled = true },
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'
})
```
## 📝 Commands
The following commands are available for your use in your own mappings or for reference.
| 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
You can use the setup options to customize the default keymaps used to trigger the Telescope Popup.
To disable these automated mappings, pass `nil` or `false` to the containing table (e.g.
`telescope_popup`) or to the keys themselves.
```lua
-- Disable entirely
require("text-transform").setup({
keymap = {
telescope_popup = nil,
},
})
-- Disable just one keymap
require("text-transform").setup({
keymap = {
telescope_popup = {
["v"] = nil,
},
},
})
```
</details>
You can also create custom mappings to specific case conversions or to the Telescope popup yourself.
## 🧰 Commands
```lua
-- Trigger telescope popup
vim.keymap.set("n", "<leader>~~", ":TtTelescope", { silent = true, desc = "Transform Text" })
Use the following as example, you can mix &amp; match the different replacement functions with the
desired transform function.
-- Trigger case converters directly
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" })
```
Normally you wouldn't need to call this, as you would just use the keymap you used in `setup()`.
| Command | Description |
| ---------------------------------------------------- | -------------------------------------------------- |
| `:lua TextTransform.replace_word("camel_case")` | Replaces selected word with camelCase version. |
| `:lua TextTransform.replace_selection("snake_case")` | Replaces visual selection with snake_case version. |
## ⌨ Contributing
## 💁🏻 Contributing
I am developing this package on my free time, so any support, whether code, issues, or just stars is
very helpful to sustaining its life. If you are feeling incredibly generous and would like to donate

View File

@@ -1,14 +1,23 @@
TextTransform.camel_case() text-transform.txt /*TextTransform.camel_case()*
TextTransform.const_case() text-transform.txt /*TextTransform.const_case()*
TextTransform.dot_case() text-transform.txt /*TextTransform.dot_case()*
TextTransform.into_words() text-transform.txt /*TextTransform.into_words()*
TextTransform.kebab_case() text-transform.txt /*TextTransform.kebab_case()*
TextTransform.options text-transform.txt /*TextTransform.options*
TextTransform.pascal_case() text-transform.txt /*TextTransform.pascal_case()*
TextTransform.replace_at() text-transform.txt /*TextTransform.replace_at()*
TextTransform.config text-transform.txt /*TextTransform.config*
TextTransform.get_visual_selection_details() text-transform.txt /*TextTransform.get_visual_selection_details()*
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.snake_case() text-transform.txt /*TextTransform.snake_case()*
TextTransform.title_case() text-transform.txt /*TextTransform.title_case()*
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()*
TextTransform.to_kebab_case() text-transform.txt /*TextTransform.to_kebab_case()*
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.transform_words() text-transform.txt /*TextTransform.transform_words()*
find_word_boundaries() text-transform.txt /*find_word_boundaries()*

View File

@@ -1,97 +1,277 @@
==============================================================================
------------------------------------------------------------------------------
*TextTransform.options*
`TextTransform.options`
*TextTransform.config*
`TextTransform.config`
Your plugin configuration with its default values.
Default values:
>
TextTransform.options = {
-- Prints useful logs about what event are triggered, and reasons actions are executed.
>lua
TextTransform.config = {
--- Prints information about internals of the plugin. Very verbose, only useful for debugging.
debug = false,
-- Keymap to trigger the transform.
--- Keymap configurations
keymap = {
-- Normal mode keymap.
["n"] = "<Leader>~",
-- Visual mode keymap.
["v"] = "<Leader>~",
--- 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
["n"] = "<Leader>~",
--- Opens the popup in visual/visual block modes
["v"] = "<Leader>~",
},
},
---
--- Configurations for the text-transform replacers
--- Keys indicate the replacer name, and the value is a table with the following options:
---
--- - `enabled` (boolean): Enable or disable the replacer - disabled replacers do not show up in the popup.
replacers = {
camel_case = { enabled = true },
const_case = { enabled = true },
dot_case = { enabled = true },
kebab_case = { enabled = true },
pascal_case = { enabled = true },
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",
}
<
------------------------------------------------------------------------------
*TextTransform.setup()*
`TextTransform.setup`({options})
Define your text-transform setup.
Parameters~
Parameters ~
{options} `(table)` Module config table. See |TextTransform.options|.
Usage~
Usage ~
`require("text-transform").setup()` (add `{}` with your |TextTransform.options| table)
==============================================================================
------------------------------------------------------------------------------
*TextTransform.into_words()*
`TextTransform.into_words`({str})
Splits a string into words.
*find_word_boundaries()*
`find_word_boundaries`({line}, {start_col})
Finds the boundaries of the surrounding word around `start_col` within `line`.
Parameters ~
{line} `(number)`
{start_col} `(number)`
Return ~
`(number)` start_col, number end_col
------------------------------------------------------------------------------
*TextTransform.camel_case()*
`TextTransform.camel_case`({string})
Transforms a string into camelCase.
*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.
------------------------------------------------------------------------------
*TextTransform.snake_case()*
`TextTransform.snake_case`({string})
Transforms a string into snake_case.
------------------------------------------------------------------------------
*TextTransform.pascal_case()*
`TextTransform.pascal_case`({string})
Transforms a string into PascalCase.
------------------------------------------------------------------------------
*TextTransform.kebab_case()*
`TextTransform.kebab_case`({string})
Transforms a string into kebab-case.
------------------------------------------------------------------------------
*TextTransform.dot_case()*
`TextTransform.dot_case`({string})
Transforms a string into dot.case.
------------------------------------------------------------------------------
*TextTransform.title_case()*
`TextTransform.title_case`({string})
Transforms a string into Title Case.
------------------------------------------------------------------------------
*TextTransform.const_case()*
`TextTransform.const_case`({string})
Transforms a string into CONSTANT_CASE.
------------------------------------------------------------------------------
*TextTransform.replace_at()*
`TextTransform.replace_at`({start_line}, {start_col}, {end_line}, {end_col}, {transform})
Replaces the text at the given position with the given transform.
------------------------------------------------------------------------------
*TextTransform.replace_selection()*
`TextTransform.replace_selection`({transform})
Replaces the current visual selection with the given transform.
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()*
`TextTransform.replace_word`({transform})
Replaces the current word with the given transform.
`TextTransform.replace_word`({transform_name}, {position})
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.
Parameters ~
{transform_name} `(string)` The transformer name
{position} `(table|nil)` A table containing the position of the word to replace
------------------------------------------------------------------------------
*TextTransform.replace_columns()*
`TextTransform.replace_columns`({transform})
Replaces each column selection with the given transform.
`TextTransform.replace_columns`({transform_name})
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})
Replaces a selection with the given transform. This function attempts to infer the replacement
type based on the cursor positiono and visual selections, and passes information to relevant
range replacement functions.
Parameters ~
{transform_name} `(string)` The transformer name
------------------------------------------------------------------------------
*TextTransform.get_visual_selection_details()*
`TextTransform.get_visual_selection_details`()
Takes the saved positions and translates them into individual visual ranges, regardless of how
the original selection was performed.
This allows to treat all ranges equally and allows to work on each selection without knowing
the full information around the selection logic.
==============================================================================
------------------------------------------------------------------------------
*TextTransform.save_positions()*
`TextTransform.save_positions`()
Save the current cursor position, mode, and visual selection ranges
------------------------------------------------------------------------------
*TextTransform.restore_positions()*
`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.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.
The cursor positions/ranges are saved before opening the menu and restored once a selection is
made.
==============================================================================
------------------------------------------------------------------------------
*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
Parameters ~
{t1} `(table)`
{t2} `(table)`
Return ~
`(table)`
vim:tw=78:ts=8:noet:ft=help:norl:

View File

@@ -0,0 +1,71 @@
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 popup = require("text-transform.popup")
local common = require("text-transform.popup.common")
local TextTransform = {}
function TextTransform.init_commands()
local map = {
TtCamel = "camel_case",
TtConst = "const_case",
TtDot = "dot_case",
TtKebab = "kebab_case",
TtPascal = "pascal_case",
TtSnake = "snake_case",
TtTitle = "title_case",
}
local cmdopts = { range = true, force = true }
local opts = function(desc)
return util.merge(cmdopts, { desc = desc })
end
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,19 +1,63 @@
local commands = require("text-transform.commands")
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 useful logs about what event are triggered, and reasons actions are executed.
TextTransform.config = {
--- Prints information about internals of the plugin. Very verbose, only useful for debugging.
debug = false,
-- Keymap to trigger the transform.
--- Keymap configurations
keymap = {
-- Normal mode keymap.
["n"] = "<Leader>~",
-- Visual mode keymap.
["v"] = "<Leader>~",
--- 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
["n"] = "<Leader>~",
--- Opens the popup in visual/visual block modes
["v"] = "<Leader>~",
},
},
---
--- Configurations for the text-transform replacers
--- Keys indicate the replacer name, and the value is a table with the following options:
---
--- - `enabled` (boolean): Enable or disable the replacer - disabled replacers do not show up in the popup.
replacers = {
camel_case = { enabled = true },
const_case = { enabled = true },
dot_case = { enabled = true },
kebab_case = { enabled = true },
pascal_case = { enabled = true },
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",
}
--- Define your text-transform setup.
@@ -24,67 +68,17 @@ TextTransform.options = {
function TextTransform.setup(options)
options = options or {}
TextTransform.options = vim.tbl_deep_extend("keep", options, TextTransform.options)
TextTransform.config = utils.merge(TextTransform.config, options)
if vim.api.nvim_get_vvar("vim_did_enter") == 0 then
vim.defer_fn(function()
TextTransform._setup()
init()
end, 0)
else
TextTransform._setup()
init()
end
return TextTransform.options
end
local CAMEL_CASE = "&camelCase"
local SNAKE_CASE = "&snake_case"
local PASCAL_CASE = "&PascalCase"
local KEBAB_CASE = "&kebab-case"
local DOT_CASE = "&dot\\.case"
local TITLE_CASE = "&Title\\ Case"
local CONST_CASE = "C&ONST_CASE"
-- TODO save frequency of use and order by frequency
local default_ordered_keys =
{ CAMEL_CASE, SNAKE_CASE, PASCAL_CASE, CONST_CASE, KEBAB_CASE, DOT_CASE, TITLE_CASE }
function TextTransform._setup()
local map = {
[CAMEL_CASE] = "camel_case",
[SNAKE_CASE] = "snake_case",
[PASCAL_CASE] = "pascal_case",
[KEBAB_CASE] = "kebab_case",
[DOT_CASE] = "dot_case",
[TITLE_CASE] = "title_case",
[CONST_CASE] = "const_case",
}
---@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_selection('"
.. v
.. "')<CR>"
)
end
vim.keymap.set(
"n",
TextTransform.options.keymap.n,
"<cmd>popup TransformsWord<CR>",
{ silent = true }
)
vim.keymap.set(
"v",
TextTransform.options.keymap.v,
"<cmd>popup TransformsSelection<CR>",
{ silent = true }
)
return TextTransform.config
end
return TextTransform

View File

@@ -1,53 +1,23 @@
local M = require("text-transform.main")
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 = {}
-- Toggle the plugin by calling the `enable`/`disable` methods respectively.
function TextTransform.toggle()
-- 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
_G.TextTransform.state = M.toggle()
end
-- starts TextTransform and set internal functions and state.
function TextTransform.enable()
if _G.TextTransform.config == nil then
_G.TextTransform.config = require("text-transform.config").options
end
local state = M.enable()
if state ~= nil then
_G.TextTransform.state = state
end
return state
end
-- disables TextTransform and reset internal functions and state.
function TextTransform.disable()
_G.TextTransform.state = M.disable()
end
-- setup TextTransform options and merge them with user provided ones.
function TextTransform.setup(opts)
_G.TextTransform.config = require("text-transform.config").setup(opts)
end
TextTransform.into_words = M.into_words
TextTransform.replace_word = M.replace_word
TextTransform.replace_selection = M.replace_selection
local function merge(table)
TextTransform = utils.merge(TextTransform, table)
end
TextTransform.camel_case = M.camel_case
TextTransform.snake_case = M.snake_case
TextTransform.pascal_case = M.pascal_case
TextTransform.kebab_case = M.kebab_case
TextTransform.dot_case = M.dot_case
TextTransform.title_case = M.title_case
TextTransform.const_case = M.const_case
merge(tt)
merge(replacers)
merge(state)
merge(popup)
_G.TextTransform = TextTransform
return _G.TextTransform

View File

@@ -1,245 +0,0 @@
local D = require("text-transform.util.debug")
-- internal methods
local TextTransform = {}
-- state
local S = {
-- Boolean determining if the plugin is enabled or not.
enabled = false,
}
---Toggle the plugin by calling the `enable`/`disable` methods respectively.
---@private
function TextTransform.toggle()
if S.enabled then
return TextTransform.disable()
end
return TextTransform.enable()
end
---Initializes the plugin.
---@private
function TextTransform.enable()
if S.enabled then
return S
end
S.enabled = true
return S
end
---Disables the plugin and reset the internal state.
---@private
function TextTransform.disable()
if not S.enabled then
return S
end
-- reset the state
S = {
enabled = false,
}
return S
end
--- Splits a string into words.
function TextTransform.into_words(str)
local words = {}
local word = ""
local previous_is_split_token = false
for i = 1, #str do
local char = str:sub(i, i)
-- split on uppercase letters or numbers
if char:match("%u") or char:match("%d") and not previous_is_split_token then
if word ~= "" then
table.insert(words, word)
end
previous_is_split_token = true
word = char
-- split on underscores, hyphens, and spaces
elseif char:match("[%_%-%s%.]") then
if word ~= "" then
table.insert(words, word)
previous_is_split_token = false
end
word = ""
else
word = word .. char
previous_is_split_token = char:match("%u") or char:match("%d")
end
end
if word ~= "" then
table.insert(words, word)
previous_is_split_token = false
end
return words
end
--- Transforms a string into camelCase.
function TextTransform.camel_case(string)
local words = TextTransform.into_words(string)
local camel_case = ""
for i, word in ipairs(words) do
if i == 1 then
camel_case = camel_case .. word:lower()
else
camel_case = camel_case .. word:sub(1, 1):upper() .. word:sub(2):lower()
end
end
return camel_case
end
--- Transforms a string into snake_case.
function TextTransform.snake_case(string)
local words = TextTransform.into_words(string)
local snake_case = ""
for i, word in ipairs(words) do
if i == 1 then
snake_case = snake_case .. word:lower()
else
snake_case = snake_case .. "_" .. word:lower()
end
end
return snake_case
end
--- Transforms a string into PascalCase.
function TextTransform.pascal_case(string)
local words = TextTransform.into_words(string)
local pascal_case = ""
for _, word in ipairs(words) do
pascal_case = pascal_case .. word:sub(1, 1):upper() .. word:sub(2):lower()
end
return pascal_case
end
--- Transforms a string into kebab-case.
function TextTransform.kebab_case(string)
local words = TextTransform.into_words(string)
local kebab_case = ""
for i, word in ipairs(words) do
if i == 1 then
kebab_case = kebab_case .. word:lower()
else
kebab_case = kebab_case .. "-" .. word:lower()
end
end
return kebab_case
end
--- Transforms a string into dot.case.
function TextTransform.dot_case(string)
local words = TextTransform.into_words(string)
local dot_case = ""
for i, word in ipairs(words) do
if i == 1 then
dot_case = dot_case .. word:lower()
else
dot_case = dot_case .. "." .. word:lower()
end
end
return dot_case
end
--- Transforms a string into Title Case.
function TextTransform.title_case(string)
local words = TextTransform.into_words(string)
local title_case = ""
for i, word in ipairs(words) do
title_case = title_case .. word:sub(1, 1):upper() .. word:sub(2):lower()
if i ~= #words then
title_case = title_case .. " "
end
end
return title_case
end
--- Transforms a string into CONSTANT_CASE.
function TextTransform.const_case(string)
local words = TextTransform.into_words(string)
local const_case = ""
for i, word in ipairs(words) do
if i == 1 then
const_case = const_case .. word:upper()
else
const_case = const_case .. "_" .. word:upper()
end
end
return const_case
end
--- Replaces the text at the given position with the given transform.
function TextTransform.replace_at(start_line, start_col, end_line, end_col, transform)
-- use the arguments to replace at the position
local lines = vim.fn.getline(start_line, end_line)
local transformed = ""
if #lines == 1 then
transformed = lines[1]:sub(1, start_col - 1)
.. TextTransform[transform](lines[1]:sub(start_col, end_col))
.. lines[1]:sub(end_col + 1)
else
transformed = lines[1]:sub(1, start_col - 1)
.. TextTransform[transform](lines[1]:sub(start_col))
.. "\n"
for i = 2, #lines - 1 do
transformed = transformed .. TextTransform[transform](lines[i]) .. "\n"
end
transformed = transformed
.. TextTransform[transform](lines[#lines]:sub(1, end_col))
.. lines[#lines]:sub(end_col + 1)
end
-- replace the lines with the transformed lines
vim.fn.setline(start_line, transformed)
for i = start_line + 1, end_line do
vim.fn.setline(i, "")
end
end
--- Replaces the current visual selection with the given transform.
function TextTransform.replace_selection(transform)
-- get the current visual selection, and transform the line, only replacing the selected text itself
local _, start_line, start_col = unpack(vim.fn.getpos("'<"))
local _, end_line, end_col = unpack(vim.fn.getpos("'>"))
-- transform all included lines
TextTransform.replace_at(start_line, start_col, end_line, end_col, transform)
-- move the cursor to the end of the transformed text
vim.fn.cursor(end_line, end_col)
end
--- Replaces the current word with the given transform.
function TextTransform.replace_word(transform)
local word = vim.fn.expand("<cword>")
local transformed = TextTransform[transform](word)
vim.cmd("normal ciw" .. transformed)
end
--- Replaces each column selection with the given transform.
function TextTransform.replace_columns(transform)
-- get each cursor position and apply to each cursor's word
local cursors = vim.fn.getpos("'<")
for _, cursor in ipairs(cursors) do
-- unpack the cursor values
local start_line, start_col, end_line, end_col = unpack(cursor)
local line = vim.fn.getline(start_line)
if start_col == end_col then
-- if the cursor is one length, get the word under the cursor
local word = line:match("%w+", start_col)
TextTransform.replace_at(start_line, start_col, start_line, start_col + #word, transform)
else
-- otherwise, get the word between the cursors
TextTransform.replace_at(start_line, start_col, end_line, end_col, transform)
end
end
end
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

@@ -0,0 +1,233 @@
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
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
local word_pat = "[A-Za-z0-9_.\\-]"
local non_word_pat = "[^A-Za-z0-9_.\\-]"
-- TODO handle searching backwards
local word_start_col = vim.fn.match(line_text:sub(start_col), word_pat) + start_col
local word_end_col = vim.fn.match(line_text:sub(word_start_col), non_word_pat)
+ word_start_col
- 1
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("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 transformed = {}
if #lines == 1 then
local line = lines[1]
local before = line:sub(0, start_col - 1)
local fixed = transform(line:sub(start_col, end_col))
local after = line:sub(end_col + 1)
table.insert(transformed, before .. fixed .. after)
else
for i, line in ipairs(lines) do
if i == 1 then
table.insert(transformed, line:sub(1, start_col - 1) .. transform(line:sub(start_col)))
elseif i == #lines then
table.insert(transformed, transform(line:sub(1, end_col)) .. line:sub(end_col + 1))
else
table.insert(transformed, transform(line))
end
end
end
vim.fn.setline(start_line, transformed)
end
--- 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
function TextTransform.replace_word(transform_name, position)
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>")
else
_, line, col = unpack(position)
start_col, end_col = find_word_boundaries(line, col)
word = vim.fn.getline(line):sub(start_col, end_col)
end
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("replace_word", "New value %s", transformed)
if not position then
vim.cmd("normal ciw" .. transformed)
else
TextTransform.replace_range(line, start_col, line, end_col, transform_name)
end
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("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
end
--- Replaces a selection with the given transform. This function attempts to infer the replacement
--- type based on the cursor positiono and visual selections, and passes information to relevant
--- range replacement functions.
---
---@param transform_name string The transformer name
function TextTransform.replace_selection(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("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(
"replace_selection",
"is_multiline: %s, is_column: %s, is_word: %s",
is_multiline,
is_column,
is_single_cursor
)
if is_single_cursor then
TextTransform.replace_word(transform_name)
elseif is_column then
TextTransform.replace_columns(transform_name)
else
for _, sel in pairs(selections) do
TextTransform.replace_range(
sel.start_line,
sel.start_col,
sel.end_line,
sel.end_col,
transform_name
)
end
end
end
--- Takes the saved positions and translates them into individual visual ranges, regardless of how
--- the original selection was performed.
---
--- 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(
"get_visual_selection_details",
"Getting visual selection details - mode: %s, is_visual: %s, is_block: %s",
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 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],
end_line = pos[2],
start_col = pos[3],
end_col = pos[3],
},
}
end
-- 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
start_col, end_col = end_col, start_col
end
-- If it's block visual mode, return table for each row
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
-- find the word surrounding the position
start_col, _ = find_word_boundaries(line, start_col)
end
table.insert(block_selection, {
start_line = line,
end_line = line,
start_col = start_col,
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,
end_line = end_line,
start_col = start_col,
end_col = end_col,
},
}
end
end
return TextTransform

View File

@@ -0,0 +1,126 @@
local D = require("text-transform.utils.debug")
-- methods
local TextTransform = {
-- 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 = {},
},
}
local function get_mode_type(mode)
-- classify mode as either visual, line, block or normal
local mode_map = {
["v"] = "visual",
["V"] = "line",
["\22"] = "block",
}
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
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, 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("save_positions", "Saved mode %s, cursor %s", mode, vim.inspect(pos))
if mode == "visual" or mode == "line" or mode == "block" then
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 = capture_part(visual_start, visual_end, "start")
visual_end = capture_part(visual_start, visual_end, "end")
end
D.log(
"state",
"Saved visual mode %s, cursor %s",
mode,
vim.inspect({ visual_start, visual_end })
)
end
local positions = {
buf = buf,
mode = mode,
pos = pos,
visual_start = visual_start,
visual_end = visual_end,
}
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(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
(positions.mode == "visual" or positions.mode == "block")
and positions.visual_start
and positions.visual_end
then
vim.fn.setpos("'<", positions.visual_start)
vim.fn.setpos("'>", positions.visual_end)
local command = "normal! gv"
vim.cmd(command)
D.log("restore_positions", [[Restored visual mode %s using "%s"]], positions.mode, command)
end
TextTransform.state.positions = {}
end
return TextTransform

View File

@@ -0,0 +1,151 @@
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
function TextTransform.to_words(string)
local words = {}
local word = ""
local last_is_upper = false
local last_is_digit = false
for i = 1, #string do
local char = string:sub(i, i)
if char:match(TextTransform.WORD_BOUNDRY) then
if word ~= "" then
table.insert(words, word:lower())
end
word = ""
else
if
(char:match("%d") and not last_is_digit)
or (char:match("%u") and not last_is_upper and word ~= "")
or (char:match("%l") and last_is_digit)
then
if word ~= "" then
table.insert(words, word:lower())
word = ""
end
end
-- Update flags based on current character type
if char:match("%d") then
last_is_digit = true
last_is_upper = false
elseif char:match("%u") then
last_is_upper = true
last_is_digit = false
else -- Lowercase or any non-digit/non-uppercase
last_is_upper = false
last_is_digit = false
end
-- Append current character to the current word
word = word .. char
end
-- 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("to_words", "words %s", vim.inspect(words))
return words
end
--- 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
function TextTransform.transform_words(words, with_word_cb, separator)
if type(words) ~= "table" then
words = TextTransform.to_words(words)
end
local out = ""
for i, word in ipairs(words) do
local new_word = with_word_cb(word, i, word)
if separator and i > 1 then
new_word = separator .. new_word
end
out = out .. new_word
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
function TextTransform.to_camel_case(string)
return TextTransform.transform_words(string, function(word, i)
if i == 1 then
return word:lower()
end
return word:sub(1, 1):upper() .. word:sub(2):lower()
end)
end
--- Transfroms a string into snake_case.
---@param string any
---@return string
function TextTransform.to_snake_case(string)
return TextTransform.transform_words(string, function(word, i)
if i == 1 then
return word:lower()
end
return word:lower()
end, "_")
end
--- Transforms a string into PascalCase.
---@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
function TextTransform.to_title_case(string)
return TextTransform.transform_words(string, function(word)
return word:sub(1, 1):upper() .. word:sub(2):lower()
end, " ")
end
--- Transforms a string into kebab-case.
---@param string string
---@return string
function TextTransform.to_kebab_case(string)
return TextTransform.transform_words(string, function(word)
return word:lower()
end, "-")
end
--- Transforms a string into dot.case.
---@param string string
---@return string
function TextTransform.to_dot_case(string)
return TextTransform.transform_words(string, function(word)
return word:lower()
end, ".")
end
--- Transforms a string into CONSTANT_CASE.
---@param string string
---@return string
function TextTransform.to_const_case(string)
return TextTransform.transform_words(string, function(word)
return word:upper()
end, "_")
end
return TextTransform

View File

@@ -1,13 +1,13 @@
local D = {}
---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
local function is_debug()
return _G.TextTransform ~= nil
and _G.TextTransform.config ~= nil
and _G.TextTransform.config.debug
end
function D.log(scope, str, ...)
if _G.TextTransform.config ~= nil and not _G.TextTransform.config.debug then
if not is_debug() then
return
end
@@ -20,22 +20,17 @@ 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 _G.TextTransform.config ~= nil and not _G.TextTransform.config.debug then
if not is_debug() then
return
end

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

@@ -6,6 +6,8 @@ if #vim.api.nvim_list_uis() == 0 then
-- Add 'mini.nvim' to 'runtimepath' to be able to use 'mini.test'
-- Assumed that 'mini.nvim' is stored in 'deps/mini.nvim'
vim.cmd("set rtp+=deps/mini.nvim")
vim.cmd("set rtp+=deps/telescope.nvim")
vim.cmd("set rtp+=deps/plenary.nvim")
-- Set up 'mini.test'
require("mini.test").setup()

26
scripts/precommit.sh Executable file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# shellcheck disable=SC2181,SC2086
# Stylua check
diffs=$(stylua --check --output-format=json .)
# Exit code != 0 means there are changes (files are not formatted)
if [[ "$?" -ne 0 ]]; then
# Get filenames of diffs
filelist="$(echo "$diffs" | jq -r '.file')"
# Format & add to git
stylua $filelist
git add $filelist
fi
# Run lints
make lint
# Run tests
# make test
# Generate docs & add to git
make documentation
git add doc

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

78
tests/test_config.lua Normal file
View File

@@ -0,0 +1,78 @@
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["setup()"] = MiniTest.new_set()
T["setup()"]["sets exposed methods and default options value"] = function()
child.lua([[require('text-transform').setup()]])
-- global object that holds your plugin information
eq_type_global(child, "_G.TextTransform", "table")
-- config
eq_type_global(child, "_G.TextTransform.config", "table")
-- assert the value, and the type
eq_type_config(child, "debug", "boolean")
eq_config(child, "debug", false)
eq_type_config(child, "keymap", "table")
eq_type_config(child, "keymap.telescope_popup", "table")
eq_type_config(child, "keymap.telescope_popup.v", "string")
eq_config(child, "keymap.telescope_popup.v", "<Leader>~")
eq_type_config(child, "keymap.telescope_popup.n", "string")
eq_config(child, "keymap.telescope_popup.n", "<Leader>~")
end
T["setup()"]["overrides default values"] = function()
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")
eq_config(child, "debug", true)
eq_type_config(child, "keymap", "table")
eq_type_config(child, "keymap.v", "string")
eq_config(child, "keymap.v", "<leader>c")
eq_type_config(child, "keymap.n", "string")
eq_config(child, "keymap.n", "<leader>c")
end
return T

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

69
tests/test_to_words.lua Normal file
View File

@@ -0,0 +1,69 @@
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,
},
})
local function test_string(child, str)
helpers.init_plugin(child)
child.lua([[result = require('text-transform').to_words("]] .. str .. [[")]])
end
T["to_words()"] = MiniTest.new_set()
T["to_words()"]["should split normal spaced words"] = function()
test_string(child, "hello world")
eq_type_global(child, "result", "table")
eq_global(child, "result", { "hello", "world" })
end
T["to_words()"]["should split camel case strings"] = function()
test_string(child, "helloWorld")
eq_type_global(child, "result", "table")
eq_global(child, "result", { "hello", "world" })
end
T["to_words()"]["should split dot case strings"] = function()
test_string(child, "hello.world")
eq_type_global(child, "result", "table")
eq_global(child, "result", { "hello", "world" })
end
T["to_words()"]["should split const case strings"] = function()
test_string(child, "HELLO_WORLD")
eq_type_global(child, "result", "table")
eq_global(child, "result", { "hello", "world" })
end
T["to_words()"]["should treat numbers as words"] = function()
test_string(child, "helloWorld123")
eq_type_global(child, "result", "table")
eq_global(child, "result", { "hello", "world", "123" })
end
T["to_words()"]["should trim trailing/leading"] = function()
test_string(child, " hello world ")
eq_type_global(child, "result", "table")
eq_global(child, "result", { "hello", "world" })
end
return T

View File

@@ -22,94 +22,14 @@ local T = MiniTest.new_set({
},
})
-- Tests related to the `setup` method.
T["setup()"] = MiniTest.new_set()
T["setup()"]["sets exposed methods and default options value"] = function()
child.lua([[require('text-transform').setup()]])
-- 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")
-- assert the value, and the type
eq_config(child, "debug", false)
eq_type_config(child, "debug", "boolean")
eq_type_config(child, "keymap", "table")
eq_config(child, "keymap.v", "<Leader>~")
eq_type_config(child, "keymap.v", "string")
eq_config(child, "keymap.n", "<Leader>~")
eq_type_config(child, "keymap.n", "string")
end
T["setup()"]["overrides default values"] = function()
child.lua([[require('text-transform').setup({
-- 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")
eq_config(child, "debug", true)
eq_type_config(child, "keymap", "table")
eq_config(child, "keymap.v", "<leader>c")
eq_type_config(child, "keymap.v", "string")
eq_config(child, "keymap.n", "<leader>c")
eq_type_config(child, "keymap.n", "string")
end
local function make_transform_test(fn_name, input, expected)
return function()
child.lua([[require('text-transform').setup()]])
child.lua([[result = require('text-transform').]] .. fn_name .. '("' .. input .. '")')
child.lua([[result = require('text-transform').to_]] .. fn_name .. '("' .. input .. '")')
eq_global(child, "result", expected)
end
end
T["into_words()"] = MiniTest.new_set()
T["into_words()"]["should split two words with spaces"] = function()
child.lua([[require('text-transform').setup()]])
child.lua([[result = require('text-transform').into_words("helloWorld")]])
eq_type_global(child, "result", "table")
eq_global(child, "result[1]", "hello")
eq_global(child, "result[2]", "World")
end
T["into_words()"]["should split two words with dots"] = function()
child.lua([[require('text-transform').setup()]])
child.lua([[result = require('text-transform').into_words("hello.world")]])
eq_type_global(child, "result", "table")
eq_global(child, "result[1]", "hello")
eq_global(child, "result[2]", "world")
end
T["into_words()"]["should split two words with a number inside"] = function()
child.lua([[require('text-transform').setup()]])
child.lua([[result = require('text-transform').into_words("helloWorld123")]])
eq_type_global(child, "result", "table")
eq_global(child, "result[1]", "hello")
eq_global(child, "result[2]", "World")
eq_global(child, "result[3]", "123")
end
local map = {
["camel_case"] = {
{ "hello_world", "helloWorld" },
@@ -118,6 +38,7 @@ local map = {
{ "hello.world", "helloWorld" },
{ "hello", "hello" },
{ "helloWorld123", "helloWorld123" },
{ "HELLO_WORLD", "helloWorld" },
},
["snake_case"] = {
{ "helloWorld", "hello_world" },
@@ -126,6 +47,7 @@ local map = {
{ "hello.world", "hello_world" },
{ "hello", "hello" },
{ "helloWorld123", "hello_world_123" },
{ "HELLO_WORLD", "hello_world" },
},
["pascal_case"] = {
{ "hello_world", "HelloWorld" },
@@ -134,6 +56,7 @@ local map = {
{ "hello.world", "HelloWorld" },
{ "hello", "Hello" },
{ "helloWorld123", "HelloWorld123" },
{ "HELLO_WORLD", "HelloWorld" },
},
["kebab_case"] = {
{ "helloWorld", "hello-world" },
@@ -142,6 +65,7 @@ local map = {
{ "hello.world", "hello-world" },
{ "hello", "hello" },
{ "helloWorld123", "hello-world-123" },
{ "HELLO_WORLD", "hello-world" },
},
["dot_case"] = {
{ "helloWorld", "hello.world" },
@@ -150,6 +74,7 @@ local map = {
{ "hello.world", "hello.world" },
{ "hello", "hello" },
{ "helloWorld123", "hello.world.123" },
{ "HELLO_WORLD", "hello.world" },
},
["const_case"] = {
{ "helloWorld", "HELLO_WORLD" },
@@ -158,6 +83,7 @@ local map = {
{ "hello.world", "HELLO_WORLD" },
{ "hello", "HELLO" },
{ "helloWorld123", "HELLO_WORLD_123" },
{ "HELLO_WORLD", "HELLO_WORLD" },
},
["title_case"] = {
{ "helloWorld", "Hello World" },
@@ -166,6 +92,7 @@ local map = {
{ "hello.world", "Hello World" },
{ "hello", "Hello" },
{ "helloWorld123", "Hello World 123" },
{ "HELLO_WORLD", "Hello World" },
},
}