├── .eslintrc.json
├── .github
└── workflows
│ └── ci.yaml
├── .gitignore
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── .vscodeignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── icon.png
├── package-lock.json
├── package.json
├── src
├── extension.ts
├── test
│ ├── runTest.ts
│ └── suite
│ │ ├── extension.test.ts
│ │ ├── files
│ │ ├── delete_all_trailing_spaces_including_blank_lines_result.js
│ │ ├── delete_all_trailing_spaces_including_blank_lines_sample.js
│ │ ├── delete_all_trailing_spaces_result.js
│ │ ├── delete_all_trailing_spaces_sample.js
│ │ ├── delete_trailing_spaces_exclude_current_line_highlight_result.js
│ │ ├── delete_trailing_spaces_exclude_current_line_highlight_sample.js
│ │ ├── delete_trailing_spaces_exclude_empty_line_result.js
│ │ ├── delete_trailing_spaces_exclude_empty_line_sample.js
│ │ ├── delete_trailing_spaces_exclude_empty_line_when_exclude_current_line_highlight_result.js
│ │ ├── delete_trailing_spaces_exclude_empty_line_when_exclude_current_line_highlight_sample.js
│ │ ├── delete_trailing_spaces_in_consecutive_modified_lines_result.js
│ │ ├── delete_trailing_spaces_in_consecutive_modified_lines_sample.js
│ │ ├── delete_trailing_spaces_in_modified_lines_result.js
│ │ ├── delete_trailing_spaces_in_modified_lines_sample.js
│ │ ├── delete_trailing_spaces_in_new_and_modified_lines_result.js
│ │ ├── delete_trailing_spaces_in_new_and_modified_lines_sample.js
│ │ ├── should_not_delete_spaces_result.js
│ │ └── should_not_delete_spaces_sample.js
│ │ └── index.ts
└── trailing-spaces
│ ├── loader.ts
│ ├── logger.ts
│ ├── settings.ts
│ ├── trailing-spaces.ts
│ └── utils.ts
└── tsconfig.json
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "parserOptions": {
5 | "ecmaVersion": 6,
6 | "sourceType": "module"
7 | },
8 | "plugins": [
9 | "@typescript-eslint"
10 | ],
11 | "rules": {
12 | "@typescript-eslint/naming-convention": "warn",
13 | "@typescript-eslint/semi": "warn",
14 | "curly": "warn",
15 | "eqeqeq": "warn",
16 | "no-throw-literal": "warn",
17 | "semi": "off"
18 | },
19 | "ignorePatterns": [
20 | "out",
21 | "dist",
22 | "**/*.d.ts"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: Trailing Spaces CI
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 | strategy:
12 | matrix:
13 | os: [macos-latest, ubuntu-latest, windows-latest]
14 | node-version: [14.x, 16.x, 18.x]
15 | runs-on: ${{ matrix.os }}
16 | steps:
17 | - name: Checkout
18 | uses: actions/checkout@v3
19 | - name: Install Node.js
20 | uses: actions/setup-node@v3
21 | with:
22 | node-version: ${{ matrix.node-version }}
23 | - run: npm install
24 | - run: xvfb-run -a npm test
25 | if: runner.os == 'Linux'
26 | - run: npm test
27 | if: runner.os != 'Linux'
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | out
2 | dist
3 | node_modules
4 | .vscode-test/
5 | *.vsix
6 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | {
6 | "version": "0.2.0",
7 | "configurations": [
8 | {
9 | "name": "Run Extension",
10 | "type": "extensionHost",
11 | "request": "launch",
12 | "args": [
13 | "--extensionDevelopmentPath=${workspaceFolder}"
14 | ],
15 | "outFiles": [
16 | "${workspaceFolder}/out/**/*.js"
17 | ],
18 | "preLaunchTask": "${defaultBuildTask}"
19 | },
20 | {
21 | "name": "Extension Tests",
22 | "type": "extensionHost",
23 | "request": "launch",
24 | "args": [
25 | "--extensionDevelopmentPath=${workspaceFolder}",
26 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
27 | ],
28 | "outFiles": [
29 | "${workspaceFolder}/out/test/**/*.js"
30 | ],
31 | "preLaunchTask": "${defaultBuildTask}"
32 | }
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "out": false // set this to true to hide the "out" folder with the compiled JS files
5 | },
6 | "search.exclude": {
7 | "out": true // set this to false to include "out" folder in search results
8 | },
9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
10 | "typescript.tsc.autoDetect": "off"
11 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // See https://go.microsoft.com/fwlink/?LinkId=733558
2 | // for the documentation about the tasks.json format
3 | {
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "type": "npm",
8 | "script": "watch",
9 | "problemMatcher": "$tsc-watch",
10 | "isBackground": true,
11 | "presentation": {
12 | "reveal": "never"
13 | },
14 | "group": {
15 | "kind": "build",
16 | "isDefault": true
17 | }
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | src/**
4 | .gitignore
5 | .yarnrc
6 | vsc-extension-quickstart.md
7 | **/tsconfig.json
8 | **/.eslintrc.json
9 | **/*.map
10 | **/*.ts
11 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 |
4 | ## [0.4.1] - 2022-07-10
5 | ### Fixed
6 | - Fixed an issue where changing settings would result in previously highlighted regions being permanently highlighted
7 | ### Changed
8 | - `highlightCurrentLine` now considers all active cursors as current lines
9 |
10 |
11 | ## [0.4.0] - 2022-07-08
12 | ### Fixed
13 | - Fix an issue where only the first line of each block of modified lines is trimmed when `deleteModifiedLinesOnly` is set
14 | ### Changed
15 | - Extension will now activate only after VSCode startup has finished to avoid VSCode startup slowdown
16 |
17 |
18 | ## [0.3.1] - 2019-04-08
19 | ### Changed
20 | - Trigger `trimOnSave` on auto-saves along with manual saves to maintain backward compatibility
21 |
22 |
23 | ## [0.3.0] - 2019-04-06
24 | ### Added
25 | - Allow specific schemes to be ignored.
26 | - Add options to change background and border colors of highlighting.
27 | - Add option to disable status bar message.
28 |
29 | ### Changed
30 | - Only attach VSCode event listeners if current settings require them.
31 | - Major refactor to simplify the core extension logic.
32 | - Ignore `output` scheme by default
33 |
34 | ### Fixed
35 | - Fix "Trim on Save" requiring file to be saved twice.
36 | - Fix error when trying to read saved document for non-file schemes.
37 |
38 | ### Removed
39 | - Remove "Save after Trim" as current VSCode lifecycle for text editor commands does not provide a clean way to implement this feature.
40 | - Remove `deleteInFolder` and `deleteInFolderRecursive` functionality which was experimental.
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Jean-Denis Vauguet, Shardul Mahadik
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Trailing Spaces
2 | ===============
3 |
4 | [](https://travis-ci.org/shardulm94/vscode-trailingspaces)
5 | [  ](https://marketplace.visualstudio.com/items?itemName=shardulm94.trailing-spaces)
6 |
7 | A [VS Code](https://code.visualstudio.com/) extension that allows you to…
8 |
9 | **highlight trailing spaces and delete them in a flash!**
10 |
11 | This extension is a port of the popular [Sublime Text](https://www.sublimetext.com/) plugin [Trailing Spaces](https://github.com/SublimeText/TrailingSpaces).
12 |
13 | ---
14 |
15 | - [Synopsis](#synopsis)
16 | - [Installation](#installation)
17 | - [Usage](#usage)
18 | - [Delete](#delete)
19 | - [Delete - Modified Lines Only](#delete---modified-lines-only)
20 | - [Highlight](#highlight)
21 | - [Options](#options)
22 | - [Highlight Current Line](#highlight-current-line)
23 | - [Include Empty Lines](#include-empty-lines)
24 | - [Delete Modified Lines Only](#delete-modified-lines-only)
25 | - [Trim On Save](#trim-on-save)
26 | - [~~Save After Trim~~ *[REMOVED]*](#save-after-trim-removed)
27 | - [Live Matching vs On-demand Matching](#live-matching-vs-on-demand-matching)
28 | - [Ignore Syntax](#ignore-syntax)
29 | - [Ignore Scheme](#ignore-scheme)
30 | - [Show Status Bar Message](#show-status-bar-message)
31 | - [Background Color](#background-color)
32 | - [Border Color](#border-color)
33 | - [For power-users only!](#for-power-users-only)
34 | - [The matching pattern](#the-matching-pattern)
35 |
36 | Synopsis
37 | --------
38 |
39 | VS Code provides a way to automate deletion of trailing spaces *by using the Trim Trailing Whitespace command*. Depending on your settings, it may be more handy to just highlight them and/or delete them by hand, at any time. This plugin provides just that, and a *lot* of options to fine-tune the way you want to decimate trailing spaces.
40 |
41 | Installation
42 | ------------
43 |
44 | It is available through [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=shardulm94.trailing-spaces) and this is the recommended way of installation (brings integrated updates).
45 |
46 |
47 | Usage
48 | -----
49 |
50 | ### Delete
51 |
52 | The main feature you gain from using this plugin is that of deleting all trailing spaces in the currently edited document. In order to use this deletion feature, you may either:
53 |
54 | * press F1 and select/type "Trailing Spaces: Delete"
55 | * bind the deletion command to a keyboard shortcut:
56 |
57 | To add a key binding, open "File / Preferences / Keyboard Shortcuts" and add:
58 |
59 | ``` js
60 | { "key": "alt+shift+t", "command": "trailing-spaces.deleteTrailingSpaces",
61 | "when": "editorTextFocus" },
62 | ```
63 |
64 | With this setting, pressing Alt + Shift + t will delete all trailing spaces at once in the current file!
65 |
66 | ### Delete - Modified Lines Only
67 |
68 | You can also delete the trailing spaces exclusively from the modified (unsaved) lines. In order to use this deletion feature, you may either:
69 |
70 | * press F1 and select/type "Trailing Spaces: Delete - Modified Lines Only"
71 | * bind the deletion command to a keyboard shortcut:
72 |
73 | To add a key binding, open "File / Preferences / Keyboard Shortcuts" and add:
74 |
75 | ``` js
76 | { "key": "alt+shift+m", "command": "trailing-spaces.deleteTrailingSpacesModifiedLinesOnly",
77 | "when": "editorTextFocus" },
78 | ```
79 | ### Highlight
80 |
81 | At any time, you can highlight the trailing spaces. You may either:
82 |
83 | - press F1 and select/type "Trailing Spaces: Highlight"
84 | - bind the highlighting command to a keyboard shortcut:
85 |
86 | ``` js
87 | { "key": "alt+shift+h", "command": "trailing-spaces.highlightTrailingSpaces",
88 | "when": "editorTextFocus" },
89 | ```
90 |
91 | Options
92 | -------
93 |
94 | Several options are available to customize the plugin's behavior. Those settings are stored in a configuration file, as JSON. You must use a specific file: Go to "File / Preferences / User Settings" to add your custom settings.
95 |
96 | All settings are global (ie. applied to all opened documents).
97 |
98 | ### Highlight Current Line
99 |
100 | *Default: true*
101 |
102 | Highlighting of trailing spaces in the currently edited line can be annoying:
103 | each time you are about to start a new word, the space you type is matched as a trailing spaces. Currently edited line can thus be ignored:
104 |
105 | ``` js
106 | { "trailing-spaces.highlightCurrentLine": false }
107 | ```
108 |
109 | Even though the trailing spaces are not highlighted on this line, they are still internally matched and will be deleted when firing the deletion command. If you are editing multiple lines (using multiple cursors), all such lines will be ignored.
110 |
111 | ### Include Empty Lines
112 |
113 | *Default: true*
114 |
115 | When firing the deletion command, empty lines are matched as trailing regions, and end up being deleted. You can specifically ignore them:
116 |
117 | ``` js
118 | { "trailing-spaces.includeEmptyLines": false }
119 | ```
120 |
121 | They will not be highlighted either.
122 |
123 | *Note:* This option only deletes the trailing spaces in blank lines and not the whole line itself. If you want to delete the newline character as well, please consider [changing the regex](#the-matching-pattern).
124 |
125 | ### Delete Modified Lines Only
126 |
127 | *Default: false*
128 |
129 | When firing the deletion command, trailing regions *in the entire document* are deleted. There are some use-cases when deleting trailing spaces *only on lines you edited* is smarter; for instance when commiting changes to some third-party source code.
130 |
131 | At any time, you can change which area is covered when deleting trailing regions by changing the setting:
132 |
133 | ``` js
134 | { "trailing-spaces.deleteModifiedLinesOnly": true }
135 | ```
136 |
137 | ### Trim On Save
138 |
139 | *Default: false*
140 |
141 | Setting this to `true` will ensure trailing spaces are deleted when you save your document. It abides by the other settings, such as *Include Empty Lines*.
142 |
143 | ``` js
144 | { "trailing-spaces.trimOnSave": true }
145 | ```
146 |
147 | ### ~~Save After Trim~~ **[REMOVED]**
148 |
149 | *NOTE: The current VSCode lifecycle for text editor commands does not provide a clean way to implement this feature. Since I did not see a lot of folks using this option, it was better to remove it.*
150 |
151 | *Default: false*
152 |
153 | You may not want to always trim trailing spaces on save, but the other way around could prove useful. Setting this to `true` will automatically save your document after you fire the deletion command:
154 |
155 | ``` js
156 | { "trailing-spaces.saveAfterTrim": true }
157 | ```
158 |
159 | It is obviously ignored if *Trim On Save* is on.
160 |
161 | ### Live Matching vs On-demand Matching
162 |
163 | *Default: true (reload VS Code Window to update)*
164 |
165 | By default, trailing regions are matched every time you edit the document, and when you open it.
166 |
167 | This feature is entirely optional and you may set it off: firing the deletion command will cause the trailing spaces to be deleted as expected even though they were not matched prior to your request. If you are afraid of the plugin to cause slowness (for instance, you already installed several *heavy* extensions), you can disable live matching:
168 |
169 | ``` js
170 | { "trailing-spaces.liveMatching": false }
171 | ```
172 |
173 | In this case, for no trailing regions are matched until you request them to be deleted, no highlighting occurs—it is in fact disabled. If you want to check the trailing spaces regions, you can use the `Highlight Trailing Spaces` command. In this case, it may come in handy to define a binding for the highlighting command. When "On-demand Matching" is on and some trailing spaces are highlighted, added ones will obviously not be. Running the highlight command again will refresh them.
174 |
175 | ### Ignore Syntax
176 |
177 | *Default: []*
178 |
179 | With this option you can ignore specific files based on the language used. An item has to match the case-sensitive string of the language used in the file:
180 |
181 | ``` js
182 | // files with the language "markdown" are ignored
183 | { "trailing-spaces.syntaxIgnore": ["markdown"]}
184 | ```
185 |
186 | Here is a list of all languages that VS Code supports (as of 28 March 2019):
187 |
188 | ```js
189 | bat, c, clojure, coffeescript, cpp, csharp, css, diff, dockerfile, fsharp, git-commit, git-rebase, go, groovy, handlebars, hexdump, hlsl, hocon, html, ignore, ini, jade, java, javascript, javascriptreact, jinja, json, jsonc, jsx-tags, jupyter, less, Log, log, lua, makefile, markdown, objective-c, objective-cpp, perl, perl6, php, pig, pip-requirements, plaintext, powershell, properties, python, r, razor, ruby, rust, scss, shaderlab, shellscript, sql, swift, toml, typescript, typescriptreact, vb, xml, xsl, yaml
190 | ```
191 |
192 | For the most recent list of langauges, please use the [known language identifiers](https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers) page or the `languages.getLanguages()` function (details [here](https://code.visualstudio.com/docs/extensionAPI/vscode-api#languages.getLanguages)).
193 |
194 | ### Ignore Scheme
195 |
196 | *Default: ["output"]*
197 |
198 | With this option you can ignore documents with a specific scheme. An item has to match the case-sensitive string of the scheme of the document. For instance, if you want to ignore VSCode output windows:
199 |
200 | ``` js
201 | // documents with the scheme "output" are ignored
202 | { "trailing-spaces.schemeIgnore": ["output"]}
203 | ```
204 |
205 | ### Show Status Bar Message
206 |
207 | *Default: true*
208 |
209 | By default, trailing space deletions will be communicated through a status bar message. Set this to `false` as below to disable these messages:
210 |
211 | ``` js
212 | { "trailing-spaces.showStatusBarMessage": false }
213 | ```
214 |
215 | ### Background Color
216 |
217 | *Default: rgba(255,0,0,0.3)*
218 |
219 | You can control the background color of the highlighting performed by Trailing Spaces using this option. To set up another color change the setting:
220 |
221 | ``` js
222 | { "trailing-spaces.backgroundColor": "rgba(255,0,0,0.3)" }
223 | ```
224 |
225 | To make the highlighting invisible, set this and the border color properties to `transparent`.
226 |
227 | ### Border Color
228 |
229 | *Default: rgba(255,100,100,0.15)*
230 |
231 | You can control the border color of the highlighting performed by Trailing Spaces using this option. To set up another color change the setting:
232 |
233 | ``` js
234 | { "trailing-spaces.borderColor": "rgba(255,100,100,0.15)" }
235 | ```
236 |
237 | To make the highlighting invisible, set this and the background color properties to `transparent`.
238 |
239 |
240 | ### For power-users only!
241 |
242 | #### The matching pattern
243 |
244 | *Default: [ \t]+*
245 |
246 | Trailing spaces are line-ending regions containing at least one simple space, tabs, or both. This pattern should be all you ever need, but if you *do* want to abide by another definition to cover edge-cases, go ahead:
247 |
248 | ``` js
249 | // *danger* will match newline chars and many other folks
250 | "trailing-spaces.regexp": "[\\s]+"
251 | ```
252 |
253 | Contributions
254 | -------------
255 | - [@HookyQR](https://github.com/HookyQR): Fixed error while deleting last line of text [PR #9](https://github.com/shardulm94/vscode-trailingspaces/pull/9)
256 | - [@yustnip](https://github.com/yustnip): Added options to change background and border colors of highlighting [PR #17](https://github.com/shardulm94/vscode-trailingspaces/pull/17)
257 | - [@ameily](https://github.com/ameily): Properly trim spaces using the new TextEditor.edit() callback [PR #26](https://github.com/shardulm94/vscode-trailingspaces/pull/26)
258 | - [@mplellison](https://github.com/mplellison): Restructure tests for maintainability and ensure consecutive modified lines are trimmed [PR #44](https://github.com/shardulm94/vscode-trailingspaces/pull/44)
259 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shardulm94/vscode-trailingspaces/6e37905f7f16cdf533add3cbdf166c81b873237d/icon.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "trailing-spaces",
3 | "displayName": "Trailing Spaces",
4 | "description": "Highlight trailing spaces and delete them in a flash!",
5 | "version": "0.4.1",
6 | "publisher": "shardulm94",
7 | "icon": "icon.png",
8 | "engines": {
9 | "vscode": "^1.68.0"
10 | },
11 | "extensionKind": [
12 | "workspace"
13 | ],
14 | "categories": [
15 | "Other"
16 | ],
17 | "main": "./out/extension.js",
18 | "contributes": {
19 | "configuration": {
20 | "type": "object",
21 | "title": "Trailing Spaces Configuration",
22 | "properties": {
23 | "trailing-spaces.logLevel": {
24 | "type": "string",
25 | "enum": [
26 | "none",
27 | "log",
28 | "info",
29 | "warn",
30 | "error"
31 | ],
32 | "default": "warn",
33 | "description": "Controls the verbosity of logging. Logs can be seen in the console by opening the dev tools. `log` is the most verbose and `error` will only show critical errors."
34 | },
35 | "trailing-spaces.includeEmptyLines": {
36 | "type": "boolean",
37 | "default": true,
38 | "description": "By default, empty lines are cleared as well when calling the deletion command. Set to false to ignore empty lines upon deletion."
39 | },
40 | "trailing-spaces.highlightCurrentLine": {
41 | "type": "boolean",
42 | "default": true,
43 | "description": "By default, the lines being currently edited (i.e. with active cursors) will have its trailing spaces highlighted. Set to false to ignore trailing spaces on the current lines."
44 | },
45 | "trailing-spaces.regexp": {
46 | "type": "string",
47 | "default": "[ \t]+",
48 | "description": "By default, only simple spaces and tabs are matched as \"trailing spaces\"."
49 | },
50 | "trailing-spaces.liveMatching": {
51 | "type": "boolean",
52 | "default": true,
53 | "description": "By default, Trailing Spaces is \"live\". It means the trailing spaces regions will be matched in the background, and highlighted if a color scope is defined, when the document is opened and edited. Set to false to disable live matching and highlighting (the deletion command remains available, so-called \"lazy matching\")."
54 | },
55 | "trailing-spaces.deleteModifiedLinesOnly": {
56 | "type": "boolean",
57 | "default": false,
58 | "description": "By default, trailing spaces are deleted within the whole document. Set to true to affect only the lines you edited since last save. Trailing spaces will still be searched for and highlighted in the whole document."
59 | },
60 | "trailing-spaces.syntaxIgnore": {
61 | "type": "array",
62 | "default": [],
63 | "description": "With this option you can ignore specific files based on the syntax used. An item has to match the case-sensitive string of the language used in the file."
64 | },
65 | "trailing-spaces.schemeIgnore": {
66 | "type": "array",
67 | "default": [
68 | "output"
69 | ],
70 | "description": "With this option you can ignore specific schemes. An item has to match the case-sensitive string of the scheme of the document."
71 | },
72 | "trailing-spaces.trimOnSave": {
73 | "type": "boolean",
74 | "default": false,
75 | "description": "Controls whether trailing spaces are trimmed automatically when saving a file."
76 | },
77 | "trailing-spaces.showStatusBarMessage": {
78 | "type": "boolean",
79 | "default": true,
80 | "description": "By default, trailing space deletions will be communicated through a status bar message. Set to false to disable these messages."
81 | },
82 | "trailing-spaces.backgroundColor": {
83 | "type": "string",
84 | "default": "rgba(255,0,0,0.3)",
85 | "description": "Controls the background color of the trailing space decoration."
86 | },
87 | "trailing-spaces.borderColor": {
88 | "type": "string",
89 | "default": "rgba(255,100,100,0.15)",
90 | "description": "Controls the color of the border around the trailing space decoration."
91 | }
92 | }
93 | },
94 | "commands": [
95 | {
96 | "command": "trailing-spaces.deleteTrailingSpaces",
97 | "title": "Delete",
98 | "category": "Trailing Spaces"
99 | },
100 | {
101 | "command": "trailing-spaces.deleteTrailingSpacesModifiedLinesOnly",
102 | "title": "Delete - Modified Lines Only",
103 | "category": "Trailing Spaces"
104 | },
105 | {
106 | "command": "trailing-spaces.highlightTrailingSpaces",
107 | "title": "Highlight",
108 | "category": "Trailing Spaces"
109 | }
110 | ]
111 | },
112 | "activationEvents": [
113 | "onCommand:trailing-spaces.deleteTrailingSpaces",
114 | "onCommand:trailing-spaces.deleteTrailingSpacesModifiedLinesOnly",
115 | "onCommand:trailing-spaces.highlightTrailingSpaces",
116 | "onStartupFinished"
117 | ],
118 | "scripts": {
119 | "vscode:prepublish": "npm run compile",
120 | "compile": "tsc -p ./",
121 | "watch": "tsc -watch -p ./",
122 | "pretest": "npm run compile && npm run lint && mkdirp out/test/suite/files && ncp src/test/suite/files out/test/suite/files ",
123 | "lint": "eslint src --ext ts",
124 | "test": "node ./out/test/runTest.js"
125 | },
126 | "devDependencies": {
127 | "@types/vscode": "^1.68.0",
128 | "@types/glob": "^7.2.0",
129 | "@types/mocha": "^9.1.1",
130 | "@types/node": "16.x",
131 | "@types/diff": "^5.0.2",
132 | "@typescript-eslint/eslint-plugin": "^5.30.0",
133 | "@typescript-eslint/parser": "^5.30.0",
134 | "eslint": "^8.18.0",
135 | "glob": "^8.0.3",
136 | "ncp": "^2.0.0",
137 | "mkdirp": "^1.0.4",
138 | "mocha": "^10.0.0",
139 | "typescript": "^4.7.4",
140 | "@vscode/test-electron": "^2.1.5"
141 | },
142 | "dependencies": {
143 | "diff": "^5.0.2"
144 | },
145 | "repository": {
146 | "type": "git",
147 | "url": "https://github.com/shardulm94/vscode-trailingspaces.git"
148 | },
149 | "keywords": [
150 | "trailing",
151 | "spaces",
152 | "delete",
153 | "highlight"
154 | ],
155 | "author": "Shardul Mahadik",
156 | "license": "MIT",
157 | "bugs": {
158 | "url": "https://github.com/shardulm94/vscode-trailingspaces/issues"
159 | },
160 | "homepage": "https://github.com/shardulm94/vscode-trailingspaces#readme",
161 | "__metadata": {
162 | "id": "6ad45f5a-09ec-44e5-b363-867ddc1ec674",
163 | "publisherDisplayName": "Shardul Mahadik",
164 | "publisherId": "cdbc316c-f1de-43c4-9a22-4e211e955680",
165 | "isPreReleaseVersion": false
166 | }
167 | }
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import * as vscode from 'vscode';
4 | import TrailingSpacesLoader from './trailing-spaces/loader';
5 |
6 | export function activate(context: vscode.ExtensionContext) {
7 | let trailingSpacesLoader: TrailingSpacesLoader = new TrailingSpacesLoader();
8 | trailingSpacesLoader.activate(context.subscriptions);
9 | }
10 |
--------------------------------------------------------------------------------
/src/test/runTest.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 |
3 | import { runTests } from '@vscode/test-electron';
4 |
5 | async function main() {
6 | try {
7 | // The folder containing the Extension Manifest package.json
8 | // Passed to `--extensionDevelopmentPath`
9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../');
10 |
11 | // The path to test runner
12 | // Passed to --extensionTestsPath
13 | const extensionTestsPath = path.resolve(__dirname, './suite/index');
14 |
15 | // Download VS Code, unzip it and run the integration test
16 | await runTests({ extensionDevelopmentPath, extensionTestsPath });
17 | } catch (err) {
18 | console.error('Failed to run tests');
19 | process.exit(1);
20 | }
21 | }
22 |
23 | main();
24 |
--------------------------------------------------------------------------------
/src/test/suite/extension.test.ts:
--------------------------------------------------------------------------------
1 | //
2 | // Note: This example test is leveraging the Mocha test framework.
3 | // Please refer to their documentation on https://mochajs.org/ for help.
4 | //
5 |
6 | // The module 'assert' provides assertion methods from node
7 | import * as assert from 'assert';
8 |
9 | // You can import and use all API from the 'vscode' module
10 | // as well as import your extension to test it
11 | import * as vscode from 'vscode';
12 | import { Settings } from '../../trailing-spaces/settings';
13 | import * as path from 'path';
14 | import { Done, afterEach, describe, it } from 'mocha';
15 | import * as fs from 'fs';
16 |
17 | describe("Extension Tests", () => {
18 | let settings: Settings = Settings.getInstance();
19 |
20 | async function loadTestFileIntoEditor(testFileName: string, done: Done): Promise {
21 | let testFileUri: vscode.Uri = vscode.Uri.file(path.join(__dirname, testFileName));
22 |
23 | return vscode.workspace.openTextDocument(testFileUri)
24 | .then(
25 | (document: vscode.TextDocument) => vscode.window.showTextDocument(document),
26 | (reason: any) => done(reason)
27 | );
28 | }
29 |
30 | describe("testForDeleteTrailingSpaces", () => {
31 | it("should delete all trailing spaces", (done: Done) => {
32 | loadTestFileIntoEditor('./files/delete_all_trailing_spaces_sample.js', done)
33 | .then((testEditor: vscode.TextEditor) => {
34 | assertDeleteTrailingSpaces(testEditor, './files/delete_all_trailing_spaces_result.js', done);
35 | });
36 | });
37 |
38 | it("should not delete trailing spaces in empty lines", (done: Done) => {
39 | settings.includeEmptyLines = false;
40 | loadTestFileIntoEditor('./files/delete_trailing_spaces_exclude_empty_line_sample.js', done)
41 | .then((testEditor: vscode.TextEditor) => {
42 | assertDeleteTrailingSpaces(testEditor, './files/delete_trailing_spaces_exclude_empty_line_result.js', done);
43 | });
44 | });
45 |
46 | it("should delete but not highlight trailing spaces in the current line", (done: Done) => {
47 | settings.highlightCurrentLine = false;
48 | loadTestFileIntoEditor('./files/delete_trailing_spaces_exclude_current_line_highlight_sample.js', done)
49 | .then((testEditor: vscode.TextEditor) => {
50 | testEditor.selections = [new vscode.Selection(new vscode.Position(1, 3), new vscode.Position(1, 3))];
51 | assertDeleteTrailingSpaces(testEditor, './files/delete_trailing_spaces_exclude_current_line_highlight_result.js', done);
52 | });
53 | });
54 |
55 | it("should not delete trailing spaces in the current line if line is empty", (done: Done) => {
56 | settings.includeEmptyLines = false;
57 | loadTestFileIntoEditor('./files/delete_trailing_spaces_exclude_empty_line_when_exclude_current_line_highlight_sample.js', done)
58 | .then((testEditor: vscode.TextEditor) => {
59 | testEditor.selections = [new vscode.Selection(new vscode.Position(11, 3), new vscode.Position(11, 3))];
60 | assertDeleteTrailingSpaces(testEditor, './files/delete_trailing_spaces_exclude_empty_line_when_exclude_current_line_highlight_result.js', done);
61 | });
62 | });
63 |
64 | it("should not delete trailing spaces when language is set in syntaxIgnore", (done: Done) => {
65 | loadTestFileIntoEditor('./files/should_not_delete_spaces_sample.js', done)
66 | .then((testEditor: vscode.TextEditor) => {
67 | settings.languagesToIgnore[testEditor.document.languageId] = true;
68 | assertDeleteTrailingSpaces(testEditor, './files/should_not_delete_spaces_result.js', done);
69 | });
70 | });
71 |
72 | it("should not delete trailing spaces when file scheme is set in schemeIgnore", (done: Done) => {
73 | loadTestFileIntoEditor('./files/should_not_delete_spaces_sample.js', done)
74 | .then((testEditor: vscode.TextEditor) => {
75 | settings.schemesToIgnore[testEditor.document.uri.scheme] = true;
76 | assertDeleteTrailingSpaces(testEditor, './files/should_not_delete_spaces_result.js', done);
77 | });
78 | });
79 |
80 | it("should delete all trailing spaces including blank lines when regex is [\\s]+", (done: Done) => {
81 | loadTestFileIntoEditor('./files/delete_all_trailing_spaces_including_blank_lines_sample.js', done)
82 | .then((testEditor: vscode.TextEditor) => {
83 | settings.regexp = "[\\s]+";
84 | assertDeleteTrailingSpaces(testEditor, './files/delete_all_trailing_spaces_including_blank_lines_result.js', done);
85 | });
86 | });
87 |
88 | it("should only delete trailing spaces in modified lines only", (done: Done) => {
89 | loadTestFileIntoEditor('./files/delete_trailing_spaces_in_modified_lines_sample.js', done)
90 | .then((testEditor: vscode.TextEditor) => {
91 | settings.deleteModifiedLinesOnly = true;
92 | testEditor.edit((editBuilder: vscode.TextEditorEdit) => {
93 | editBuilder.insert(new vscode.Position(11, 2), "test");
94 | editBuilder.delete(new vscode.Range(1, 0, 1, 3));
95 | }).then((flag: boolean) => {
96 | assertDeleteTrailingSpaces(testEditor, './files/delete_trailing_spaces_in_modified_lines_result.js', done);
97 | });
98 | });
99 | });
100 |
101 | it("should delete trailing spaces in all consecutive modified lines only", (done: Done) => {
102 | loadTestFileIntoEditor('./files/delete_trailing_spaces_in_consecutive_modified_lines_sample.js', done)
103 | .then((testEditor: vscode.TextEditor) => {
104 | settings.deleteModifiedLinesOnly = true;
105 | testEditor.edit((editBuilder: vscode.TextEditorEdit) => {
106 | editBuilder.insert(new vscode.Position(11, 2), "test");
107 | editBuilder.insert(new vscode.Position(12, 2), "test");
108 | editBuilder.delete(new vscode.Range(1, 0, 1, 3));
109 | }).then((flag: boolean) => {
110 | assertDeleteTrailingSpaces(testEditor, './files/delete_trailing_spaces_in_consecutive_modified_lines_result.js', done);
111 | });
112 | });
113 | });
114 |
115 | it("should delete trailing spaces in newly inserted and modified lines only", (done: Done) => {
116 | loadTestFileIntoEditor('./files/delete_trailing_spaces_in_new_and_modified_lines_sample.js', done)
117 | .then((testEditor: vscode.TextEditor) => {
118 | settings.deleteModifiedLinesOnly = true;
119 | testEditor.edit((editBuilder: vscode.TextEditorEdit) => {
120 | editBuilder.insert(new vscode.Position(11, 2), "test");
121 | editBuilder.insert(new vscode.Position(12, 2), "test \n");
122 | editBuilder.delete(new vscode.Range(1, 0, 1, 3));
123 | }).then((flag: boolean) => {
124 | assertDeleteTrailingSpaces(testEditor, './files/delete_trailing_spaces_in_new_and_modified_lines_result.js', done);
125 | });
126 | });
127 | });
128 | });
129 |
130 | afterEach((done: Done) => {
131 | settings.resetToDefaults();
132 | vscode.commands.executeCommand("workbench.action.closeActiveEditor").then(() => done());
133 | });
134 |
135 | });
136 |
137 | let assertDeleteTrailingSpaces = (editor: vscode.TextEditor, expectedOutputFile: string, done: Done): void => {
138 | let outputFile: string = fs.readFileSync(path.join(__dirname, expectedOutputFile), "utf-8");
139 | vscode.commands.executeCommand("trailing-spaces.deleteTrailingSpaces").then(() => {
140 | try {
141 | assert.equal(editor.document.getText(), outputFile);
142 | } catch (err) {
143 | done(err);
144 | return;
145 | }
146 | done();
147 | return;
148 | });
149 | };
150 |
--------------------------------------------------------------------------------
/src/test/suite/files/delete_all_trailing_spaces_including_blank_lines_result.js:
--------------------------------------------------------------------------------
1 | // Line with 5 trailing spaces
2 | var a = "";
3 | // Line with no trailing spaces
4 | var b = "";
5 | // Line with leading spaces
6 | var c = "";
7 | // Line with leading and 3 trailing spaces
8 | var d = "";
9 | // Empty line with no trailing spaces
10 | // Empty line with trailing spaces
--------------------------------------------------------------------------------
/src/test/suite/files/delete_all_trailing_spaces_including_blank_lines_sample.js:
--------------------------------------------------------------------------------
1 | // Line with 5 trailing spaces
2 | var a = "";
3 | // Line with no trailing spaces
4 | var b = "";
5 | // Line with leading spaces
6 | var c = "";
7 | // Line with leading and 3 trailing spaces
8 | var d = "";
9 | // Empty line with no trailing spaces
10 |
11 | // Empty line with trailing spaces
12 |
--------------------------------------------------------------------------------
/src/test/suite/files/delete_all_trailing_spaces_result.js:
--------------------------------------------------------------------------------
1 | // Line with 5 trailing spaces
2 | var a = "";
3 | // Line with no trailing spaces
4 | var b = "";
5 | // Line with leading spaces
6 | var c = "";
7 | // Line with leading and 3 trailing spaces
8 | var d = "";
9 | // Empty line with no trailing spaces
10 |
11 | // Empty line with trailing spaces
12 |
--------------------------------------------------------------------------------
/src/test/suite/files/delete_all_trailing_spaces_sample.js:
--------------------------------------------------------------------------------
1 | // Line with 5 trailing spaces
2 | var a = "";
3 | // Line with no trailing spaces
4 | var b = "";
5 | // Line with leading spaces
6 | var c = "";
7 | // Line with leading and 3 trailing spaces
8 | var d = "";
9 | // Empty line with no trailing spaces
10 |
11 | // Empty line with trailing spaces
12 |
--------------------------------------------------------------------------------
/src/test/suite/files/delete_trailing_spaces_exclude_current_line_highlight_result.js:
--------------------------------------------------------------------------------
1 | // Line with 5 trailing spaces
2 | var a = "";
3 | // Line with no trailing spaces
4 | var b = "";
5 | // Line with leading spaces
6 | var c = "";
7 | // Line with leading and 3 trailing spaces
8 | var d = "";
9 | // Empty line with no trailing spaces
10 |
11 | // Empty line with trailing spaces
12 |
--------------------------------------------------------------------------------
/src/test/suite/files/delete_trailing_spaces_exclude_current_line_highlight_sample.js:
--------------------------------------------------------------------------------
1 | // Line with 5 trailing spaces
2 | var a = "";
3 | // Line with no trailing spaces
4 | var b = "";
5 | // Line with leading spaces
6 | var c = "";
7 | // Line with leading and 3 trailing spaces
8 | var d = "";
9 | // Empty line with no trailing spaces
10 |
11 | // Empty line with trailing spaces
12 |
--------------------------------------------------------------------------------
/src/test/suite/files/delete_trailing_spaces_exclude_empty_line_result.js:
--------------------------------------------------------------------------------
1 | // Line with 5 trailing spaces
2 | var a = "";
3 | // Line with no trailing spaces
4 | var b = "";
5 | // Line with leading spaces
6 | var c = "";
7 | // Line with leading and 3 trailing spaces
8 | var d = "";
9 | // Empty line with no trailing spaces
10 |
11 | // Empty line with trailing spaces
12 |
--------------------------------------------------------------------------------
/src/test/suite/files/delete_trailing_spaces_exclude_empty_line_sample.js:
--------------------------------------------------------------------------------
1 | // Line with 5 trailing spaces
2 | var a = "";
3 | // Line with no trailing spaces
4 | var b = "";
5 | // Line with leading spaces
6 | var c = "";
7 | // Line with leading and 3 trailing spaces
8 | var d = "";
9 | // Empty line with no trailing spaces
10 |
11 | // Empty line with trailing spaces
12 |
--------------------------------------------------------------------------------
/src/test/suite/files/delete_trailing_spaces_exclude_empty_line_when_exclude_current_line_highlight_result.js:
--------------------------------------------------------------------------------
1 | // Line with 5 trailing spaces
2 | var a = "";
3 | // Line with no trailing spaces
4 | var b = "";
5 | // Line with leading spaces
6 | var c = "";
7 | // Line with leading and 3 trailing spaces
8 | var d = "";
9 | // Empty line with no trailing spaces
10 |
11 | // Empty line with trailing spaces
12 |
--------------------------------------------------------------------------------
/src/test/suite/files/delete_trailing_spaces_exclude_empty_line_when_exclude_current_line_highlight_sample.js:
--------------------------------------------------------------------------------
1 | // Line with 5 trailing spaces
2 | var a = "";
3 | // Line with no trailing spaces
4 | var b = "";
5 | // Line with leading spaces
6 | var c = "";
7 | // Line with leading and 3 trailing spaces
8 | var d = "";
9 | // Empty line with no trailing spaces
10 |
11 | // Empty line with trailing spaces
12 |
--------------------------------------------------------------------------------
/src/test/suite/files/delete_trailing_spaces_in_consecutive_modified_lines_result.js:
--------------------------------------------------------------------------------
1 | // Line with 5 trailing spaces
2 | a = "";
3 | // Line with no trailing spaces
4 | var b = "";
5 | // Line with leading spaces
6 | var c = "";
7 | // Line with leading and 3 trailing spaces
8 | var d = "";
9 | // Empty line with no trailing spaces
10 |
11 | // Empty lines with trailing spaces
12 | test
13 | test
14 |
--------------------------------------------------------------------------------
/src/test/suite/files/delete_trailing_spaces_in_consecutive_modified_lines_sample.js:
--------------------------------------------------------------------------------
1 | // Line with 5 trailing spaces
2 | var a = "";
3 | // Line with no trailing spaces
4 | var b = "";
5 | // Line with leading spaces
6 | var c = "";
7 | // Line with leading and 3 trailing spaces
8 | var d = "";
9 | // Empty line with no trailing spaces
10 |
11 | // Empty lines with trailing spaces
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/test/suite/files/delete_trailing_spaces_in_modified_lines_result.js:
--------------------------------------------------------------------------------
1 | // Line with 5 trailing spaces
2 | a = "";
3 | // Line with no trailing spaces
4 | var b = "";
5 | // Line with leading spaces
6 | var c = "";
7 | // Line with leading and 3 trailing spaces
8 | var d = "";
9 | // Empty line with no trailing spaces
10 |
11 | // Empty line with trailing spaces
12 | test
--------------------------------------------------------------------------------
/src/test/suite/files/delete_trailing_spaces_in_modified_lines_sample.js:
--------------------------------------------------------------------------------
1 | // Line with 5 trailing spaces
2 | var a = "";
3 | // Line with no trailing spaces
4 | var b = "";
5 | // Line with leading spaces
6 | var c = "";
7 | // Line with leading and 3 trailing spaces
8 | var d = "";
9 | // Empty line with no trailing spaces
10 |
11 | // Empty line with trailing spaces
12 |
--------------------------------------------------------------------------------
/src/test/suite/files/delete_trailing_spaces_in_new_and_modified_lines_result.js:
--------------------------------------------------------------------------------
1 | // Line with 5 trailing spaces
2 | a = "";
3 | // Line with no trailing spaces
4 | var b = "";
5 | // Line with leading spaces
6 | var c = "";
7 | // Line with leading and 3 trailing spaces
8 | var d = "";
9 | // Empty line with no trailing spaces
10 |
11 | // Empty lines with trailing spaces
12 | test
13 | test
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/test/suite/files/delete_trailing_spaces_in_new_and_modified_lines_sample.js:
--------------------------------------------------------------------------------
1 | // Line with 5 trailing spaces
2 | var a = "";
3 | // Line with no trailing spaces
4 | var b = "";
5 | // Line with leading spaces
6 | var c = "";
7 | // Line with leading and 3 trailing spaces
8 | var d = "";
9 | // Empty line with no trailing spaces
10 |
11 | // Empty lines with trailing spaces
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/test/suite/files/should_not_delete_spaces_result.js:
--------------------------------------------------------------------------------
1 | // Line with 5 trailing spaces
2 | var a = "";
3 | // Line with no trailing spaces
4 | var b = "";
5 | // Line with leading spaces
6 | var c = "";
7 | // Line with leading and 3 trailing spaces
8 | var d = "";
9 | // Empty line with no trailing spaces
10 |
11 | // Empty line with trailing spaces
12 |
--------------------------------------------------------------------------------
/src/test/suite/files/should_not_delete_spaces_sample.js:
--------------------------------------------------------------------------------
1 | // Line with 5 trailing spaces
2 | var a = "";
3 | // Line with no trailing spaces
4 | var b = "";
5 | // Line with leading spaces
6 | var c = "";
7 | // Line with leading and 3 trailing spaces
8 | var d = "";
9 | // Empty line with no trailing spaces
10 |
11 | // Empty line with trailing spaces
12 |
--------------------------------------------------------------------------------
/src/test/suite/index.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import * as Mocha from 'mocha';
3 | import * as glob from 'glob';
4 |
5 | export function run(): Promise {
6 | // Create the mocha test
7 | const mocha = new Mocha({
8 | ui: 'tdd',
9 | color: true,
10 | timeout: 10000
11 | });
12 |
13 | const testsRoot = path.resolve(__dirname, '..');
14 |
15 | return new Promise((c, e) => {
16 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
17 | if (err) {
18 | return e(err);
19 | }
20 |
21 | // Add files to the test suite
22 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
23 |
24 | try {
25 | // Run the mocha test
26 | mocha.run(failures => {
27 | if (failures > 0) {
28 | e(new Error(`${failures} tests failed.`));
29 | } else {
30 | c();
31 | }
32 | });
33 | } catch (err) {
34 | console.error(err);
35 | e(err);
36 | }
37 | });
38 | });
39 | }
40 |
--------------------------------------------------------------------------------
/src/trailing-spaces/loader.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import * as vscode from 'vscode';
4 | import { ILogger, Logger } from './logger';
5 | import { Settings } from './settings';
6 | import { TrailingSpaces } from './trailing-spaces';
7 |
8 | export default class TrailingSpacesLoader {
9 |
10 | private logger: ILogger;
11 | private settings: Settings;
12 | private trailingSpaces: TrailingSpaces;
13 | private listenerDisposables: vscode.Disposable[] | undefined;
14 |
15 | constructor() {
16 | this.logger = Logger.getInstance();
17 | this.settings = Settings.getInstance();
18 | this.trailingSpaces = new TrailingSpaces();
19 | }
20 |
21 | public activate(subscriptions: vscode.Disposable[]): void {
22 | subscriptions.push(this);
23 | this.initialize(subscriptions);
24 | this.logger.log("Trailing Spaces activated.");
25 | }
26 |
27 | private initialize(subscriptions: vscode.Disposable[]): void {
28 | vscode.workspace.onDidChangeConfiguration(this.reinitialize, this, subscriptions);
29 | this.registerCommands(subscriptions);
30 | this.registerEventListeners();
31 | this.highlightActiveEditors();
32 | }
33 |
34 | private reinitialize(): void {
35 | this.dispose();
36 | this.settings.refreshSettings();
37 | this.registerEventListeners();
38 | this.highlightActiveEditors();
39 | }
40 |
41 | private registerCommands(subscriptions: vscode.Disposable[]): void {
42 | subscriptions.push(
43 | vscode.commands.registerTextEditorCommand('trailing-spaces.deleteTrailingSpaces', this.trailingSpaces.delete, this.trailingSpaces),
44 | vscode.commands.registerTextEditorCommand('trailing-spaces.deleteTrailingSpacesModifiedLinesOnly', this.trailingSpaces.deleteModifiedLinesOnly, this.trailingSpaces),
45 | vscode.commands.registerTextEditorCommand('trailing-spaces.highlightTrailingSpaces', this.trailingSpaces.highlight, this.trailingSpaces)
46 | );
47 | }
48 |
49 | private registerEventListeners(): void {
50 | let disposables: vscode.Disposable[] = [];
51 | if (this.settings.liveMatching) {
52 | disposables.push(
53 | vscode.window.onDidChangeActiveTextEditor((editor: vscode.TextEditor | undefined) => {
54 | if (editor !== undefined) {
55 | this.logger.log(`onDidChangeActiveTextEditor event called - ${editor.document.fileName}`);
56 | this.trailingSpaces.highlight(editor);
57 | }
58 | }),
59 | vscode.workspace.onDidChangeTextDocument((event: vscode.TextDocumentChangeEvent) => {
60 | if (vscode.window.activeTextEditor !== undefined && vscode.window.activeTextEditor.document === event.document) {
61 | this.logger.log(`onDidChangeTextDocument event called - ${event.document.fileName}`);
62 | this.trailingSpaces.highlight(vscode.window.activeTextEditor);
63 | }
64 | }),
65 | vscode.workspace.onDidOpenTextDocument((document: vscode.TextDocument) => {
66 | if (vscode.window.activeTextEditor !== undefined && vscode.window.activeTextEditor.document === document) {
67 | this.logger.log(`onDidOpenTextDocument event called - ${document.fileName}`);
68 | this.trailingSpaces.highlight(vscode.window.activeTextEditor);
69 | }
70 | })
71 | );
72 |
73 | if (!this.settings.highlightCurrentLine) {
74 | disposables.push(
75 | vscode.window.onDidChangeTextEditorSelection((event: vscode.TextEditorSelectionChangeEvent) => {
76 | let editor: vscode.TextEditor = event.textEditor;
77 | this.logger.log(`onDidChangeTextEditorSelection event called - ${editor.document.fileName}`);
78 | this.trailingSpaces.highlight(editor);
79 | })
80 | );
81 | }
82 | }
83 |
84 | if (this.settings.trimOnSave) {
85 | disposables.push(
86 | vscode.workspace.onWillSaveTextDocument((event: vscode.TextDocumentWillSaveEvent) => {
87 | this.logger.log(`onWillSaveTextDocument event called - ${event.document.fileName}`);
88 | vscode.window.visibleTextEditors.forEach((editor: vscode.TextEditor) => {
89 | if (event.document.uri === editor.document.uri) {
90 | event.waitUntil(Promise.resolve(this.trailingSpaces.getEditsForDeletingTralingSpaces(editor.document)));
91 | }
92 | });
93 | })
94 | );
95 | }
96 | this.listenerDisposables = disposables;
97 | }
98 |
99 | private highlightActiveEditors(): void {
100 | if (this.settings.liveMatching) {
101 | vscode.window.visibleTextEditors.forEach((editor: vscode.TextEditor) => {
102 | this.trailingSpaces.highlight(editor);
103 | });
104 | this.logger.info("All visible text editors highlighted");
105 | }
106 | }
107 |
108 | public dispose(): void {
109 | if (this.listenerDisposables !== undefined) {
110 | this.listenerDisposables.forEach(disposable => {
111 | disposable.dispose();
112 | });
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/trailing-spaces/logger.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { window } from 'vscode';
4 |
5 | export enum LogLevel {
6 | none,
7 | error,
8 | warn,
9 | info,
10 | log
11 | }
12 |
13 | export interface ILogger {
14 | setLogLevel(level: LogLevel): void;
15 | setPrefix(prefix: string): void;
16 | error(message: string): void;
17 | warn(message: string): void;
18 | log(message: string): void;
19 | info(message: string): void;
20 | }
21 |
22 | export class Logger implements ILogger {
23 |
24 | private static instance: Logger = new Logger();
25 | private level!: LogLevel;
26 | private prefix!: string;
27 |
28 | public constructor(prefix?: string, level?: LogLevel) {
29 | if (!Logger.instance) {
30 | Logger.instance = this;
31 | this.prefix = prefix || 'LOGGER';
32 | this.level = level || LogLevel.error;
33 | }
34 | }
35 |
36 | public static getInstance(): Logger {
37 | return Logger.instance;
38 | }
39 |
40 | public setPrefix(prefix: string): void {
41 | this.prefix = prefix;
42 | }
43 |
44 | public setLogLevel(level: LogLevel): void {
45 | this.level = level;
46 | }
47 |
48 | public log(message: string): void {
49 | if (this.level >= LogLevel.log) {
50 | console.log(`${this.prefix} - ${LogLevel[LogLevel.log]} - ${message}`);
51 | }
52 | }
53 |
54 | public info(message: string): void {
55 | if (this.level >= LogLevel.info) {
56 | console.info(`${this.prefix} - ${LogLevel[LogLevel.info]} - ${message}`);
57 | }
58 | }
59 |
60 | public warn(message: string): void {
61 | if (this.level >= LogLevel.warn) {
62 | console.warn(`${this.prefix} - ${LogLevel[LogLevel.warn]} - ${message}`);
63 | }
64 | }
65 |
66 | public error(message: string): void {
67 | if (this.level >= LogLevel.error) {
68 | console.error(`${this.prefix} - ${LogLevel[LogLevel.error]} - ${message}`);
69 | window.showErrorMessage(message);
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/trailing-spaces/settings.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import * as vscode from 'vscode';
4 | import { LogLevel, ILogger, Logger } from './logger';
5 |
6 | export interface TrailingSpacesSettings {
7 | logLevel: LogLevel,
8 | includeEmptyLines: boolean,
9 | highlightCurrentLine: boolean,
10 | regexp: string,
11 | liveMatching: boolean,
12 | deleteModifiedLinesOnly: boolean,
13 | languagesToIgnore: { [id: string]: boolean; },
14 | schemesToIgnore: { [id: string]: boolean; },
15 | trimOnSave: boolean,
16 | showStatusBarMessage: boolean,
17 | textEditorDecorationType: vscode.TextEditorDecorationType
18 | }
19 |
20 | export class Settings implements TrailingSpacesSettings {
21 |
22 | private static instance: Settings = new Settings();
23 | private logger!: ILogger;
24 |
25 | logLevel!: LogLevel;
26 | includeEmptyLines!: boolean;
27 | highlightCurrentLine!: boolean;
28 | regexp!: string;
29 | liveMatching!: boolean;
30 | deleteModifiedLinesOnly!: boolean;
31 | languagesToIgnore!: { [id: string]: boolean; };
32 | schemesToIgnore!: { [id: string]: boolean; };
33 | trimOnSave!: boolean;
34 | showStatusBarMessage!: boolean;
35 | backgroundColor!: string;
36 | borderColor!: string;
37 | textEditorDecorationType!: vscode.TextEditorDecorationType;
38 |
39 | constructor() {
40 | if (!Settings.instance) {
41 | Settings.instance = this;
42 | this.logger = Logger.getInstance();
43 | this.refreshSettings();
44 | }
45 | }
46 |
47 | public static getInstance(): Settings {
48 | return Settings.instance;
49 | }
50 |
51 | public refreshSettings(): void {
52 | let config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration('trailing-spaces');
53 | this.logLevel = LogLevel[this.getOrError(config, 'logLevel')];
54 | this.includeEmptyLines = this.getOrError(config, 'includeEmptyLines');
55 | this.highlightCurrentLine = this.getOrError(config, 'highlightCurrentLine');
56 | this.regexp = this.getOrError(config, 'regexp');
57 | this.liveMatching = this.getOrError(config, 'liveMatching');
58 | this.deleteModifiedLinesOnly = this.getOrError(config, 'deleteModifiedLinesOnly');
59 | this.languagesToIgnore = this.getMapFromStringArray(this.getOrError(config, 'syntaxIgnore'));
60 | this.schemesToIgnore = this.getMapFromStringArray(this.getOrError(config, 'schemeIgnore'));
61 | this.trimOnSave = this.getOrError(config, 'trimOnSave');
62 | this.showStatusBarMessage = this.getOrError(config, 'showStatusBarMessage');
63 | this.setTextEditorDecorationType(config);
64 | this.logger.setLogLevel(this.logLevel);
65 | this.logger.setPrefix('Trailing Spaces');
66 | this.logger.log('Configuration loaded');
67 | }
68 |
69 | public resetToDefaults(): void {
70 | let config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration('trailing-spaces');
71 | config.update('logLevel', undefined, true);
72 | config.update('includeEmptyLines', undefined, true);
73 | config.update('highlightCurrentLine', undefined, true);
74 | config.update('regexp', undefined, true);
75 | config.update('liveMatching', undefined, true);
76 | config.update('deleteModifiedLinesOnly', undefined, true);
77 | config.update('syntaxIgnore', undefined, true);
78 | config.update('schemeIgnore', undefined, true);
79 | config.update('trimOnSave', undefined, true);
80 | config.update('showStatusBarMessage', undefined, true);
81 | config.update('backgroundColor', undefined, true);
82 | config.update('borderColor', undefined, true);
83 | this.refreshSettings();
84 | }
85 |
86 | private getMapFromStringArray(array: string[]): { [id: string]: boolean; } {
87 | let map: { [id: string]: boolean; } = {};
88 | array.forEach((element: string) => {
89 | map[element] = true;
90 | });
91 | return map;
92 | }
93 |
94 | private setTextEditorDecorationType(config: vscode.WorkspaceConfiguration): void {
95 | let newBackgroundColor = this.getOrError(config, 'backgroundColor');
96 | let newBorderColor = this.getOrError(config, 'borderColor');
97 |
98 | if (newBackgroundColor !== this.backgroundColor || newBorderColor !== this.borderColor) {
99 | this.backgroundColor = newBackgroundColor;
100 | this.borderColor = newBorderColor;
101 | if (this.textEditorDecorationType) {
102 | // if an old decoration already exists, dispose it prior to creating a new one
103 | this.textEditorDecorationType.dispose();
104 | }
105 | this.textEditorDecorationType = vscode.window.createTextEditorDecorationType({
106 | borderRadius: "3px",
107 | borderWidth: "1px",
108 | borderStyle: "solid",
109 | backgroundColor: newBackgroundColor,
110 | borderColor: newBorderColor
111 | });
112 | }
113 | }
114 |
115 | private getOrError(config: vscode.WorkspaceConfiguration, key: string): T {
116 | let value = config.get(key);
117 | if (value === undefined) {
118 | throw new Error(`Did not expect undefined config: ${key}`);
119 | } else {
120 | return value;
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/trailing-spaces/trailing-spaces.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import * as vscode from 'vscode';
4 | import { ILogger, Logger } from './logger';
5 | import { Settings, TrailingSpacesSettings } from './settings';
6 | import * as utils from './utils';
7 | import fs = require('fs');
8 | import { isNullOrUndefined } from 'util';
9 |
10 |
11 | export class TrailingSpaces {
12 | private logger: ILogger;
13 | private settings: TrailingSpacesSettings;
14 |
15 | constructor() {
16 | this.logger = Logger.getInstance();
17 | this.settings = Settings.getInstance();
18 | }
19 |
20 | public highlight(editor: vscode.TextEditor, editorEdit: vscode.TextEditorEdit | undefined = undefined): void {
21 | this.highlightTrailingSpaces(editor);
22 | }
23 |
24 | public delete(editor: vscode.TextEditor, editorEdit: vscode.TextEditorEdit): void {
25 | this.deleteTrailingSpaces(editor, editorEdit);
26 | }
27 |
28 | public deleteModifiedLinesOnly(editor: vscode.TextEditor, editorEdit: vscode.TextEditorEdit): void {
29 | this.deleteTrailingSpaces(editor, editorEdit, true);
30 | }
31 |
32 | /**
33 | * Highlights the trailing spaces in the current editor.
34 | *
35 | * @private
36 | * @param {vscode.TextEditor} editor The editor in which the spaces have to be highlighted
37 | */
38 | private highlightTrailingSpaces(editor: vscode.TextEditor): void {
39 | editor.setDecorations(this.settings.textEditorDecorationType, this.getRangesToHighlight(editor.document, editor.selections));
40 | }
41 |
42 | /**
43 | * Deletes the trailing spaces in the current editor.
44 | *
45 | * @private
46 | * @param {vscode.TextEditor} editor The editor in which the spaces have to be deleted
47 | * @param {vscode.TextEditorEdit} editBuilder The edit builders for apply deletions
48 | * @param {boolean} deleteModifiedLinesOnlyOverride Whether to only deleted modified lines regardless of the settings
49 | */
50 | private deleteTrailingSpaces(editor: vscode.TextEditor, editBuilder: vscode.TextEditorEdit, deleteModifiedLinesOnlyOverride: boolean = false): void {
51 | let ranges: vscode.Range[] = this.getRangesToDelete(editor.document, deleteModifiedLinesOnlyOverride);
52 | for (let i: number = ranges.length - 1; i >= 0; i--) {
53 | editBuilder.delete(ranges[i]);
54 | }
55 | this.showStatusBarMessage(editor.document, ranges.length, true);
56 | }
57 |
58 | /**
59 | * Returns the edits required to delete the trailings spaces from a document
60 | *
61 | * @param {vscode.TextDocument} document The document in which the trailing spaces should be found
62 | * @returns {vscode.TextEdit[]} An array of edits required to delete the trailings spaces from the document
63 | */
64 | public getEditsForDeletingTralingSpaces(document: vscode.TextDocument): vscode.TextEdit[] {
65 | let ranges: vscode.Range[] = this.getRangesToDelete(document);
66 | let edits: vscode.TextEdit[] = new Array(ranges.length);
67 | for (let i: number = ranges.length - 1; i >= 0; i--) {
68 | edits[ranges.length - 1 - i] = vscode.TextEdit.delete(ranges[i]);
69 | }
70 | this.showStatusBarMessage(document, ranges.length);
71 | return edits;
72 | }
73 |
74 | /**
75 | * Displays a status bar message containing the number of trailing space regions deleted
76 | *
77 | * @private
78 | * @param {vscode.TextDocument} document The document for which the message has to be shown
79 | * @param {number} numRegions Number of trailing space regions found
80 | * @param {boolean} showIfNoRegions Should the message be shown even if no regions are founds
81 | */
82 | private showStatusBarMessage(document: vscode.TextDocument, numRegions: number, showIfNoRegions: boolean = false): void {
83 | let message: string;
84 | if (numRegions > 0) {
85 | message = `Deleting ${numRegions} trailing space region${(numRegions > 1 ? "s" : "")}`;
86 | } else {
87 | message = "No trailing spaces to delete!";
88 | }
89 | this.logger.info(message + " - " + document.fileName);
90 | if (this.settings.showStatusBarMessage) {
91 | if (numRegions > 0 || showIfNoRegions) {
92 | vscode.window.setStatusBarMessage(message, 3000);
93 | }
94 | }
95 | }
96 |
97 | /**
98 | * Gets trailing spaces ranges which have to be highlighted.
99 | *
100 | * @private
101 | * @param {vscode.TextDocument} document The document in which the trailing spaces should be found
102 | * @param {vscode.Selection[]} selections The current selections inside the editor
103 | * @returns {vscode.Range[]} An array of ranges containing the trailing spaces
104 | */
105 | private getRangesToHighlight(document: vscode.TextDocument, selections: readonly vscode.Selection[]): vscode.Range[] {
106 | let ranges: vscode.Range[] = this.findTrailingSpaces(document);
107 |
108 | if (!this.settings.highlightCurrentLine) {
109 | let currentLines = selections.map((selection: vscode.Selection) => {
110 | return document.lineAt(document.validatePosition(selection.active));
111 | });
112 |
113 | ranges = ranges.filter(range => {
114 | // range should not intersect with any of our "current" lines
115 | return !currentLines.some((line: vscode.TextLine) => {
116 | return range.intersection(line.range) !== undefined;
117 | });
118 | });
119 | }
120 |
121 | return ranges;
122 | }
123 |
124 | /**
125 | * Gets trailing spaces ranges which have to be deleted.
126 | *
127 | * @private
128 | * @param {vscode.TextDocument} document The document in which the trailing spaces should be found
129 | * @param {boolean} deleteModifiedLinesOnlyOverride Whether to delete only modified lines regardless of the settings
130 | * @returns {vscode.Range[]} An array of ranges containing the trailing spaces
131 | */
132 | private getRangesToDelete(document: vscode.TextDocument, deleteModifiedLinesOnlyOverride: boolean = false): vscode.Range[] {
133 | let ranges: vscode.Range[] = this.findTrailingSpaces(document);
134 |
135 | // If deleteModifiedLinesOnly is set, filter out the ranges contained in the non-modified lines
136 | if ((this.settings.deleteModifiedLinesOnly || deleteModifiedLinesOnlyOverride)
137 | && !document.isUntitled && document.uri.scheme === "file") {
138 | let modifiedLines: Set = utils.getModifiedLineNumbers(fs.readFileSync(document.uri.fsPath, "utf-8"), document.getText());
139 | ranges = ranges.filter((range: vscode.Range) => {
140 | return (modifiedLines.has(range.start.line));
141 | });
142 | }
143 | return ranges;
144 | }
145 |
146 | /**
147 | * Finds all ranges in the document which contain trailing spaces
148 | *
149 | * @private
150 | * @param {vscode.TextDocument} document The document in which the trailing spaces should be found
151 | * @returns {vscode.Range[]} An array of ranges containing the trailing spaces
152 | */
153 | private findTrailingSpaces(document: vscode.TextDocument): vscode.Range[] {
154 | if (this.ignoreDocument(document.languageId, document.uri.scheme)) {
155 | this.logger.info(`File with langauge '${document.languageId}' and scheme '${document.uri.scheme}' ignored - ${document.fileName}`);
156 | return [];
157 | } else {
158 | let offendingRanges: vscode.Range[] = [];
159 | let regexp: string = "(" + this.settings.regexp + ")$";
160 | let noEmptyLinesRegexp: string = "\\S" + regexp;
161 | let offendingRangesRegexp: RegExp = new RegExp(this.settings.includeEmptyLines ? regexp : noEmptyLinesRegexp, "gm");
162 | let documentText: string = document.getText();
163 |
164 | let match: RegExpExecArray | null;
165 | // Loop through all the trailing spaces in the document
166 | while ((match = offendingRangesRegexp.exec(documentText)) !== null) {
167 | let matchStart: number = (match.index + match[0].length - match[1].length),
168 | matchEnd: number = match.index + match[0].length;
169 | let matchRange: vscode.Range = new vscode.Range(document.positionAt(matchStart), document.positionAt(matchEnd));
170 | // Ignore ranges which are empty (only containing a single line ending)
171 | if (!matchRange.isEmpty) {
172 | offendingRanges.push(matchRange);
173 | }
174 | }
175 | return offendingRanges;
176 | }
177 | }
178 |
179 | /**
180 | * Checks if the language or the scheme of the document is set to be ignored.
181 | *
182 | * @private
183 | * @param {string} language The language of the document to be checked
184 | * @param {string} scheme The scheme of the document to be checked
185 | * @returns {boolean} A boolean indicating if the document needs to be ignored
186 | */
187 | private ignoreDocument(language: string, scheme: string): boolean {
188 | return (!isNullOrUndefined(language) && this.settings.languagesToIgnore[language]
189 | || !isNullOrUndefined(scheme) && this.settings.schemesToIgnore[scheme]);
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/src/trailing-spaces/utils.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import jsdiff = require('diff');
4 |
5 | /**
6 | * Gets numbers of all the lines which have changed between the two strings.
7 | *
8 | * @export
9 | * @param {string} oldFile A string representing the old version of the file
10 | * @param {string} newFile A string representing the new version of the file
11 | * @returns {Set} A set containing all line numbers which have been modified
12 | */
13 | export function getModifiedLineNumbers(oldFile: string, newFile: string): Set {
14 | let diffs: jsdiff.Change[] = jsdiff.diffLines(oldFile, newFile);
15 | let lineNumber: number = 0;
16 | let editedLines: Set = new Set();
17 | diffs.forEach(diff => {
18 | if (diff.added) {
19 | if (diff.count) {
20 | for (let i = 0; i < diff.count; i++) {
21 | editedLines.add(lineNumber + i);
22 | }
23 | } else {
24 | editedLines.add(lineNumber);
25 | }
26 | }
27 | if (!diff.removed && diff.count) {
28 | lineNumber += diff.count;
29 | }
30 | });
31 | return editedLines;
32 | }
33 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "ES2020",
5 | "outDir": "out",
6 | "lib": [
7 | "ES2020"
8 | ],
9 | "sourceMap": true,
10 | "rootDir": "src",
11 | "strict": true /* enable all strict type-checking options */
12 | /* Additional Checks */
13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
16 | }
17 | }
18 |
--------------------------------------------------------------------------------