├── .github
└── stale.yml
├── .gitignore
├── .prettierrc.yaml
├── .travis.yml
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── keymaps
└── markdown-table-formatter.cson
├── lib
├── config.js
├── formatTable.js
├── markdown-table-formatter.js
├── regex.js
└── table-formatter.js
├── menus
└── markdown-table-formatter.cson
├── package.json
├── settings.png
├── spec
├── atom.d.ts
├── duplicate-tests.spec.ts
├── editor.spec.ts
├── fixtures
│ ├── empty.md
│ ├── empty.text
│ ├── test-tables-pref-line-len.md
│ └── test-tables.md
├── format.spec.ts
├── global.d.ts
├── markdown-table-formatter.spec.ts
├── regex.spec.ts
├── tables
│ ├── non-tables.ts
│ └── test-tables-default.ts
├── tsconfig.json
├── tslint.json
└── utils.ts
├── src
├── atom.d.ts
├── config.ts
├── formatTable.ts
├── markdown-table-formatter.ts
├── regex.ts
└── table-formatter.ts
├── tsconfig.json
└── tslint.json
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 90
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 7
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - pinned
8 | - security
9 | - enhancement
10 | # Label to use when marking an issue as stale
11 | staleLabel: stale
12 | # Comment to post when marking an issue as stale. Set to `false` to disable
13 | markComment: >
14 | This issue has been automatically marked as stale because it has not had
15 | recent activity. It will be closed if no further activity occurs. Thank you
16 | for your contributions.
17 | # Comment to post when closing a stale issue. Set to `false` to disable
18 | closeComment: false
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | npm-debug.log
3 | node_modules
4 |
--------------------------------------------------------------------------------
/.prettierrc.yaml:
--------------------------------------------------------------------------------
1 | # .prettierrc
2 | semi: false
3 | singleQuote: true
4 | trailingComma: 'all'
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | ### Project specific config ###
2 | language: generic
3 |
4 | env:
5 | global:
6 | - APM_TEST_PACKAGES=""
7 | - ATOM_LINT_WITH_BUNDLED_NODE="true"
8 |
9 | matrix:
10 | - ATOM_CHANNEL=stable
11 | # - ATOM_CHANNEL=beta
12 |
13 | os:
14 | - linux
15 | # - osx
16 |
17 | ### Generic setup follows ###
18 | script:
19 | - curl -s -O https://raw.githubusercontent.com/atom/ci/master/build-package.sh
20 | - chmod u+x build-package.sh
21 | - ./build-package.sh
22 | - npm test
23 |
24 | notifications:
25 | email:
26 | on_success: never
27 | on_failure: change
28 |
29 | branches:
30 | only:
31 | - master
32 |
33 | git:
34 | depth: 10
35 |
36 | sudo: false
37 |
38 | dist: trusty
39 |
40 | addons:
41 | apt:
42 | packages:
43 | - build-essential
44 | - fakeroot
45 | - git
46 | - libsecret-1-dev
47 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 2.9.2
2 | * Fix #26 (CRLF breaks whole-document formatting)
3 | * Add tests for CRLF (#26)
4 |
5 | ## 2.9.1
6 | * [Fix] #44 - Events not triggering
7 |
8 | ## 2.9.0
9 | * [Fix] Do not EVER contract selection ranges
10 | * [Change] Format selection for non-Markdown scopes
11 | * [New] Command to add current grammar to markdownGrammarScopes
12 |
13 | ## 2.8.4
14 | * Added limitLastColumnPadding setting to generate less padding for tables where the last column runs long
15 |
16 | ## 2.8.3
17 | * [Fix] #30 regression
18 |
19 | ## 2.8.2
20 | * [Fix] \_this.getModel is not a function
21 |
22 | ## 2.8.1
23 | * [Change] Safeguard in cases that the editor was not instantiated
24 |
25 | ## 2.8.0
26 | * [New] Added the option to choose in which grammars the plugin run
27 | * [Fix] Changed if test to the check scope names
28 | * [Change] Updated settings image
29 |
30 | ## 2.7.2
31 | * Bugfixes (closes #23, #24)
32 |
33 | ## 2.7.1
34 | * Bugfix for misinterpretation of default table Justification
35 |
36 | ## 2.6.0
37 | * Added the option to set the default justification for tables that have only a \'-\' on the formatting line
38 |
39 | ## 2.5.5
40 | * Added some usage information to the README
41 |
42 | ## 2.5.4
43 | * Tests code cleanup
44 | * Raise minimal compatible Atom version to 1.0.0
45 | * Wrap all changes into a single undo transaction
46 |
47 | ## 2.5.3
48 | * Ignore common zero-width unicode characters for cell width (#17)
49 |
50 | ## 2.5.2
51 | * Bogus release (no changes)
52 |
53 | ## 2.5.1
54 | * Fix #16
55 |
56 | ## 2.5.0
57 |
58 | * Removed unnecessary comments
59 | * Code format
60 | * Add possible EOF at table body end will allow for capturing tables that are at end of file w/o trailing newline
61 | * Extra newline removed
62 | * Fix typo
63 | * Add testcases for Chinese and Russian tables
64 | * Use wcwidth
65 | * Small regex format update
66 | * Extend selections to whole tables
67 | * Only auto-select document if all ranges are empty
68 | * Only extend ranges when not empty or not ASED (auto-select entire document
69 |
70 |
71 | ## 2.4.2
72 | * Update specs to check more cases
73 | * Put all extra content into last cell
74 | * Correct CJK character width counting
75 |
76 |
77 | ## 2.4.1
78 |
79 | * Fix #11
80 | * Settings are now handled via fields to facilitate easier testing
81 | * Update specs
82 |
83 |
84 | ## 2.4.0
85 |
86 | * Fixed stray `if` from #7
87 |
88 | I'm very sorry for this bug. I didn't tested because today was my first day at work after my vacation. Things were a little crazy. I promisse I'll be more careful from now on.
89 |
90 |
91 | ## 2.3.0
92 |
93 | * Merged changes from #7 and #8
94 | * **Now the package is ready for Atom API 1.0**
95 |
96 |
97 | ## 2.2.0
98 |
99 | * Merge branch 'lierdakil-newregex' lierdakil@08374da6c9ec2bb35d35bc666c0408929448fe37
100 |
101 |
102 | ## 2.1.1
103 |
104 | * Fix for bug #3 "Insertion Point Lost On Save"
105 |
106 |
107 | ## 2.1.0
108 |
109 | * Fix for keeping document auto selected
110 |
111 |
112 | ## 2.0.1
113 |
114 | * Fix a regression where a table wouldn't be recognized
115 |
116 |
117 | ## 2.0.0
118 |
119 | * Added option to choose if first and last pipes should be kept
120 | * Added option to choose the padding of the cells
121 | * Added support for latest config api
122 | * Added specs to package
123 | * Changed regex to identify pipeless tables
124 | * Changed requirements to atom >= 0.135.0
125 | * Removed deprecated APIs to observe text editors
126 |
127 |
128 | ## 1.0.2
129 |
130 | * Sorry to anyone using my package, had some troubles with git and remote tags. Hope this fixes everything.
131 |
132 |
133 | ## 1.0.1
134 |
135 | * Added option to restore selections (default: false)
136 | * Fixed incorrect trimming of selected tables
137 | * Auto-select entire document if selection is empty (default: true)
138 | * Added a fix in the regex to consider spaces at the end of each line
139 |
140 |
141 | ## 0.1.2
142 |
143 | * Bug fixes
144 |
145 |
146 | ## 0.1.1
147 |
148 | * Bug fixes
149 |
150 |
151 | ## 0.1.0
152 |
153 | * Every feature added
154 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Fernando Xavier de Freitas Crespo
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Markdown Table Formatter
2 |
3 | A simple markdown plugin to format tables.
4 |
5 | Based on the awesome [Improved Markdown table commands for TextMate](http://www.leancrew.com/all-this/2012/03/improved-markdown-table-commands-for-textmate/) work from [Dr. Drang (@drdrang)](https://twitter.com/drdrang)
6 |
7 | [Changelog](https://github.com/fcrespo82/atom-markdown-table-formatter/blob/master/CHANGELOG.md)
8 |
9 |
10 | ## Usage
11 |
12 | There are two basic ways of using this plugin.
13 |
14 | 1. Select the table you want to format and then hit `alt-shift-T` to format it.
15 | 2. If you didn't select any table the entire document (default) will be scanned, by the plugin, for tables and format all of them.
16 |
17 | ### Settings & Keybindings
18 |
19 | 
20 |
21 | ## Tips
22 |
23 | ### Using with `language-markdown` package
24 |
25 | Since `language-markdown` is a community package, auto-formatting entire document is not supported out-of-the box, but that is easy to fix, see next tip.
26 |
27 | ### Enable Markdown Table Formatter for the current file type
28 |
29 | To enable Markdown Table Formatter for your current file type: put your cursor in the file, open the Command Palette ⌘ (CMD)+⇧ (SHIFT)+P, and run the `Markdown Table Formatter: Enable For Current Scope` command. This will add grammar scope from current editor to the list of scopes in the settings for the Markdown Table Formatter package. You can edit this setting manually later if you want to.
30 |
31 | Formatting selection should work regardless though.
32 |
33 | ### How to style the tables correctly if you use non-monospace fonts
34 |
35 | Pre Atom-1.13:
36 |
37 | ```less
38 | atom-text-editor::shadow .table.gfm {
39 | font-family: monospace;
40 | }
41 | ```
42 |
43 | Post Atom-1.13:
44 |
45 | ```less
46 | atom-text-editor .syntax--table.syntax--gfm {
47 | font-family: monospace;
48 | }
49 | ```
50 |
--------------------------------------------------------------------------------
/keymaps/markdown-table-formatter.cson:
--------------------------------------------------------------------------------
1 | # Keybindings require three things to be fully defined: A selector that is
2 | # matched against the focused element, the keystroke and the command to
3 | # execute.
4 | #
5 | # Below is a basic keybinding which registers on all platforms by applying to
6 | # the root workspace element.
7 |
8 | # For more detailed documentation see
9 | # https://atom.io/docs/latest/advanced/keymaps
10 | 'atom-text-editor':
11 | 'alt-shift-t': 'markdown-table-formatter:format'
12 |
--------------------------------------------------------------------------------
/lib/config.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.config = {
4 | formatOnSave: {
5 | type: 'boolean',
6 | default: true,
7 | description: 'Format tables when document is saved?',
8 | },
9 | autoSelectEntireDocument: {
10 | type: 'boolean',
11 | default: true,
12 | description: 'Select entire document if selection is empty',
13 | },
14 | spacePadding: {
15 | type: 'integer',
16 | default: 1,
17 | description: 'How many spaces between left and right of each column content',
18 | },
19 | keepFirstAndLastPipes: {
20 | type: 'boolean',
21 | default: true,
22 | description: `Keep first and last pipes "|" in table formatting. \
23 | Tables are easier to format when pipes are kept`,
24 | },
25 | defaultTableJustification: {
26 | type: 'string',
27 | default: 'Left',
28 | enum: ['Left', 'Center', 'Right'],
29 | description: `Defines the default justification for tables that have only a \
30 | \'-\' on the formatting line`,
31 | },
32 | markdownGrammarScopes: {
33 | type: 'array',
34 | default: ['source.gfm'],
35 | description: `File grammar scopes that will be considered Markdown by this package (comma-separated). \
36 | Run \'Markdown Table Formatter: Enable For Current Scope\' command to \
37 | add current editor grammar to this setting.`,
38 | items: {
39 | type: 'string',
40 | },
41 | },
42 | limitLastColumnPadding: {
43 | type: 'boolean',
44 | default: false,
45 | description: `Do not pad the last column to more than your editor\'s \
46 | preferredLineLength setting.`,
47 | },
48 | };
49 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2NvbmZpZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFhLFFBQUEsTUFBTSxHQUFHO0lBQ3BCLFlBQVksRUFBRTtRQUNaLElBQUksRUFBRSxTQUFTO1FBQ2YsT0FBTyxFQUFFLElBQUk7UUFDYixXQUFXLEVBQUUsdUNBQXVDO0tBQ3JEO0lBQ0Qsd0JBQXdCLEVBQUU7UUFDeEIsSUFBSSxFQUFFLFNBQVM7UUFDZixPQUFPLEVBQUUsSUFBSTtRQUNiLFdBQVcsRUFBRSw4Q0FBOEM7S0FDNUQ7SUFDRCxZQUFZLEVBQUU7UUFDWixJQUFJLEVBQUUsU0FBUztRQUNmLE9BQU8sRUFBRSxDQUFDO1FBQ1YsV0FBVyxFQUNULCtEQUErRDtLQUNsRTtJQUNELHFCQUFxQixFQUFFO1FBQ3JCLElBQUksRUFBRSxTQUFTO1FBQ2YsT0FBTyxFQUFFLElBQUk7UUFDYixXQUFXLEVBQUU7Z0RBQytCO0tBQzdDO0lBQ0QseUJBQXlCLEVBQUU7UUFDekIsSUFBSSxFQUFFLFFBQVE7UUFDZCxPQUFPLEVBQUUsTUFBTTtRQUNmLElBQUksRUFBRSxDQUFDLE1BQU0sRUFBRSxRQUFRLEVBQUUsT0FBTyxDQUFDO1FBQ2pDLFdBQVcsRUFBRTs2QkFDWTtLQUMxQjtJQUNELHFCQUFxQixFQUFFO1FBQ3JCLElBQUksRUFBRSxPQUFPO1FBQ2IsT0FBTyxFQUFFLENBQUMsWUFBWSxDQUFDO1FBQ3ZCLFdBQVcsRUFBRTs7NENBRTJCO1FBQ3hDLEtBQUssRUFBRTtZQUNMLElBQUksRUFBRSxRQUFRO1NBQ2Y7S0FDRjtJQUNELHNCQUFzQixFQUFFO1FBQ3RCLElBQUksRUFBRSxTQUFTO1FBQ2YsT0FBTyxFQUFFLEtBQUs7UUFDZCxXQUFXLEVBQUU7NkJBQ1k7S0FDMUI7Q0FDRixDQUFBIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGNvbnN0IGNvbmZpZyA9IHtcbiAgZm9ybWF0T25TYXZlOiB7XG4gICAgdHlwZTogJ2Jvb2xlYW4nLFxuICAgIGRlZmF1bHQ6IHRydWUsXG4gICAgZGVzY3JpcHRpb246ICdGb3JtYXQgdGFibGVzIHdoZW4gZG9jdW1lbnQgaXMgc2F2ZWQ/JyxcbiAgfSxcbiAgYXV0b1NlbGVjdEVudGlyZURvY3VtZW50OiB7XG4gICAgdHlwZTogJ2Jvb2xlYW4nLFxuICAgIGRlZmF1bHQ6IHRydWUsXG4gICAgZGVzY3JpcHRpb246ICdTZWxlY3QgZW50aXJlIGRvY3VtZW50IGlmIHNlbGVjdGlvbiBpcyBlbXB0eScsXG4gIH0sXG4gIHNwYWNlUGFkZGluZzoge1xuICAgIHR5cGU6ICdpbnRlZ2VyJyxcbiAgICBkZWZhdWx0OiAxLFxuICAgIGRlc2NyaXB0aW9uOlxuICAgICAgJ0hvdyBtYW55IHNwYWNlcyBiZXR3ZWVuIGxlZnQgYW5kIHJpZ2h0IG9mIGVhY2ggY29sdW1uIGNvbnRlbnQnLFxuICB9LFxuICBrZWVwRmlyc3RBbmRMYXN0UGlwZXM6IHtcbiAgICB0eXBlOiAnYm9vbGVhbicsXG4gICAgZGVmYXVsdDogdHJ1ZSxcbiAgICBkZXNjcmlwdGlvbjogYEtlZXAgZmlyc3QgYW5kIGxhc3QgcGlwZXMgXCJ8XCIgaW4gdGFibGUgZm9ybWF0dGluZy4gXFxcblRhYmxlcyBhcmUgZWFzaWVyIHRvIGZvcm1hdCB3aGVuIHBpcGVzIGFyZSBrZXB0YCxcbiAgfSxcbiAgZGVmYXVsdFRhYmxlSnVzdGlmaWNhdGlvbjoge1xuICAgIHR5cGU6ICdzdHJpbmcnLFxuICAgIGRlZmF1bHQ6ICdMZWZ0JyxcbiAgICBlbnVtOiBbJ0xlZnQnLCAnQ2VudGVyJywgJ1JpZ2h0J10sXG4gICAgZGVzY3JpcHRpb246IGBEZWZpbmVzIHRoZSBkZWZhdWx0IGp1c3RpZmljYXRpb24gZm9yIHRhYmxlcyB0aGF0IGhhdmUgb25seSBhIFxcXG5cXCctXFwnIG9uIHRoZSBmb3JtYXR0aW5nIGxpbmVgLFxuICB9LFxuICBtYXJrZG93bkdyYW1tYXJTY29wZXM6IHtcbiAgICB0eXBlOiAnYXJyYXknLFxuICAgIGRlZmF1bHQ6IFsnc291cmNlLmdmbSddLFxuICAgIGRlc2NyaXB0aW9uOiBgRmlsZSBncmFtbWFyIHNjb3BlcyB0aGF0IHdpbGwgYmUgY29uc2lkZXJlZCBNYXJrZG93biBieSB0aGlzIHBhY2thZ2UgKGNvbW1hLXNlcGFyYXRlZCkuIFxcXG5SdW4gXFwnTWFya2Rvd24gVGFibGUgRm9ybWF0dGVyOiBFbmFibGUgRm9yIEN1cnJlbnQgU2NvcGVcXCcgY29tbWFuZCB0byBcXFxuYWRkIGN1cnJlbnQgZWRpdG9yIGdyYW1tYXIgdG8gdGhpcyBzZXR0aW5nLmAsXG4gICAgaXRlbXM6IHtcbiAgICAgIHR5cGU6ICdzdHJpbmcnLFxuICAgIH0sXG4gIH0sXG4gIGxpbWl0TGFzdENvbHVtblBhZGRpbmc6IHtcbiAgICB0eXBlOiAnYm9vbGVhbicsXG4gICAgZGVmYXVsdDogZmFsc2UsXG4gICAgZGVzY3JpcHRpb246IGBEbyBub3QgcGFkIHRoZSBsYXN0IGNvbHVtbiB0byBtb3JlIHRoYW4geW91ciBlZGl0b3JcXCdzIFxcXG5wcmVmZXJyZWRMaW5lTGVuZ3RoIHNldHRpbmcuYCxcbiAgfSxcbn1cbiJdfQ==
--------------------------------------------------------------------------------
/lib/formatTable.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const table_formatter_1 = require("./table-formatter");
4 | const wcswidth = require("wcwidth");
5 | function swidth(str) {
6 | const zwcrx = /[\u200B-\u200D\uFEFF\u00AD]/g;
7 | const match = str.match(zwcrx);
8 | return wcswidth(str) - (match ? match.length : 0);
9 | }
10 | function padding(len, str = ' ') {
11 | return str.repeat(len);
12 | }
13 | const stripTailPipes = (str) => str.trim().replace(/(^\||\|$)/g, '');
14 | const splitCells = (str) => str.split('|');
15 | const addTailPipes = (str) => `|${str}|`;
16 | const joinCells = (arr) => arr.join('|');
17 | const tableJustMap = {
18 | Left: ':-',
19 | Center: '::',
20 | Right: '-:',
21 | };
22 | function formatTable(text, settings = table_formatter_1.getAllSettings()) {
23 | const addTailPipesIfNeeded = settings.keepFirstAndLastPipes
24 | ? addTailPipes
25 | : (x) => x;
26 | let formatline = text[2].trim();
27 | const headerline = text[1].trim();
28 | let formatrow;
29 | let data;
30 | if (headerline.length === 0) {
31 | formatrow = 0;
32 | data = text[3];
33 | }
34 | else {
35 | formatrow = 1;
36 | data = text[1] + text[3];
37 | }
38 | const lines = data.trim().split(/\r?\n/);
39 | const justify = splitCells(stripTailPipes(formatline)).map(cell => {
40 | const trimmed = cell.trim();
41 | const first = trimmed[0];
42 | const last = trimmed[trimmed.length - 1];
43 | const ends = (first || ':') + (last || '-');
44 | if (ends === '--')
45 | return tableJustMap[settings.defaultTableJustification];
46 | else
47 | return ends;
48 | });
49 | const columns = justify.length;
50 | const colArr = Array.from(Array(columns));
51 | const cellPadding = padding(settings.spacePadding);
52 | const content = lines.map(line => {
53 | const cells = splitCells(stripTailPipes(line));
54 | if (columns - cells.length > 0) {
55 | cells.push(...Array(columns - cells.length).fill(''));
56 | }
57 | else if (columns - cells.length < 0) {
58 | cells[columns - 1] = joinCells(cells.slice(columns - 1));
59 | }
60 | return cells.map(cell => `${cellPadding}${cell.trim()}${cellPadding}`);
61 | });
62 | const widths = colArr.map((_x, i) => Math.max(2, ...content.map(cells => swidth(cells[i]))));
63 | if (settings.limitLastColumnPadding) {
64 | const preferredLineLength = atom.config.get('editor.preferredLineLength');
65 | const sum = (arr) => arr.reduce((x, y) => x + y, 0);
66 | const wsum = sum(widths);
67 | if (wsum > preferredLineLength) {
68 | const prewsum = sum(widths.slice(0, -1));
69 | widths[widths.length - 1] = Math.max(preferredLineLength - prewsum - widths.length - 1, 3);
70 | }
71 | }
72 | const just = function (str, col) {
73 | const length = Math.max(widths[col] - swidth(str), 0);
74 | switch (justify[col]) {
75 | case '::':
76 | return padding(length / 2) + str + padding((length + 1) / 2);
77 | case '-:':
78 | return padding(length) + str;
79 | case ':-':
80 | return str + padding(length);
81 | default:
82 | throw new Error(`Unknown column justification ${justify[col]}`);
83 | }
84 | };
85 | const formatted = content.map(cells => addTailPipesIfNeeded(joinCells(colArr.map((_x, i) => just(cells[i], i)))));
86 | formatline = addTailPipesIfNeeded(joinCells(colArr.map((_x, i) => {
87 | const [front, back] = justify[i];
88 | return front + padding(widths[i] - 2, '-') + back;
89 | })));
90 | formatted.splice(formatrow, 0, formatline);
91 | return ((formatrow === 0 && text[1] !== '' ? '\n' : '') +
92 | formatted.join('\n') +
93 | '\n');
94 | }
95 | exports.formatTable = formatTable;
96 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9ybWF0VGFibGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvZm9ybWF0VGFibGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSx1REFBa0Q7QUFDbEQsb0NBQW9DO0FBRXBDLGdCQUFnQixHQUFXO0lBR3pCLE1BQU0sS0FBSyxHQUFHLDhCQUE4QixDQUFBO0lBQzVDLE1BQU0sS0FBSyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUE7SUFDOUIsTUFBTSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUE7QUFDbkQsQ0FBQztBQUVELGlCQUFpQixHQUFXLEVBQUUsTUFBYyxHQUFHO0lBQzdDLE1BQU0sQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFBO0FBQ3hCLENBQUM7QUFDRCxNQUFNLGNBQWMsR0FBRyxDQUFDLEdBQVcsRUFBRSxFQUFFLENBQUMsR0FBRyxDQUFDLElBQUksRUFBRSxDQUFDLE9BQU8sQ0FBQyxZQUFZLEVBQUUsRUFBRSxDQUFDLENBQUE7QUFDNUUsTUFBTSxVQUFVLEdBQUcsQ0FBQyxHQUFXLEVBQUUsRUFBRSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUE7QUFDbEQsTUFBTSxZQUFZLEdBQUcsQ0FBQyxHQUFXLEVBQUUsRUFBRSxDQUFDLElBQUksR0FBRyxHQUFHLENBQUE7QUFDaEQsTUFBTSxTQUFTLEdBQUcsQ0FBQyxHQUFhLEVBQUUsRUFBRSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUE7QUFFbEQsTUFBTSxZQUFZLEdBQUc7SUFDbkIsSUFBSSxFQUFFLElBQUk7SUFDVixNQUFNLEVBQUUsSUFBSTtJQUNaLEtBQUssRUFBRSxJQUFJO0NBQ1osQ0FBQTtBQUVELHFCQUNFLElB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QUMsS0FBSyxFQUFFLElBQUksQ0FBQyxHQUFHLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQTtRQUNoQyxNQUFNLENBQUMsS0FBSyxHQUFHLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxHQUFHLElBQUksQ0FBQTtJQUNuRCxDQUFDLENBQUMsQ0FDSCxDQUNGLENBQUE7SUFFRCxTQUFTLENBQUMsTUFBTSxDQUFDLFNBQVMsRUFBRSxDQUFDLEVBQUUsVUFBVSxDQUFDLENBQUE7SUFFMUMsTUFBTSxDQUFDLENBQ0wsQ0FBQyxTQUFTLEtBQUssQ0FBQyxJQUFJLElBQUksQ0FBQyxDQUFDLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1FBQy9DLFNBQVMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDO1FBQ3BCLElBQUksQ0FDTCxDQUFBO0FBQ0gsQ0FBQztBQXBHRCxrQ0FvR0MiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBnZXRBbGxTZXR0aW5ncyB9IGZyb20gJy4vdGFibGUtZm9ybWF0dGVyJ1xuaW1wb3J0IHdjc3dpZHRoID0gcmVxdWlyZSgnd2N3aWR0aCcpXG5cbmZ1bmN0aW9uIHN3aWR0aChzdHI6IHN0cmluZykge1xuICAvLyB6ZXJvLXdpZHRoIFVuaWNvZGUgY2hhcmFjdGVycyB0aGF0IHdlIHNob3VsZCBpZ25vcmUgZm9yXG4gIC8vIHB1cnBvc2VzIG9mIGNvbXB1dGluZyBzdHJpbmcgXCJkaXNwbGF5XCIgd2lkdGhcbiAgY29uc3QgendjcnggPSAvW1xcdTIwMEItXFx1MjAwRFxcdUZFRkZcXHUwMEFEXS9nXG4gIGNvbnN0IG1hdGNoID0gc3RyLm1hdGNoKHp3Y3J4KVxuICByZXR1cm4gd2Nzd2lkdGgoc3RyKSAtIChtYXRjaCA/IG1hdGNoLmxlbmd0aCA6IDApXG59XG5cbmZ1bmN0aW9uIHBhZGRpbmcobGVuOiBudW1iZXIsIHN0cjogc3RyaW5nID0gJyAnKSB7XG4gIHJldHVybiBzdHIucmVwZWF0KGxlbilcbn1cbmNvbnN0IHN0cmlwVGFpbFBpcGVzID0gKHN0cjogc3RyaW5nKSA9PiBzdHIudHJpbSgpLnJlcGxhY2UoLyheXFx8fFxcfCQpL2csICcnKVxuY29uc3Qgc3BsaXRDZWxscyA9IChzdHI6IHN0cmluZykgPT4gc3RyLnNwbGl0KCd8JylcbmNvbnN0IGFkZFRhaWxQaXBlcyA9IChzdHI6IHN0cmluZykgPT4gYHwke3N0cn18YFxuY29uc3Qgam9pbkNlbGxzID0gKGFycjogc3RyaW5nW10pID0+IGFyci5qb2luKCd8JylcblxuY29uc3QgdGFibGVKdXN0TWFwID0ge1xuICBMZWZ0OiAnOi0nLFxuICBDZW50ZXI6ICc6OicsXG4gIFJpZ2h0OiAnLTonLFxufVxuXG5leHBvcnQgZnVuY3Rpb24gZm9ybWF0VGFibGUoXG4gIHRleHQ6IFJlZ0V4cEV4ZWNBcnJheSxcbiAgc2V0dGluZ3MgPSBnZXRBbGxTZXR0aW5ncygpLFxuKSB7XG4gIGNvbnN0IGFkZFRhaWxQaXBlc0lmTmVlZGVkID0gc2V0dGluZ3Mua2VlcEZpcnN0QW5kTGFzdFBpcGVzXG4gICAgPyBhZGRUYWlsUGlwZXNcbiAgICA6ICh4OiBzdHJpbmcpID0+IHhcblxuICBsZXQgZm9ybWF0bGluZSA9IHRleHRbMl0udHJpbSgpXG4gIGNvbnN0IGhlYWRlcmxpbmUgPSB0ZXh0WzFdLnRyaW0oKVxuXG4gIGxldCBmb3JtYXRyb3c6IG51bWJlclxuICBsZXQgZGF0YTogc3RyaW5nXG4gIGlmIChoZWFkZXJsaW5lLmxlbmd0aCA9PT0gMCkge1xuICAgIGZvcm1hdHJvdyA9IDBcbiAgICBkYXRhID0gdGV4dFszXVxuICB9IGVsc2Uge1xuICAgIGZvcm1hdHJvdyA9IDFcbiAgICBkYXRhID0gdGV4dFsxXSArIHRleHRbM11cbiAgfVxuICBjb25zdCBsaW5lcyA9IGRhdGEudHJpbSgpLnNwbGl0KC9cXHI/XFxuLylcblxuICBjb25zdCBqdXN0aWZ5ID0gc3BsaXRDZWxscyhzdHJpcFRhaWxQaXBlcyhmb3JtYXRsaW5lKSkubWFwKGNlbGwgPT4ge1xuICAgIGNvbnN0IHRyaW1tZWQgPSBjZWxsLnRyaW0oKVxuICAgIGNvbnN0IGZpcnN0ID0gdHJpbW1lZFswXVxuICAgIGNvbnN0IGxhc3QgPSB0cmltbWVkW3RyaW1tZWQubGVuZ3RoIC0gMV1cbiAgICBjb25zdCBlbmRzID0gKGZpcnN0IHx8ICc6JykgKyAobGFzdCB8fCAnLScpXG4gICAgaWYgKGVuZHMgPT09ICctLScpIHJldHVybiB0YWJsZUp1c3RNYXBbc2V0dGluZ3MuZGVmYXVsdFRhYmxlSnVzdGlmaWNhdGlvbl1cbiAgICBlbHNlIHJldHVybiBlbmRzXG4gIH0pXG5cbiAgY29uc3QgY29sdW1ucyA9IGp1c3RpZnkubGVuZ3RoXG4gIGNvbnN0IGNvbEFycjogdW5kZWZpbmVkW10gPSBBcnJheS5mcm9tKEFycmF5KGNvbHVtbnMpKVxuXG4gIGNvbnN0IGNlbGxQYWRkaW5nID0gcGFkZGluZyhzZXR0aW5ncy5zcGFjZVBhZGRpbmcpXG5cbiAgY29uc3QgY29udGVudCA9IGxpbmVzLm1hcChsaW5lID0+IHtcbiAgICBjb25zdCBjZWxscyA9IHNwbGl0Q2VsbHMoc3RyaXBUYWlsUGlwZXMobGluZSkpXG4gICAgaWYgKGNvbHVtbnMgLSBjZWxscy5sZW5ndGggPiAwKSB7XG4gICAgICAvLyBwYWQgcm93cyB0byBoYXZlIGBjb2x1bW5zYCBjZWxsc1xuICAgICAgY2VsbHMucHVzaCguLi5BcnJheShjb2x1bW5zIC0gY2VsbHMubGVuZ3RoKS5maWxsKCcnKSlcbiAgICB9IGVsc2UgaWYgKGNvbHVtbnMgLSBjZWxscy5sZW5ndGggPCAwKSB7XG4gICAgICAvLyBwdXQgYWxsIGV4dHJhIGNvbnRlbnQgaW50byBsYXN0IGNlbGxcbiAgICAgIGNlbGxzW2NvbHVtbnMgLSAxXSA9IGpvaW5DZWxscyhjZWxscy5zbGljZShjb2x1bW5zIC0gMSkpXG4gICAgfVxuICAgIHJldHVybiBjZWxscy5tYXAoY2VsbCA9PiBgJHtjZWxsUGFkZGluZ30ke2NlbGwudHJpbSgpfSR7Y2VsbFBhZGRpbmd9YClcbiAgfSlcblxuICBjb25zdCB3aWR0aHMgPSBjb2xBcnIubWFwKChfeCwgaSkgPT5cbiAgICBNYXRoLm1heCgyLCAuLi5jb250ZW50Lm1hcChjZWxscyA9PiBzd2lkdGgoY2VsbHNbaV0pKSksXG4gIClcblxuICBpZiAoc2V0dGluZ3MubGltaXRMYXN0Q29sdW1uUGFkZGluZykge1xuICAgIGNvbnN0IHByZWZlcnJlZExpbmVMZW5ndGggPSBhdG9tLmNvbmZpZy5nZXQoJ2VkaXRvci5wcmVmZXJyZWRMaW5lTGVuZ3RoJylcbiAgICBjb25zdCBzdW0gPSAoYXJyOiBudW1iZXJbXSkgPT4gYXJyLnJlZHVjZSgoeCwgeSkgPT4geCArIHksIDApXG4gICAgY29uc3Qgd3N1bSA9IHN1bSh3aWR0aHMpXG4gICAgaWYgKHdzdW0gPiBwcmVmZXJyZWRMaW5lTGVuZ3RoKSB7XG4gICAgICBjb25zdCBwcmV3c3VtID0gc3VtKHdpZHRocy5zbGljZSgwLCAtMSkpXG4gICAgICB3aWR0aHNbd2lkdGhzLmxlbmd0aCAtIDFdID0gTWF0aC5tYXgoXG4gICAgICAgIHByZWZlcnJlZExpbmVMZW5ndGggLSBwcmV3c3VtIC0gd2lkdGhzLmxlbmd0aCAtIDEsXG4gICAgICAgIDMsXG4gICAgICApXG4gICAgICAvLyBOZWVkIGF0IGxlYXN0IDotLSBmb3IgZ2l0aHViIHRvIHJlY29nbml6ZSBhIGNvbHVtblxuICAgIH1cbiAgfVxuXG4gIGNvbnN0IGp1c3QgPSBmdW5jdGlvbihzdHI6IHN0cmluZywgY29sOiBudW1iZXIpIHtcbiAgICBjb25zdCBsZW5ndGggPSBNYXRoLm1heCh3aWR0aHNbY29sXSAtIHN3aWR0aChzdHIpLCAwKVxuICAgIHN3aXRjaCAoanVzdGlmeVtjb2xdKSB7XG4gICAgICBjYXNlICc6Oic6XG4gICAgICAgIHJldHVybiBwYWRkaW5nKGxlbmd0aCAvIDIpICsgc3RyICsgcGFkZGluZygobGVuZ3RoICsgMSkgLyAyKVxuICAgICAgY2FzZSAnLTonOlxuICAgICAgICByZXR1cm4gcGFkZGluZyhsZW5ndGgpICsgc3RyXG4gICAgICBjYXNlICc6LSc6XG4gICAgICAgIHJldHVybiBzdHIgKyBwYWRkaW5nKGxlbmd0aClcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgVW5rbm93biBjb2x1bW4ganVzdGlmaWNhdGlvbiAke2p1c3RpZnlbY29sXX1gKVxuICAgIH1cbiAgfVxuXG4gIGNvbnN0IGZvcm1hdHRlZCA9IGNvbnRlbnQubWFwKGNlbGxzID0+XG4gICAgYWRkVGFpbFBpcGVzSWZOZWVkZWQoam9pbkNlbGxzKGNvbEFyci5tYXAoKF94LCBpKSA9PiBqdXN0KGNlbGxzW2ldLCBpKSkpKSxcbiAgKVxuXG4gIGZvcm1hdGxpbmUgPSBhZGRUYWlsUGlwZXNJZk5lZWRlZChcbiAgICBqb2luQ2VsbHMoXG4gICAgICBjb2xBcnIubWFwKChfeCwgaSkgPT4ge1xuICAgICAgICBjb25zdCBbZnJvbnQsIGJhY2tdID0ganVzdGlmeVtpXVxuICAgICAgICByZXR1cm4gZnJvbnQgKyBwYWRkaW5nKHdpZHRoc1tpXSAtIDIsICctJykgKyBiYWNrXG4gICAgICB9KSxcbiAgICApLFxuICApXG5cbiAgZm9ybWF0dGVkLnNwbGljZShmb3JtYXRyb3csIDAsIGZvcm1hdGxpbmUpXG5cbiAgcmV0dXJuIChcbiAgICAoZm9ybWF0cm93ID09PSAwICYmIHRleHRbMV0gIT09ICcnID8gJ1xcbicgOiAnJykgK1xuICAgIGZvcm1hdHRlZC5qb2luKCdcXG4nKSArXG4gICAgJ1xcbidcbiAgKVxufVxuIl19
--------------------------------------------------------------------------------
/lib/markdown-table-formatter.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const table_formatter_1 = require("./table-formatter");
4 | var config_1 = require("./config");
5 | exports.config = config_1.config;
6 | let tableFormatter;
7 | let command;
8 | function activate() {
9 | tableFormatter = new table_formatter_1.TableFormatter();
10 | command = atom.commands.add('atom-text-editor', {
11 | 'markdown-table-formatter:format': event => {
12 | const editor = event.currentTarget.getModel();
13 | tableFormatter.format(editor);
14 | },
15 | 'markdown-table-formatter:enable-for-current-scope'(event) {
16 | const editor = event.currentTarget.getModel();
17 | const scope = editor.getGrammar().scopeName;
18 | const key = 'markdown-table-formatter.markdownGrammarScopes';
19 | const current = atom.config.get(key);
20 | if (!scope) {
21 | atom.notifications.addError('Could not determine editor grammar scope');
22 | }
23 | else if (current.includes(scope)) {
24 | atom.notifications.addWarning(`${scope} already considered Markdown`);
25 | }
26 | else {
27 | atom.config.set(key, [...current, scope]);
28 | atom.notifications.addSuccess(`Successfully added ${scope} to Markdown scopes`);
29 | }
30 | },
31 | });
32 | }
33 | exports.activate = activate;
34 | function deactivate() {
35 | command.dispose();
36 | tableFormatter.destroy();
37 | }
38 | exports.deactivate = deactivate;
39 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFya2Rvd24tdGFibGUtZm9ybWF0dGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL21hcmtkb3duLXRhYmxlLWZvcm1hdHRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLHVEQUFrRDtBQUVsRCxtQ0FBaUM7QUFBeEIsMEJBQUEsTUFBTSxDQUFBO0FBRWYsSUFBSSxjQUE4QixDQUFBO0FBQ2xDLElBQUksT0FBbUIsQ0FBQTtBQUV2QjtJQUNFLGNBQWMsR0FBRyxJQUFJLGdDQUFjLEVBQUUsQ0FBQTtJQUVyQyxPQUFPLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsa0JBQWtCLEVBQUU7UUFDOUMsaUNBQWlDLEVBQUUsS0FBSyxDQUFDLEVBQUU7WUFDekMsTUFBTSxNQUFNLEdBQUcsS0FBSyxDQUFDLGFBQWEsQ0FBQyxRQUFRLEVBQUUsQ0FBQTtZQUM3QyxjQUFjLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFBO1FBQy9CLENBQUM7UUFDRCxtREFBbUQsQ0FBQyxLQUFLO1lBQ3ZELE1BQU0sTUFBTSxHQUFHLEtBQUssQ0FBQyxhQUFhLENBQUMsUUFBUSxFQUFFLENBQUE7WUFDN0MsTUFBTSxLQUFLLEdBQUcsTUFBTSxDQUFDLFVBQVUsRUFBRSxDQUFDLFNBQVMsQ0FBQTtZQUMzQyxNQUFNLEdBQUcsR0FBRyxnREFBZ0QsQ0FBQTtZQUM1RCxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQTtZQUNwQyxFQUFFLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7Z0JBQ1gsSUFBSSxDQUFDLGFBQWEsQ0FBQyxRQUFRLENBQUMsMENBQTBDLENBQUMsQ0FBQTtZQUN6RSxDQUFDO1lBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO2dCQUNuQyxJQUFJLENBQUMsYUFBYSxDQUFDLFVBQVUsQ0FBQyxHQUFHLEtBQUssOEJBQThCLENBQUMsQ0FBQTtZQUN2RSxDQUFDO1lBQUMsSUFBSSxDQUFDLENBQUM7Z0JBQ04sSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLENBQUMsR0FBRyxPQUFPLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQTtnQkFDekMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxVQUFVLENBQzNCLHNCQUFzQixLQUFLLHFCQUFxQixDQUNqRCxDQUFBO1lBQ0gsQ0FBQztRQUNILENBQUM7S0FDRixDQUFDLENBQUE7QUFDSixDQUFDO0FBekJELDRCQXlCQztBQUVEO0lBQ0UsT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFBO0lBQ2pCLGNBQWMsQ0FBQyxPQUFPLEVBQUUsQ0FBQTtBQUMxQixDQUFDO0FBSEQsZ0NBR0MiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBUYWJsZUZvcm1hdHRlciB9IGZyb20gJy4vdGFibGUtZm9ybWF0dGVyJ1xuaW1wb3J0IHsgRGlzcG9zYWJsZSB9IGZyb20gJ2F0b20nXG5leHBvcnQgeyBjb25maWcgfSBmcm9tICcuL2NvbmZpZydcblxubGV0IHRhYmxlRm9ybWF0dGVyOiBUYWJsZUZvcm1hdHRlclxubGV0IGNvbW1hbmQ6IERpc3Bvc2FibGVcblxuZXhwb3J0IGZ1bmN0aW9uIGFjdGl2YXRlKCkge1xuICB0YWJsZUZvcm1hdHRlciA9IG5ldyBUYWJsZUZvcm1hdHRlcigpXG4gIC8vIFJlZ2lzdGVyIGNvbW1hbmQgdG8gd29ya3NwYWNlXG4gIGNvbW1hbmQgPSBhdG9tLmNvbW1hbmRzLmFkZCgnYXRvbS10ZXh0LWVkaXRvcicsIHtcbiAgICAnbWFya2Rvd24tdGFibGUtZm9ybWF0dGVyOmZvcm1hdCc6IGV2ZW50ID0+IHtcbiAgICAgIGNvbnN0IGVkaXRvciA9IGV2ZW50LmN1cnJlbnRUYXJnZXQuZ2V0TW9kZWwoKVxuICAgICAgdGFibGVGb3JtYXR0ZXIuZm9ybWF0KGVkaXRvcilcbiAgICB9LFxuICAgICdtYXJrZG93bi10YWJsZS1mb3JtYXR0ZXI6ZW5hYmxlLWZvci1jdXJyZW50LXNjb3BlJyhldmVudCkge1xuICAgICAgY29uc3QgZWRpdG9yID0gZXZlbnQuY3VycmVudFRhcmdldC5nZXRNb2RlbCgpXG4gICAgICBjb25zdCBzY29wZSA9IGVkaXRvci5nZXRHcmFtbWFyKCkuc2NvcGVOYW1lXG4gICAgICBjb25zdCBrZXkgPSAnbWFya2Rvd24tdGFibGUtZm9ybWF0dGVyLm1hcmtkb3duR3JhbW1hclNjb3BlcydcbiAgICAgIGNvbnN0IGN1cnJlbnQgPSBhdG9tLmNvbmZpZy5nZXQoa2V5KVxuICAgICAgaWYgKCFzY29wZSkge1xuICAgICAgICBhdG9tLm5vdGlmaWNhdGlvbnMuYWRkRXJyb3IoJ0NvdWxkIG5vdCBkZXRlcm1pbmUgZWRpdG9yIGdyYW1tYXIgc2NvcGUnKVxuICAgICAgfSBlbHNlIGlmIChjdXJyZW50LmluY2x1ZGVzKHNjb3BlKSkge1xuICAgICAgICBhdG9tLm5vdGlmaWNhdGlvbnMuYWRkV2FybmluZyhgJHtzY29wZX0gYWxyZWFkeSBjb25zaWRlcmVkIE1hcmtkb3duYClcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGF0b20uY29uZmlnLnNldChrZXksIFsuLi5jdXJyZW50LCBzY29wZV0pXG4gICAgICAgIGF0b20ubm90aWZpY2F0aW9ucy5hZGRTdWNjZXNzKFxuICAgICAgICAgIGBTdWNjZXNzZnVsbHkgYWRkZWQgJHtzY29wZX0gdG8gTWFya2Rvd24gc2NvcGVzYCxcbiAgICAgICAgKVxuICAgICAgfVxuICAgIH0sXG4gIH0pXG59XG5cbmV4cG9ydCBmdW5jdGlvbiBkZWFjdGl2YXRlKCkge1xuICBjb21tYW5kLmRpc3Bvc2UoKVxuICB0YWJsZUZvcm1hdHRlci5kZXN0cm95KClcbn1cbiJdfQ==
--------------------------------------------------------------------------------
/lib/regex.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const XRegExp = require("xregexp");
4 | exports.regex = XRegExp(`\
5 | ( # header capture
6 | (?:
7 | (?:[^\\r\\n]*?\\|[^\\r\\n]*) # line w/ at least one pipe
8 | \\ * # maybe trailing whitespace
9 | )? # maybe header
10 | (?:\\r?\\n|^) # newline
11 | )
12 | ( # format capture
13 | (?:
14 | \\|\\ *(?::?-+:?|::)\\ * # format starting w/pipe
15 | |\\|?(?:\\ *(?::?-+:?|::)\\ *\\|)+ # or separated by pipe
16 | )
17 | (?:\\ *(?::?-+:?|::)\\ *)? # maybe w/o trailing pipe
18 | \\ * # maybe trailing whitespace
19 | \\r?\\n # newline
20 | )
21 | ( # body capture
22 | (?:
23 | (?:[^\\r\\n]*?\\|[^\\r\\n]*) # line w/ at least one pipe
24 | \\ * # maybe trailing whitespace
25 | (?:\\r?\\n|$) # newline
26 | )+ # at least one
27 | )
28 | `, 'gx');
29 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVnZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvcmVnZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxtQ0FBbUM7QUFFdEIsUUFBQSxLQUFLLEdBQUcsT0FBTyxDQUMxQjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0NBd0JELEVBQ0MsSUFBSSxDQUNMLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgWFJlZ0V4cCA9IHJlcXVpcmUoJ3hyZWdleHAnKVxuXG5leHBvcnQgY29uc3QgcmVnZXggPSBYUmVnRXhwKFxuICBgXFxcbiggIyBoZWFkZXIgY2FwdHVyZVxuICAoPzpcbiAgICAoPzpbXlxcXFxyXFxcXG5dKj9cXFxcfFteXFxcXHJcXFxcbl0qKSAgICAgICAjIGxpbmUgdy8gYXQgbGVhc3Qgb25lIHBpcGVcbiAgICBcXFxcICogICAgICAgICAgICAgICAgICAgICAgICMgbWF5YmUgdHJhaWxpbmcgd2hpdGVzcGFjZVxuICApPyAgICAgICAgICAgICAgICAgICAgICAgICAgIyBtYXliZSBoZWFkZXJcbiAgKD86XFxcXHI/XFxcXG58XikgICAgICAgICAgICAgICAgICMgbmV3bGluZVxuKVxuKCAjIGZvcm1hdCBjYXB0dXJlXG4gICg/OlxuICAgIFxcXFx8XFxcXCAqKD86Oj8tKzo/fDo6KVxcXFwgKiAgICAgICAgICAgICMgZm9ybWF0IHN0YXJ0aW5nIHcvcGlwZVxuICAgIHxcXFxcfD8oPzpcXFxcICooPzo6Py0rOj98OjopXFxcXCAqXFxcXHwpKyAgICMgb3Igc2VwYXJhdGVkIGJ5IHBpcGVcbiAgKVxuICAoPzpcXFxcICooPzo6Py0rOj98OjopXFxcXCAqKT8gICAgICAgICAgICMgbWF5YmUgdy9vIHRyYWlsaW5nIHBpcGVcbiAgXFxcXCAqICAgICAgICAgICAgICAgICAgICAgICAgICMgbWF5YmUgdHJhaWxpbmcgd2hpdGVzcGFjZVxuICBcXFxccj9cXFxcbiAgICAgICAgICAgICAgICAgICAgICAgIyBuZXdsaW5lXG4pXG4oICMgYm9keSBjYXB0dXJlXG4gICg/OlxuICAgICg/OlteXFxcXHJcXFxcbl0qP1xcXFx8W15cXFxcclxcXFxuXSopICAgICAgICMgbGluZSB3LyBhdCBsZWFzdCBvbmUgcGlwZVxuICAgIFxcXFwgKiAgICAgICAgICAgICAgICAgICAgICAgIyBtYXliZSB0cmFpbGluZyB3aGl0ZXNwYWNlXG4gICAgKD86XFxcXHI/XFxcXG58JCkgICAgICAgICAgICAgICAjIG5ld2xpbmVcbiAgKSsgIyBhdCBsZWFzdCBvbmVcbilcbmAsXG4gICdneCcsXG4pXG4iXX0=
--------------------------------------------------------------------------------
/lib/table-formatter.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const atom_1 = require("atom");
4 | const regex_1 = require("./regex");
5 | const formatTable_1 = require("./formatTable");
6 | function getAllSettings() {
7 | return atom.config.get('markdown-table-formatter');
8 | }
9 | exports.getAllSettings = getAllSettings;
10 | class TableFormatter {
11 | constructor() {
12 | this.subscriptions = new atom_1.CompositeDisposable();
13 | atom.workspace.observeTextEditors(editor => this.subscriptions.add(editor.getBuffer().onWillSave(() => {
14 | if (atom.config.get('markdown-table-formatter.formatOnSave')) {
15 | this.format(editor, true);
16 | }
17 | })));
18 | }
19 | destroy() {
20 | this.subscriptions.dispose();
21 | }
22 | format(editor, force = false) {
23 | let selectionsRanges = editor.getSelectedBufferRanges();
24 | const settings = getAllSettings();
25 | const bufferRange = editor.getBuffer().getRange();
26 | const selectionsRangesEmpty = selectionsRanges.every(i => i.isEmpty());
27 | if (!settings.markdownGrammarScopes.includes(editor.getGrammar().scopeName) &&
28 | selectionsRangesEmpty) {
29 | return undefined;
30 | }
31 | if (force || (selectionsRangesEmpty && settings.autoSelectEntireDocument)) {
32 | selectionsRanges = [bufferRange];
33 | }
34 | else {
35 | selectionsRanges = selectionsRanges
36 | .filter(srange => !(srange.isEmpty() && settings.autoSelectEntireDocument))
37 | .map(srange => {
38 | let { start } = bufferRange;
39 | let { end } = bufferRange;
40 | editor.scanInBufferRange(/^$/m, new atom_1.Range(srange.start, bufferRange.end), ({ range }) => (end = range.start));
41 | editor.backwardsScanInBufferRange(/^$/m, new atom_1.Range(bufferRange.start, srange.end), ({ range }) => (start = range.start));
42 | if (end.isLessThan(srange.end))
43 | end = srange.end;
44 | if (start.isGreaterThan(srange.start))
45 | start = srange.start;
46 | return new atom_1.Range(start, end);
47 | });
48 | }
49 | return editor.getBuffer().transact(() => selectionsRanges.map(range => editor.backwardsScanInBufferRange(regex_1.regex, range, function (obj) {
50 | return editor.setTextInBufferRange(obj.range, formatTable_1.formatTable(obj.match, settings));
51 | })));
52 | }
53 | }
54 | exports.TableFormatter = TableFormatter;
55 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGFibGUtZm9ybWF0dGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3RhYmxlLWZvcm1hdHRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLCtCQUErRTtBQUMvRSxtQ0FBK0I7QUFDL0IsK0NBQTJDO0FBRTNDO0lBQ0UsTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLDBCQUEwQixDQUFDLENBQUE7QUFDcEQsQ0FBQztBQUZELHdDQUVDO0FBRUQ7SUFFRTtRQURpQixrQkFBYSxHQUFHLElBQUksMEJBQW1CLEVBQUUsQ0FBQTtRQUV4RCxJQUFJLENBQUMsU0FBUyxDQUFDLGtCQUFrQixDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQ3pDLElBQUksQ0FBQyxhQUFhLENBQUMsR0FBRyxDQUNwQixNQUFNLENBQUMsU0FBUyxFQUFFLENBQUMsVUFBVSxDQUFDLEdBQUcsRUFBRTtZQUNqQyxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyx1Q0FBdUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztnQkFDN0QsSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLENBQUE7WUFDM0IsQ0FBQztRQUNILENBQUMsQ0FBQyxDQUNILENBQ0YsQ0FBQTtJQUNILENBQUM7SUFFTSxPQUFPO1FBQ1osSUFBSSxDQUFDLGFBQWEsQ0FBQyxPQUFPLEVBQUUsQ0FBQTtJQUM5QixDQUFDO0lBRU0sTUFBTSxDQUFDLE1BQWtCLEVBQUUsUUFBaUIsS0FBSztRQUN0RCxJQUFJLGdCQUFnQixHQUFHLE1BQU0sQ0FBQyx1QkFBdUIsRUFBRSxDQUFBO1FBQ3ZELE1BQU0sUUFBUSxHQUFHLGNBQWMsRUFBRSxDQUFBO1FBRWpDLE1BQU0sV0FBVyxHQUFHLE1BQU0sQ0FBQyxTQUFTLEVBQUUsQ0FBQyxRQUFRLEVBQUUsQ0FBQTtRQUNqRCxNQUFNLHFCQUFxQixHQUFHLGdCQUFnQixDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFBO1FBQ3RFLEVBQUUsQ0FBQyxDQUNELENBQUMsUUFBUSxDQUFDLHFCQUFxQixDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsVUFBVSxFQUFFLENBQUMsU0FBUyxDQUFDO1lBQ3ZFLHFCQUNGLENBQUMsQ0FBQyxDQUFDO1lBQ0QsTUFBTSxDQUFDLFNBQVMsQ0FBQTtRQUNsQixDQUFDO1FBQ0QsRUFBRSxDQUFDLENBQUMsS0FBSyxJQUFJLENBQUMscUJBQXFCLElBQUksUUFBUSxDQUFDLHdCQUF3QixDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQzFFLGdCQUFnQixHQUFHLENBQUMsV0FBVyxDQUFDLENBQUE7UUFDbEMsQ0FBQztRQUFDLElBQUksQ0FBQyxDQUFDO1lBQ04sZ0JBQWdCLEdBQUcsZ0JBQWdCO2lCQUNoQyxNQUFNLENBQ0wsTUFBTSxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxJQUFJLFFBQVEsQ0FBQyx3QkFBd0IsQ0FBQyxDQUNuRTtpQkFDQSxHQUFHLENBQUMsTUFBTSxDQUFDLEVBQUU7Z0JBQ1osSUFBSSxFQUFFLEtBQUssRUFBRSxHQUFHLFdBQVcsQ0FBQTtnQkFDM0IsSUFBSSxFQUFFLEdBQUcsRUFBRSxHQUFHLFdBQVcsQ0FBQTtnQkFDekIsTUFBTSxDQUFDLGlCQUFpQixDQUN0QixLQUFLLEVBQ0wsSUFBSSxZQUFLLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxXQUFXLENBQUMsR0FBRyxDQUFDLEVBQ3hDLENBQUMsRUFBRSxLQUFLLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQyxHQUFHLEdBQUcsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUNuQyxDQUFBO2dCQUNELE1BQU0sQ0FBQywwQkFBMEIsQ0FDL0IsS0FBSyxFQUNMLElBQUksWUFBSyxDQUFDLFdBQVcsQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLEdBQUcsQ0FBQyxFQUN4QyxDQUFDLEVBQUUsS0FBSyxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUMsS0FBSyxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FDckMsQ0FBQTtnQkFDRCxFQUFFLENBQUMsQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQztvQkFBQyxHQUFHLEdBQUcsTUFBTSxDQUFDLEdBQUcsQ0FBQTtnQkFDaEQsRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLGFBQWEsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7b0JBQUMsS0FBSyxHQUFHLE1BQU0sQ0FBQyxLQUFLLENBQUE7Z0JBQzNELE1BQU0sQ0FBQyxJQUFJLFlBQUssQ0FBQyxLQUFLLEVBQUUsR0FBRyxDQUFDLENBQUE7WUFDOUIsQ0FBQyxDQUFDLENBQUE7UUFDTixDQUFDO1FBRUQsTUFBTSxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUUsQ0FBQyxRQUFRLENBQUMsR0FBRyxFQUFFLENBQ3RDLGdCQUFnQixDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUMzQixNQUFNLENBQUMsMEJBQTBCLENBQUMsYUFBSyxFQUFFLEtBQUssRUFBRSxVQUM5QyxHQUFxQjtZQUVyQixNQUFNLENBQUMsTUFBTSxDQUFDLG9CQUFvQixDQUNoQyxHQUFHLENBQUMsS0FBSyxFQUNULHlCQUFXLENBQUMsR0FBRyxDQUFDLEtBQUssRUFBRSxRQUFRLENBQUMsQ0FDakMsQ0FBQTtRQUNILENBQUMsQ0FBQyxDQUNILENBQ0YsQ0FBQTtJQUNILENBQUM7Q0FDRjtBQXJFRCx3Q0FxRUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb21wb3NpdGVEaXNwb3NhYmxlLCBSYW5nZSwgVGV4dEVkaXRvciwgQnVmZmVyU2NhblJlc3VsdCB9IGZyb20gJ2F0b20nXG5pbXBvcnQgeyByZWdleCB9IGZyb20gJy4vcmVnZXgnXG5pbXBvcnQgeyBmb3JtYXRUYWJsZSB9IGZyb20gJy4vZm9ybWF0VGFibGUnXG5cbmV4cG9ydCBmdW5jdGlvbiBnZXRBbGxTZXR0aW5ncygpIHtcbiAgcmV0dXJuIGF0b20uY29uZmlnLmdldCgnbWFya2Rvd24tdGFibGUtZm9ybWF0dGVyJylcbn1cblxuZXhwb3J0IGNsYXNzIFRhYmxlRm9ybWF0dGVyIHtcbiAgcHJpdmF0ZSByZWFkb25seSBzdWJzY3JpcHRpb25zID0gbmV3IENvbXBvc2l0ZURpc3Bvc2FibGUoKVxuICBjb25zdHJ1Y3RvcigpIHtcbiAgICBhdG9tLndvcmtzcGFjZS5vYnNlcnZlVGV4dEVkaXRvcnMoZWRpdG9yID0+XG4gICAgICB0aGlzLnN1YnNjcmlwdGlvbnMuYWRkKFxuICAgICAgICBlZGl0b3IuZ2V0QnVmZmVyKCkub25XaWxsU2F2ZSgoKSA9PiB7XG4gICAgICAgICAgaWYgKGF0b20uY29uZmlnLmdldCgnbWFya2Rvd24tdGFibGUtZm9ybWF0dGVyLmZvcm1hdE9uU2F2ZScpKSB7XG4gICAgICAgICAgICB0aGlzLmZvcm1hdChlZGl0b3IsIHRydWUpXG4gICAgICAgICAgfVxuICAgICAgICB9KSxcbiAgICAgICksXG4gICAgKVxuICB9XG5cbiAgcHVibGljIGRlc3Ryb3koKSB7XG4gICAgdGhpcy5zdWJzY3JpcHRpb25zLmRpc3Bvc2UoKVxuICB9XG5cbiAgcHVibGljIGZvcm1hdChlZGl0b3I6IFRleHRFZGl0b3IsIGZvcmNlOiBib29sZWFuID0gZmFsc2UpIHtcbiAgICBsZXQgc2VsZWN0aW9uc1JhbmdlcyA9IGVkaXRvci5nZXRTZWxlY3RlZEJ1ZmZlclJhbmdlcygpXG4gICAgY29uc3Qgc2V0dGluZ3MgPSBnZXRBbGxTZXR0aW5ncygpXG5cbiAgICBjb25zdCBidWZmZXJSYW5nZSA9IGVkaXRvci5nZXRCdWZmZXIoKS5nZXRSYW5nZSgpXG4gICAgY29uc3Qgc2VsZWN0aW9uc1Jhbmdlc0VtcHR5ID0gc2VsZWN0aW9uc1Jhbmdlcy5ldmVyeShpID0+IGkuaXNFbXB0eSgpKVxuICAgIGlmIChcbiAgICAgICFzZXR0aW5ncy5tYXJrZG93bkdyYW1tYXJTY29wZXMuaW5jbHVkZXMoZWRpdG9yLmdldEdyYW1tYXIoKS5zY29wZU5hbWUpICYmXG4gICAgICBzZWxlY3Rpb25zUmFuZ2VzRW1wdHlcbiAgICApIHtcbiAgICAgIHJldHVybiB1bmRlZmluZWRcbiAgICB9XG4gICAgaWYgKGZvcmNlIHx8IChzZWxlY3Rpb25zUmFuZ2VzRW1wdHkgJiYgc2V0dGluZ3MuYXV0b1NlbGVjdEVudGlyZURvY3VtZW50KSkge1xuICAgICAgc2VsZWN0aW9uc1JhbmdlcyA9IFtidWZmZXJSYW5nZV1cbiAgICB9IGVsc2Uge1xuICAgICAgc2VsZWN0aW9uc1JhbmdlcyA9IHNlbGVjdGlvbnNSYW5nZXNcbiAgICAgICAgLmZpbHRlcihcbiAgICAgICAgICBzcmFuZ2UgPT4gIShzcmFuZ2UuaXNFbXB0eSgpICYmIHNldHRpbmdzLmF1dG9TZWxlY3RFbnRpcmVEb2N1bWVudCksXG4gICAgICAgIClcbiAgICAgICAgLm1hcChzcmFuZ2UgPT4ge1xuICAgICAgICAgIGxldCB7IHN0YXJ0IH0gPSBidWZmZXJSYW5nZVxuICAgICAgICAgIGxldCB7IGVuZCB9ID0gYnVmZmVyUmFuZ2VcbiAgICAgICAgICBlZGl0b3Iuc2NhbkluQnVmZmVyUmFuZ2UoXG4gICAgICAgICAgICAvXiQvbSxcbiAgICAgICAgICAgIG5ldyBSYW5nZShzcmFuZ2Uuc3RhcnQsIGJ1ZmZlclJhbmdlLmVuZCksXG4gICAgICAgICAgICAoeyByYW5nZSB9KSA9PiAoZW5kID0gcmFuZ2Uuc3RhcnQpLFxuICAgICAgICAgIClcbiAgICAgICAgICBlZGl0b3IuYmFja3dhcmRzU2NhbkluQnVmZmVyUmFuZ2UoXG4gICAgICAgICAgICAvXiQvbSxcbiAgICAgICAgICAgIG5ldyBSYW5nZShidWZmZXJSYW5nZS5zdGFydCwgc3JhbmdlLmVuZCksXG4gICAgICAgICAgICAoeyByYW5nZSB9KSA9PiAoc3RhcnQgPSByYW5nZS5zdGFydCksXG4gICAgICAgICAgKVxuICAgICAgICAgIGlmIChlbmQuaXNMZXNzVGhhbihzcmFuZ2UuZW5kKSkgZW5kID0gc3JhbmdlLmVuZFxuICAgICAgICAgIGlmIChzdGFydC5pc0dyZWF0ZXJUaGFuKHNyYW5nZS5zdGFydCkpIHN0YXJ0ID0gc3JhbmdlLnN0YXJ0XG4gICAgICAgICAgcmV0dXJuIG5ldyBSYW5nZShzdGFydCwgZW5kKVxuICAgICAgICB9KVxuICAgIH1cblxuICAgIHJldHVybiBlZGl0b3IuZ2V0QnVmZmVyKCkudHJhbnNhY3QoKCkgPT5cbiAgICAgIHNlbGVjdGlvbnNSYW5nZXMubWFwKHJhbmdlID0+XG4gICAgICAgIGVkaXRvci5iYWNrd2FyZHNTY2FuSW5CdWZmZXJSYW5nZShyZWdleCwgcmFuZ2UsIGZ1bmN0aW9uKFxuICAgICAgICAgIG9iajogQnVmZmVyU2NhblJlc3VsdCxcbiAgICAgICAgKSB7XG4gICAgICAgICAgcmV0dXJuIGVkaXRvci5zZXRUZXh0SW5CdWZmZXJSYW5nZShcbiAgICAgICAgICAgIG9iai5yYW5nZSxcbiAgICAgICAgICAgIGZvcm1hdFRhYmxlKG9iai5tYXRjaCwgc2V0dGluZ3MpLFxuICAgICAgICAgIClcbiAgICAgICAgfSksXG4gICAgICApLFxuICAgIClcbiAgfVxufVxuIl19
--------------------------------------------------------------------------------
/menus/markdown-table-formatter.cson:
--------------------------------------------------------------------------------
1 | # See https://atom.io/docs/latest/creating-a-package#menus for more details
2 | 'menu': [
3 | {
4 | 'label': 'Packages'
5 | 'submenu': [
6 | 'label': 'Markdown Table Formatter'
7 | 'submenu': [
8 | { 'label': 'Format', 'command': 'markdown-table-formatter:format' }
9 | ]
10 | ]
11 | }
12 | ]
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "markdown-table-formatter",
3 | "main": "./lib/markdown-table-formatter",
4 | "version": "2.9.2",
5 | "description": "A simple markdown plugin to format tables.",
6 | "repository": "https://github.com/fcrespo82/atom-markdown-table-formatter",
7 | "license": "MIT",
8 | "engines": {
9 | "atom": ">=1.25.0 <2.0.0"
10 | },
11 | "atomTestRunner": "./node_modules/atom-ts-spec-runner/runner.js",
12 | "scripts": {
13 | "build": "tsc -p .",
14 | "prettier": "prettier --write '**/*.ts'",
15 | "prettier-check": "prettier -l '**/*.ts'",
16 | "typecheck": "tsc --noEmit -p . && tsc --noEmit -p ./spec",
17 | "lint": "tslint --project . && tslint --project ./spec",
18 | "test": "npm run typecheck && npm run lint && npm run prettier-check"
19 | },
20 | "dependencies": {
21 | "wcwidth": "1.*",
22 | "xregexp": "^4.1.1"
23 | },
24 | "devDependencies": {
25 | "@types/atom": "~1.25.0",
26 | "@types/chai": "^4.1.2",
27 | "@types/mocha": "^2.2.48",
28 | "@types/wcwidth": "^1.0.0",
29 | "@types/xregexp": "^3.0.29",
30 | "atom-haskell-tslint-rules": "^0.2.2",
31 | "atom-ts-spec-runner": "^1.0.0",
32 | "chai": "^4.1.2",
33 | "mocha": "^5.0.4",
34 | "prettier": "^1.11.1",
35 | "ts-node": "^5.0.1",
36 | "tslint": "^5.9.1",
37 | "typescript": "^2.7.2",
38 | "xregexp": "^4.1.1"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fcrespo82/atom-markdown-table-formatter/9a8d7cdac38129176eab56eb8ac3b0045731fcb4/settings.png
--------------------------------------------------------------------------------
/spec/atom.d.ts:
--------------------------------------------------------------------------------
1 | export {}
2 | declare module 'atom' {
3 | interface TextBuffer {
4 | setPreferredLineEnding(e: string): void
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/spec/duplicate-tests.spec.ts:
--------------------------------------------------------------------------------
1 | import { loadFixture } from './utils'
2 | import { expect } from 'chai'
3 |
4 | describe('Duplicate tests', () => {
5 | function test(name: string) {
6 | it(`do not exist on ${name}`, () => {
7 | const res = loadFixture(name)
8 | res.forEach((x, i) => {
9 | expect(res.findIndex(y => y.input === x.input)).to.be.equal(
10 | i,
11 | `duplicate test in ${name}[${i}]: "${x.input}"`,
12 | )
13 | })
14 | })
15 | }
16 | test('test-tables')
17 | test('test-tables-pref-line-len')
18 | })
19 |
--------------------------------------------------------------------------------
/spec/editor.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai'
2 | import { TextEditor } from 'atom'
3 | import {
4 | testFormat,
5 | testSuite,
6 | loadPackage,
7 | settings,
8 | loadFixture,
9 | } from './utils'
10 | import nonTables = require('./tables/non-tables')
11 | import { sep } from 'path'
12 |
13 | const testTables = loadFixture('test-tables')
14 |
15 | describe('editor tests', function() {
16 | beforeEach(async () => {
17 | await loadPackage()
18 | })
19 |
20 | const runEditorTests = function(
21 | grammar: string,
22 | fixture: string,
23 | scope: string,
24 | inopts: {
25 | addGrammar?: boolean
26 | selectBeforeTest?: boolean
27 | crlf?: '\r\n' | '\n'
28 | } = {},
29 | ) {
30 | const defopts = {
31 | addGrammar: true,
32 | selectBeforeTest: false,
33 | crlf: '\n',
34 | }
35 | const opts = { ...inopts, ...defopts }
36 |
37 | return describe(`editor tests for ${grammar} with ${JSON.stringify(
38 | opts.crlf,
39 | )}`, function() {
40 | let editor: TextEditor
41 |
42 | function editorFormat(input: string) {
43 | editor.setText(input)
44 | if (opts.selectBeforeTest) {
45 | editor.setSelectedBufferRange(editor.getBuffer().getRange())
46 | }
47 | atom.commands.dispatch(
48 | atom.views.getView(editor),
49 | 'markdown-table-formatter:format',
50 | )
51 | return editor.getText()
52 | }
53 |
54 | function modCrLf(x: string) {
55 | return x.replace(/\n/g, opts.crlf)
56 | }
57 |
58 | const test = testFormat(editorFormat, modCrLf)
59 |
60 | beforeEach(async function() {
61 | await atom.packages.activatePackage(grammar)
62 | editor = (await atom.workspace.open(
63 | `${__dirname}${sep}fixtures${sep}${fixture}`,
64 | )) as TextEditor
65 | expect(editor).to.exist
66 | editor.getBuffer().setPreferredLineEnding(opts.crlf)
67 | if (opts.addGrammar) {
68 | expect(
69 | atom.commands.dispatch(
70 | atom.views.getView(editor),
71 | 'markdown-table-formatter:enable-for-current-scope',
72 | ),
73 | ).to.be.ok
74 | }
75 | })
76 |
77 | it(`should ${
78 | opts.addGrammar ? '' : 'NOT '
79 | }have ${scope} in grammarScopes`, function() {
80 | if (opts.addGrammar) {
81 | return expect(settings('markdownGrammarScopes')).to.contain(scope)
82 | } else {
83 | return expect(settings('markdownGrammarScopes')).not.to.contain(scope)
84 | }
85 | })
86 |
87 | testSuite(test)
88 |
89 | it("shouldn't try to format non-tables", () =>
90 | nonTables.map((nonTable: string) =>
91 | test({ input: nonTable, expected: nonTable }),
92 | ))
93 |
94 | describe('Tables at the end of document', function() {
95 | const modTest = testFormat(editorFormat, (text, rand) =>
96 | modCrLf(
97 | nonTables[Math.floor(rand * nonTables.length)] + opts.crlf + text,
98 | ),
99 | )
100 | testSuite(modTest)
101 | })
102 |
103 | describe('Tables at the beginning of document', function() {
104 | const modTest = testFormat(editorFormat, (text, rand) =>
105 | modCrLf(
106 | text + opts.crlf + nonTables[Math.floor(rand * nonTables.length)],
107 | ),
108 | )
109 | testSuite(modTest)
110 | })
111 |
112 | it('should properly format large text', function() {
113 | let edtext = ''
114 | let expected = ''
115 | for (const table of testTables) {
116 | const text =
117 | nonTables[Math.floor(Math.random() * nonTables.length + 1)] || ''
118 | edtext += opts.crlf + text + opts.crlf + table.input
119 | expected += opts.crlf + text + opts.crlf + table.expected
120 | }
121 | test({ input: edtext, expected })
122 | })
123 | })
124 | }
125 |
126 | runEditorTests('language-gfm', 'empty.md', 'source.gfm')
127 | runEditorTests('language-text', 'empty.text', 'text.plain.null-grammar')
128 | runEditorTests('language-gfm', 'empty.md', 'source.gfm', { crlf: '\r\n' })
129 | runEditorTests('language-text', 'empty.text', 'text.plain.null-grammar', {
130 | crlf: '\r\n',
131 | })
132 | runEditorTests('language-text', 'empty.text', 'text.plain.null-grammar', {
133 | addGrammar: false,
134 | selectBeforeTest: true,
135 | })
136 | })
137 |
--------------------------------------------------------------------------------
/spec/fixtures/empty.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fcrespo82/atom-markdown-table-formatter/9a8d7cdac38129176eab56eb8ac3b0045731fcb4/spec/fixtures/empty.md
--------------------------------------------------------------------------------
/spec/fixtures/empty.text:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fcrespo82/atom-markdown-table-formatter/9a8d7cdac38129176eab56eb8ac3b0045731fcb4/spec/fixtures/empty.text
--------------------------------------------------------------------------------
/spec/fixtures/test-tables-pref-line-len.md:
--------------------------------------------------------------------------------
1 |
2 | # Input
3 | | aaa | aaa | aaa | aaa | aaa | aaaaaaa |
4 | | --- | --- | --- | --- | --- | --------------------------------------------------------- |
5 | | bbb | bbb | bbb | bbb | bbb | bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb |
6 |
7 | # Expected
8 | | aaa | aaa | aaa | aaa | aaa | aaaaaaa |
9 | |:----|:----|:----|:----|:----|:-----------------------------------------------|
10 | | bbb | bbb | bbb | bbb | bbb | bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb |
11 |
12 | # Input
13 | |Coffee||
14 | |:---|:---|
15 | |Origin/Name|[prompt:Origin/Name]|
16 | |Brew method|[list:Brew Method|AeroPress|Chemex|Drip|Espresso|French Press|Pour Over|Other]|
17 | |Brewer|[prompt:Who brewed it?]|
18 | |Rating|[list:Rating|★☆☆|★★☆|★★★]|
19 | |Notes|[prompt:Notes]|
20 |
21 | # Expected
22 | | Coffee | |
23 | |:------------|:---------------------------------------------------------------|
24 | | Origin/Name | [prompt:Origin/Name] |
25 | | Brew method | [list:Brew Method|AeroPress|Chemex|Drip|Espresso|French Press|Pour Over|Other] |
26 | | Brewer | [prompt:Who brewed it?] |
27 | | Rating | [list:Rating|★☆☆|★★☆|★★★] |
28 | | Notes | [prompt:Notes] |
29 |
--------------------------------------------------------------------------------
/spec/fixtures/test-tables.md:
--------------------------------------------------------------------------------
1 |
2 | # Input
3 | |Left|Center|Right|
4 | |:-|:-:|-:|
5 | |1|2|3|
6 |
7 | # Expected
8 | | Left | Center | Right |
9 | |:-----|:------:|------:|
10 | | 1 | 2 | 3 |
11 |
12 | # Input
13 | | h1 h1| h2 | h3 |
14 | |-|-|-|
15 | | data1 | data2 | data3 |
16 |
17 | # Expected
18 | | h1 h1 | h2 | h3 |
19 | |:------|:------|:------|
20 | | data1 | data2 | data3 |
21 |
22 | # Input
23 | h1 | h2 | h3
24 | -|-|-
25 | data-1 | data-2 | data-3
26 |
27 | # Expected
28 | | h1 | h2 | h3 |
29 | |:-------|:-------|:-------|
30 | | data-1 | data-2 | data-3 |
31 |
32 | # Input
33 | -|-|-
34 | a|b|c
35 |
36 | # Expected
37 | |:--|:--|:--|
38 | | a | b | c |
39 |
40 | # Input
41 | | Header 1 | Header 2 | Header 3 |
42 | |----|---|-|
43 | | data1a | Data is longer than header | 1 |
44 | | d1b | add a cell|
45 | |lorem|ipsum|3|
46 | | | empty outside cells
47 | | skip| | 5 |
48 | | six | Morbi purus | 6 |
49 |
50 | # Expected
51 | | Header 1 | Header 2 | Header 3 |
52 | |:---------|:---------------------------|:---------|
53 | | data1a | Data is longer than header | 1 |
54 | | d1b | add a cell | |
55 | | lorem | ipsum | 3 |
56 | | | empty outside cells | |
57 | | skip | | 5 |
58 | | six | Morbi purus | 6 |
59 |
60 | # Input
61 | |teste-3|
62 | |---|
63 | |other|
64 |
65 | # Expected
66 | | teste-3 |
67 | |:--------|
68 | | other |
69 |
70 | # Input
71 | |outro
72 | |-
73 | |teste
74 |
75 | # Expected
76 | | outro |
77 | |:------|
78 | | teste |
79 |
80 | # Input
81 | outro|
82 | :-:|
83 | teste|
84 |
85 | # Expected
86 | | outro |
87 | |:-----:|
88 | | teste |
89 |
90 | # Input
91 | First Header | Second Header
92 | ------------- | -------------
93 | Content Cell | Content Cell
94 | Content Cell | Content Cell
95 |
96 | # Expected
97 | | First Header | Second Header |
98 | |:-------------|:--------------|
99 | | Content Cell | Content Cell |
100 | | Content Cell | Content Cell |
101 |
102 | # Input
103 | | First Header | Second Header |
104 | | ------------- | ------------- |
105 | | Content Cell | Content Cell |
106 | | Content Cell | Content Cell |
107 |
108 | # Expected
109 | | First Header | Second Header |
110 | |:-------------|:--------------|
111 | | Content Cell | Content Cell |
112 | | Content Cell | Content Cell |
113 |
114 | # Input
115 | | Item | Value |
116 | | --------- | -----:|
117 | | Computer | $1600 |
118 | | Phone | $12 |
119 | | Pipe | $1 |
120 |
121 | # Expected
122 | | Item | Value |
123 | |:---------|------:|
124 | | Computer | $1600 |
125 | | Phone | $12 |
126 | | Pipe | $1 |
127 |
128 | # Input
129 | | Function name | Description |
130 | | ------------- | ------------------------------ |
131 | | `help()` | Display the help window. |
132 | | `destroy()` | **Destroy your computer!** |
133 |
134 | # Expected
135 | | Function name | Description |
136 | |:--------------|:---------------------------|
137 | | `help()` | Display the help window. |
138 | | `destroy()` | **Destroy your computer!** |
139 |
140 | # Input
141 | | Left align | Right align | Center align |
142 | |:-----------|------------:|:------------:|
143 | | This | This | This |
144 | | column | column | column |
145 | | will | will | will |
146 | | be | be | be |
147 | | left | right | center |
148 | | aligned | aligned | aligned |
149 |
150 | # Expected
151 | | Left align | Right align | Center align |
152 | |:-----------|------------:|:------------:|
153 | | This | This | This |
154 | | column | column | column |
155 | | will | will | will |
156 | | be | be | be |
157 | | left | right | center |
158 | | aligned | aligned | aligned |
159 |
160 | # Input
161 | |Left align|Right align|Center align|
162 | |:---------|----------:|:----------:|
163 | |This|This|This|
164 | |column|column|column|
165 | |will|will|will|
166 | |be|be|be|
167 | |left|right|center|
168 | |aligned|aligned|aligned|
169 |
170 | # Expected
171 | | Left align | Right align | Center align |
172 | |:-----------|------------:|:------------:|
173 | | This | This | This |
174 | | column | column | column |
175 | | will | will | will |
176 | | be | be | be |
177 | | left | right | center |
178 | | aligned | aligned | aligned |
179 |
180 | # Input
181 | | Name | Description |
182 | | ------------- | ----------- |
183 | | Help | Display the help window.|
184 | | Close | Closes a window |
185 |
186 | # Expected
187 | | Name | Description |
188 | |:------|:-------------------------|
189 | | Help | Display the help window. |
190 | | Close | Closes a window |
191 |
192 | # Input
193 | | Name | Description |
194 | | ------------- | ----------- |
195 | | Help | ~~Display the~~ help window.|
196 | | Close | _Closes_ a window |
197 |
198 | # Expected
199 | | Name | Description |
200 | |:------|:-----------------------------|
201 | | Help | ~~Display the~~ help window. |
202 | | Close | _Closes_ a window |
203 |
204 | # Input
205 | | Left-Aligned | Center Aligned | Right Aligned |
206 | | :------------ |:---------------:| -----:|
207 | | col 3 is | some wordy text | $1600 |
208 | | col 2 is | centered | $12 |
209 | | zebra stripes | are neat | $1 |
210 |
211 | # Expected
212 | | Left-Aligned | Center Aligned | Right Aligned |
213 | |:--------------|:---------------:|--------------:|
214 | | col 3 is | some wordy text | $1600 |
215 | | col 2 is | centered | $12 |
216 | | zebra stripes | are neat | $1 |
217 |
218 | # Input
219 | | First Header | Second Header | Third Header |
220 | | :----------- | :-----------: | -------------------: |
221 | | First row | Data | Very long data entry |
222 | | Second row | **Cell** | *Cell* |
223 |
224 | # Expected
225 | | First Header | Second Header | Third Header |
226 | |:-------------|:-------------:|---------------------:|
227 | | First row | Data | Very long data entry |
228 | | Second row | **Cell** | *Cell* |
229 |
230 | # Input
231 | |Coffee||
232 | |:---|:---|
233 | |Origin/Name|[prompt:Origin/Name]|
234 | |Brew method|[list:Brew Method|AeroPress|Chemex|Drip|Espresso|French Press|Pour Over|Other]|
235 | |Brewer|[prompt:Who brewed it?]|
236 | |Rating|[list:Rating|★☆☆|★★☆|★★★]|
237 | |Notes|[prompt:Notes]|
238 |
239 | # Expected
240 | | Coffee | |
241 | |:------------|:-------------------------------------------------------------------------------|
242 | | Origin/Name | [prompt:Origin/Name] |
243 | | Brew method | [list:Brew Method|AeroPress|Chemex|Drip|Espresso|French Press|Pour Over|Other] |
244 | | Brewer | [prompt:Who brewed it?] |
245 | | Rating | [list:Rating|★☆☆|★★☆|★★★] |
246 | | Notes | [prompt:Notes] |
247 |
248 | # Input
249 | code|描述|详细解释
250 | :-|:-|:-
251 | 200|成功|成功
252 | 400|错误请求|该请求是无效的,详细的错误信息会说明原因
253 | 401|验证错误|验证失败,详细的错误信息会说明原因
254 | 403|被拒绝|被拒绝调用,详细的错误信息会说明原因
255 | 404|无法找到|资源不存在
256 | 429|过多的请求|超出了调用频率限制,详细的错误信息会说明原因
257 | 500|内部服务错误|服务器内部出错了,请联系我们尽快解决问题
258 | 504|内部服务响应超时|服务器在运行,本次请求响应超时,请稍后重试
259 |
260 | # Expected
261 | | code | 描述 | 详细解释 |
262 | |:-----|:-----------------|:---------------------------------------------|
263 | | 200 | 成功 | 成功 |
264 | | 400 | 错误请求 | 该请求是无效的,详细的错误信息会说明原因 |
265 | | 401 | 验证错误 | 验证失败,详细的错误信息会说明原因 |
266 | | 403 | 被拒绝 | 被拒绝调用,详细的错误信息会说明原因 |
267 | | 404 | 无法找到 | 资源不存在 |
268 | | 429 | 过多的请求 | 超出了调用频率限制,详细的错误信息会说明原因 |
269 | | 500 | 内部服务错误 | 服务器内部出错了,请联系我们尽快解决问题 |
270 | | 504 | 内部服务响应超时 | 服务器在运行,本次请求响应超时,请稍后重试 |
271 |
272 | # Input
273 | заголовок|таблицы
274 | -|-
275 | тело|таблицы
276 | продолжение|тела
277 |
278 | # Expected
279 | | заголовок | таблицы |
280 | |:------------|:--------|
281 | | тело | таблицы |
282 | | продолжение | тела |
283 |
284 | # Input (regression test for #16)
285 | | test | table | with| many | columns |
286 | |-|-|-|-|-
287 | |asd
288 | |dsa
289 |
290 | # Expected
291 | | test | table | with | many | columns |
292 | |:-----|:------|:-----|:-----|:--------|
293 | | asd | | | | |
294 | | dsa | | | | |
295 |
296 | # Input (regression test for #17)
297 | | **Name** | **Unicode** | **Unicode Name** | **ASCII Dec** |
298 | |:----------------|:--------------------------------------|:------------------------------------------------------|:---------------|
299 | | Digits | The code points U+0030 through U+0039 | DIGIT ZERO through DIGIT NINE | 48 through 57 |
300 | | CAPITAL-LETTERS | The code points U+0041 through U+005A | LATIN CAPITAL LETTER A through LATIN CAPITAL LETTER Z | 65 through 90 |
301 | | SMALL-LETTERS | The code points U+0061 through U+007A | LATIN SMALL LETTER A through LATIN SMALL LETTER Z | 97 through 122 |
302 |
303 | # Expected
304 | | **Name** | **Unicode** | **Unicode Name** | **ASCII Dec** |
305 | |:----------------|:--------------------------------------|:------------------------------------------------------|:---------------|
306 | | Digits | The code points U+0030 through U+0039 | DIGIT ZERO through DIGIT NINE | 48 through 57 |
307 | | CAPITAL-LETTERS | The code points U+0041 through U+005A | LATIN CAPITAL LETTER A through LATIN CAPITAL LETTER Z | 65 through 90 |
308 | | SMALL-LETTERS | The code points U+0061 through U+007A | LATIN SMALL LETTER A through LATIN SMALL LETTER Z | 97 through 122 |
309 |
310 | # Input (regression test for #24)
311 | |
312 | |-|-|-|-|
313 | |table | with |empty |header|
314 |
315 | # Expected
316 | | | | | |
317 | |:------|:-----|:------|:-------|
318 | | table | with | empty | header |
319 |
--------------------------------------------------------------------------------
/spec/format.spec.ts:
--------------------------------------------------------------------------------
1 | import { formatTable } from '../src/formatTable'
2 | import { testFormat, testSuite, loadPackage } from './utils'
3 | import { regex } from '../src/regex'
4 |
5 | describe('format tests', function() {
6 | beforeEach(async () => {
7 | await loadPackage()
8 | })
9 |
10 | const test = testFormat(function(input: string) {
11 | regex.lastIndex = 0
12 | const r = regex.exec(input)
13 | if (r) return formatTable(r)
14 | else return 'No regex match'
15 | })
16 |
17 | describe('default options', function() {
18 | beforeEach(function() {
19 | atom.config.set('markdown-table-formatter.spacePadding', 1)
20 | atom.config.set('markdown-table-formatter.keepFirstAndLastPipes', true)
21 | atom.config.set(
22 | 'markdown-table-formatter.defaultTableJustification',
23 | 'Left',
24 | )
25 | })
26 |
27 | testSuite(test)
28 | })
29 | })
30 |
--------------------------------------------------------------------------------
/spec/global.d.ts:
--------------------------------------------------------------------------------
1 | interface ObjectConstructor {
2 | keys(o: { [Key in K]: any }): K[]
3 | entries(o: { [Key in K]: T }): [K, T][]
4 | }
5 |
--------------------------------------------------------------------------------
/spec/markdown-table-formatter.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai'
2 | import { loadPackage } from './utils'
3 |
4 | describe('package', function() {
5 | it('should activate', async () => {
6 | await loadPackage()
7 | expect(atom.packages.isPackageActive('markdown-table-formatter')).to.equal(
8 | true,
9 | )
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/spec/regex.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai'
2 | import nonTables = require('./tables/non-tables')
3 | import { regex } from '../src/regex'
4 | import { loadFixture } from './utils'
5 |
6 | const testTables = loadFixture('test-tables')
7 |
8 | describe('regex tests', function() {
9 | it('should match the regex', function() {
10 | for (const table of testTables) {
11 | table.input.match(regex)
12 | expect(table.input).to.match(regex)
13 | }
14 | })
15 |
16 | it('should NOT match the regex', function() {
17 | for (const table of nonTables) {
18 | table.match(regex)
19 | expect(table).not.to.match(regex)
20 | }
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/spec/tables/non-tables.ts:
--------------------------------------------------------------------------------
1 | export = [
2 | `\
3 | aaa|dafdas|adfas
4 | ----\t| ----\t| ---
5 | asdf|asadf|sdfas
6 | \
7 | `,
8 |
9 | `\
10 | Here’s the original Coffee Log after decoding:
11 | dayone://post?entry=#coffee #log
12 | If you wish, you can add a leading and tailing pipe to each line of the table.
13 | Use the form that you like. As an illustration, this will give the same result as above:
14 | Note: A table need at least one pipe on each line for Markdown Extra to parse
15 | it correctly. This means that the only way to create a one-column table is to
16 | add a leading or a tailing pipe, or both of them, to each line.
17 | The align HTML attribute is applied to each cell of the concerned column.
18 | You can apply span-level formatting to the content of each cell using regular
19 | Markdown syntax:
20 | First, let’s talk about what Markdown tables look like. The full explanation
21 | can be found at either the PHP Markdown Extra or MultiMarkdown sites, but the
22 | nutshell version is this:
23 | The pipe (|) characters separate the columns. The alignment of each column is
24 | determined by the placement of the colons in the separator line. A colon at the
25 | left end only (or no colon at all) means left aligned; a colon at the right end
26 | only means right aligned; a colon at each end means centered.
27 | It’s nice to have the text version of the table spaced as I have it above with
28 | the pipes stacked on top of one another and the text aligned the way the separator
29 | line says. It’s very time consuming to type it in that way, though, so my tables
30 | tend to look more like this:
31 | which is easy to type, but hard to read and edit. So the first command I made
32 | is a Python script that takes an ugly text table and makes it pretty. Here’s the code.\
33 | `,
34 |
35 | `\
36 | 1 #!/usr/bin/python
37 | 2
38 | 3 import sys
39 | 4
40 | 5 def just(string, type, n):
41 | 6 "Justify a string to length n according to type."
42 | 7
43 | 8 if type == '::':
44 | 9 return string.center(n)
45 | 10 elif type == '-:':
46 | 11 return string.rjust(n)
47 | 12 elif type == ':-':
48 | 13 return string.ljust(n)
49 | 14 else:
50 | 15 return string
51 | 16
52 | 17
53 | 18 def normtable(text):
54 | 19 "Aligns the vertical bars in a text table."
55 | 20
56 | 21 # Start by turning the text into a list of lines.
57 | 22 lines = text.splitlines()
58 | 23 rows = len(lines)
59 | 24
60 | 25 # Figure out the cell formatting.
61 | 26 # First, find the separator line.
62 | 27 for i in range(rows):
63 | 28 if set(lines[i]).issubset('|:.-'):
64 | 29 formatline = lines[i]
65 | 30 formatrow = i
66 | 31 break
67 | 32
68 | 33 # Delete the separator line from the content.
69 | 34 del lines[formatrow]
70 | 35
71 | 36 # Determine how each column is to be justified.
72 | 37 formatline = formatline.strip('| ')
73 | 38 fstrings = formatline.split('|')
74 | 39 justify = []
75 | 40 for cell in fstrings:
76 | 41 ends = cell[0] + cell[-1]
77 | 42 if ends == '::':
78 | 43 justify.append('::')
79 | 44 elif ends == '-:':
80 | 45 justify.append('-:')
81 | 46 else:
82 | 47 justify.append(':-')
83 | 48
84 | 49 # Assume the number of columns in the separator line is the number
85 | 50 # for the entire table.
86 | 51 columns = len(justify)
87 | 52
88 | 53 # Extract the content into a matrix.
89 | 54 content = []
90 | 55 for line in lines:
91 | 56 line = line.strip('| ')
92 | 57 cells = line.split('|')
93 | 58 # Put exactly one space at each end as "bumpers."
94 | 59 linecontent = [ ' ' + x.strip() + ' ' for x in cells ]
95 | 60 content.append(linecontent)
96 | 61
97 | 62 # Append cells to rows that don't have enough.
98 | 63 rows = len(content)
99 | 64 for i in range(rows):
100 | 65 while len(content[i]) < columns:
101 | 66 content[i].append('')
102 | 67
103 | 68 # Get the width of the content in each column. The minimum width will
104 | 69 # be 2, because that's the shortest length of a formatting string and
105 | 70 # because that matches an empty column with "bumper" spaces.
106 | 71 widths = [2] * columns
107 | 72 for row in content:
108 | 73 for i in range(columns):
109 | 74 widths[i] = max(len(row[i]), widths[i])
110 | 75
111 | 76 # Add whitespace to make all the columns the same width and
112 | 77 formatted = []
113 | 78 for row in content:
114 | 79 formatted.append('|' + '|'.join([ just(s, t, n) for (s, t, n) in zip(row, justify, widths) ]) + '|')
115 | 80
116 | 81 # Recreate the format line with the appropriate column widths.
117 | 82 formatline = '|' + '|'.join([ s[0] + '-'*(n-2) + s[-1] for (s, n) in zip(justify, widths) ]) + '|'
118 | 83
119 | 84 # Insert the formatline back into the table.
120 | 85 formatted.insert(formatrow, formatline)
121 | 86
122 | 87 # Return the formatted table.
123 | 88 return '\\n'.join(formatted)
124 | 89
125 | 90
126 | 91 # Read the input, process, and print.
127 | 92 unformatted = sys.stdin.read()
128 | 93 print normtable(unformatted)\
129 | `,
130 | ]
131 |
--------------------------------------------------------------------------------
/spec/tables/test-tables-default.ts:
--------------------------------------------------------------------------------
1 | export = {
2 | Left: [
3 | {
4 | input: `\
5 | |First Header|Second Header|Third Header|
6 | |-|-:|:-:|
7 | |Content|Content|Content|
8 | |Content|Content|Content|
9 | \
10 | `,
11 | expected: `\
12 | | First Header | Second Header | Third Header |
13 | |:-------------|--------------:|:------------:|
14 | | Content | Content | Content |
15 | | Content | Content | Content |
16 | \
17 | `,
18 | },
19 | ],
20 | Center: [
21 | {
22 | input: `\
23 | |First Header|Second Header|Third Header|
24 | |-|:-|::|
25 | |Content|Content|Content|
26 | |Content|Content|Content|
27 | \
28 | `,
29 | expected: `\
30 | | First Header | Second Header | Third Header |
31 | |:------------:|:--------------|:------------:|
32 | | Content | Content | Content |
33 | | Content | Content | Content |
34 | \
35 | `,
36 | },
37 | ],
38 | Right: [
39 | {
40 | input: `\
41 | |First Header|Second Header|
42 | |-|:-|
43 | |Content|Content|
44 | |Content|Content|
45 | \
46 | `,
47 | expected: `\
48 | | First Header | Second Header |
49 | |-------------:|:--------------|
50 | | Content | Content |
51 | | Content | Content |
52 | \
53 | `,
54 | },
55 | ],
56 | }
57 |
--------------------------------------------------------------------------------
/spec/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "extends": "../tsconfig.json",
4 | "include": ["./**.ts", "../src/**.ts", "../src/atom.d.ts"],
5 | "exclude": ["../node_modules"]
6 | }
7 |
--------------------------------------------------------------------------------
/spec/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tslint.json",
3 | "rules": {
4 | "no-unused-expression": false
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/spec/utils.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai'
2 | import testTablesDefault = require('./tables/test-tables-default')
3 | import { join } from 'path'
4 | import { readFileSync } from 'fs'
5 |
6 | const pkg = join(__dirname, '..')
7 | const testTables = loadFixture('test-tables')
8 | const testTablesPrefLineLen = loadFixture('test-tables-pref-line-len')
9 |
10 | export interface TestData {
11 | input: string
12 | expected: string
13 | }
14 |
15 | export function testFormat(
16 | formatter: (x: string) => string,
17 | modifier?: (x: string, rand: number) => string,
18 | ) {
19 | return function({ input, expected }: TestData) {
20 | if (modifier === undefined) {
21 | modifier = x => x
22 | }
23 | const rand = Math.random()
24 | expect(formatter(modifier(input, rand))).to.equal(modifier(expected, rand))
25 | }
26 | }
27 |
28 | function testSuiteSingle(test: (arg: TestData) => void, tbls: TestData[]) {
29 | it('should properly format these tables', () => tbls.forEach(test))
30 | }
31 |
32 | export function testSuite(test: (arg: TestData) => void) {
33 | testSuiteSingle(test, testTables)
34 |
35 | for (const just of Object.keys(testTablesDefault)) {
36 | const tbls = testTablesDefault[just]
37 | describe(`Default ${just} justification tests`, function() {
38 | beforeEach(() =>
39 | atom.config.set(
40 | 'markdown-table-formatter.defaultTableJustification',
41 | just,
42 | ),
43 | )
44 | testSuiteSingle(test, tbls)
45 | afterEach(() => {
46 | atom.config.unset('markdown-table-formatter.defaultTableJustification')
47 | })
48 | })
49 | }
50 |
51 | describe('limitLastColumnPadding tests', function() {
52 | beforeEach(function() {
53 | atom.config.set('markdown-table-formatter.limitLastColumnPadding', true)
54 | })
55 |
56 | testSuiteSingle(test, testTablesPrefLineLen)
57 |
58 | afterEach(function() {
59 | atom.config.set('markdown-table-formatter.limitLastColumnPadding', false)
60 | })
61 | })
62 | }
63 |
64 | export async function loadPackage() {
65 | await atom.packages.activatePackage(pkg)
66 | }
67 |
68 | export function settings(name: string) {
69 | return atom.config.get(`markdown-table-formatter.${name}`)
70 | }
71 |
72 | export function loadFixture(name: string) {
73 | const fixture = join(__dirname, `fixtures/${name}.md`)
74 | const data = readFileSync(fixture, { encoding: 'utf8' })
75 |
76 | return data
77 | .split(/\n# Input*[^\n]*\n/g)
78 | .slice(1)
79 | .map(t => {
80 | const [input, expected] = t.split(/\n# Expected*[^\n]*\n/g)
81 | expect(input).to.exist
82 | expect(expected).to.exist
83 | return { input, expected }
84 | })
85 | }
86 |
--------------------------------------------------------------------------------
/src/atom.d.ts:
--------------------------------------------------------------------------------
1 | export {}
2 | declare module 'atom' {
3 | interface ConfigValues {
4 | 'markdown-table-formatter': {
5 | autoSelectEntireDocument: boolean
6 | spacePadding: number
7 | keepFirstAndLastPipes: boolean
8 | formatOnSave: boolean
9 | defaultTableJustification: 'Left' | 'Right' | 'Center'
10 | markdownGrammarScopes: string[]
11 | limitLastColumnPadding: boolean
12 | }
13 | 'markdown-table-formatter.autoSelectEntireDocument': boolean
14 | 'markdown-table-formatter.spacePadding': number
15 | 'markdown-table-formatter.keepFirstAndLastPipes': boolean
16 | 'markdown-table-formatter.formatOnSave': boolean
17 | 'markdown-table-formatter.defaultTableJustification':
18 | | 'Left'
19 | | 'Right'
20 | | 'Center'
21 | 'markdown-table-formatter.markdownGrammarScopes': string[]
22 | 'markdown-table-formatter.limitLastColumnPadding': boolean
23 | }
24 | interface Config {
25 | get(
26 | keyPath: T,
27 | options?: {
28 | sources?: string[]
29 | excludeSources?: string[]
30 | scope?: string[] | ScopeDescriptor
31 | },
32 | ): ConfigValues[T]
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------
1 | export const config = {
2 | formatOnSave: {
3 | type: 'boolean',
4 | default: true,
5 | description: 'Format tables when document is saved?',
6 | },
7 | autoSelectEntireDocument: {
8 | type: 'boolean',
9 | default: true,
10 | description: 'Select entire document if selection is empty',
11 | },
12 | spacePadding: {
13 | type: 'integer',
14 | default: 1,
15 | description:
16 | 'How many spaces between left and right of each column content',
17 | },
18 | keepFirstAndLastPipes: {
19 | type: 'boolean',
20 | default: true,
21 | description: `Keep first and last pipes "|" in table formatting. \
22 | Tables are easier to format when pipes are kept`,
23 | },
24 | defaultTableJustification: {
25 | type: 'string',
26 | default: 'Left',
27 | enum: ['Left', 'Center', 'Right'],
28 | description: `Defines the default justification for tables that have only a \
29 | \'-\' on the formatting line`,
30 | },
31 | markdownGrammarScopes: {
32 | type: 'array',
33 | default: ['source.gfm'],
34 | description: `File grammar scopes that will be considered Markdown by this package (comma-separated). \
35 | Run \'Markdown Table Formatter: Enable For Current Scope\' command to \
36 | add current editor grammar to this setting.`,
37 | items: {
38 | type: 'string',
39 | },
40 | },
41 | limitLastColumnPadding: {
42 | type: 'boolean',
43 | default: false,
44 | description: `Do not pad the last column to more than your editor\'s \
45 | preferredLineLength setting.`,
46 | },
47 | }
48 |
--------------------------------------------------------------------------------
/src/formatTable.ts:
--------------------------------------------------------------------------------
1 | import { getAllSettings } from './table-formatter'
2 | import wcswidth = require('wcwidth')
3 |
4 | function swidth(str: string) {
5 | // zero-width Unicode characters that we should ignore for
6 | // purposes of computing string "display" width
7 | const zwcrx = /[\u200B-\u200D\uFEFF\u00AD]/g
8 | const match = str.match(zwcrx)
9 | return wcswidth(str) - (match ? match.length : 0)
10 | }
11 |
12 | function padding(len: number, str: string = ' ') {
13 | return str.repeat(len)
14 | }
15 | const stripTailPipes = (str: string) => str.trim().replace(/(^\||\|$)/g, '')
16 | const splitCells = (str: string) => str.split('|')
17 | const addTailPipes = (str: string) => `|${str}|`
18 | const joinCells = (arr: string[]) => arr.join('|')
19 |
20 | const tableJustMap = {
21 | Left: ':-',
22 | Center: '::',
23 | Right: '-:',
24 | }
25 |
26 | export function formatTable(
27 | text: RegExpExecArray,
28 | settings = getAllSettings(),
29 | ) {
30 | const addTailPipesIfNeeded = settings.keepFirstAndLastPipes
31 | ? addTailPipes
32 | : (x: string) => x
33 |
34 | let formatline = text[2].trim()
35 | const headerline = text[1].trim()
36 |
37 | let formatrow: number
38 | let data: string
39 | if (headerline.length === 0) {
40 | formatrow = 0
41 | data = text[3]
42 | } else {
43 | formatrow = 1
44 | data = text[1] + text[3]
45 | }
46 | const lines = data.trim().split(/\r?\n/)
47 |
48 | const justify = splitCells(stripTailPipes(formatline)).map(cell => {
49 | const trimmed = cell.trim()
50 | const first = trimmed[0]
51 | const last = trimmed[trimmed.length - 1]
52 | const ends = (first || ':') + (last || '-')
53 | if (ends === '--') return tableJustMap[settings.defaultTableJustification]
54 | else return ends
55 | })
56 |
57 | const columns = justify.length
58 | const colArr: undefined[] = Array.from(Array(columns))
59 |
60 | const cellPadding = padding(settings.spacePadding)
61 |
62 | const content = lines.map(line => {
63 | const cells = splitCells(stripTailPipes(line))
64 | if (columns - cells.length > 0) {
65 | // pad rows to have `columns` cells
66 | cells.push(...Array(columns - cells.length).fill(''))
67 | } else if (columns - cells.length < 0) {
68 | // put all extra content into last cell
69 | cells[columns - 1] = joinCells(cells.slice(columns - 1))
70 | }
71 | return cells.map(cell => `${cellPadding}${cell.trim()}${cellPadding}`)
72 | })
73 |
74 | const widths = colArr.map((_x, i) =>
75 | Math.max(2, ...content.map(cells => swidth(cells[i]))),
76 | )
77 |
78 | if (settings.limitLastColumnPadding) {
79 | const preferredLineLength = atom.config.get('editor.preferredLineLength')
80 | const sum = (arr: number[]) => arr.reduce((x, y) => x + y, 0)
81 | const wsum = sum(widths)
82 | if (wsum > preferredLineLength) {
83 | const prewsum = sum(widths.slice(0, -1))
84 | widths[widths.length - 1] = Math.max(
85 | preferredLineLength - prewsum - widths.length - 1,
86 | 3,
87 | )
88 | // Need at least :-- for github to recognize a column
89 | }
90 | }
91 |
92 | const just = function(str: string, col: number) {
93 | const length = Math.max(widths[col] - swidth(str), 0)
94 | switch (justify[col]) {
95 | case '::':
96 | return padding(length / 2) + str + padding((length + 1) / 2)
97 | case '-:':
98 | return padding(length) + str
99 | case ':-':
100 | return str + padding(length)
101 | default:
102 | throw new Error(`Unknown column justification ${justify[col]}`)
103 | }
104 | }
105 |
106 | const formatted = content.map(cells =>
107 | addTailPipesIfNeeded(joinCells(colArr.map((_x, i) => just(cells[i], i)))),
108 | )
109 |
110 | formatline = addTailPipesIfNeeded(
111 | joinCells(
112 | colArr.map((_x, i) => {
113 | const [front, back] = justify[i]
114 | return front + padding(widths[i] - 2, '-') + back
115 | }),
116 | ),
117 | )
118 |
119 | formatted.splice(formatrow, 0, formatline)
120 |
121 | return (
122 | (formatrow === 0 && text[1] !== '' ? '\n' : '') +
123 | formatted.join('\n') +
124 | '\n'
125 | )
126 | }
127 |
--------------------------------------------------------------------------------
/src/markdown-table-formatter.ts:
--------------------------------------------------------------------------------
1 | import { TableFormatter } from './table-formatter'
2 | import { Disposable } from 'atom'
3 | export { config } from './config'
4 |
5 | let tableFormatter: TableFormatter
6 | let command: Disposable
7 |
8 | export function activate() {
9 | tableFormatter = new TableFormatter()
10 | // Register command to workspace
11 | command = atom.commands.add('atom-text-editor', {
12 | 'markdown-table-formatter:format': event => {
13 | const editor = event.currentTarget.getModel()
14 | tableFormatter.format(editor)
15 | },
16 | 'markdown-table-formatter:enable-for-current-scope'(event) {
17 | const editor = event.currentTarget.getModel()
18 | const scope = editor.getGrammar().scopeName
19 | const key = 'markdown-table-formatter.markdownGrammarScopes'
20 | const current = atom.config.get(key)
21 | if (!scope) {
22 | atom.notifications.addError('Could not determine editor grammar scope')
23 | } else if (current.includes(scope)) {
24 | atom.notifications.addWarning(`${scope} already considered Markdown`)
25 | } else {
26 | atom.config.set(key, [...current, scope])
27 | atom.notifications.addSuccess(
28 | `Successfully added ${scope} to Markdown scopes`,
29 | )
30 | }
31 | },
32 | })
33 | }
34 |
35 | export function deactivate() {
36 | command.dispose()
37 | tableFormatter.destroy()
38 | }
39 |
--------------------------------------------------------------------------------
/src/regex.ts:
--------------------------------------------------------------------------------
1 | import XRegExp = require('xregexp')
2 |
3 | export const regex = XRegExp(
4 | `\
5 | ( # header capture
6 | (?:
7 | (?:[^\\r\\n]*?\\|[^\\r\\n]*) # line w/ at least one pipe
8 | \\ * # maybe trailing whitespace
9 | )? # maybe header
10 | (?:\\r?\\n|^) # newline
11 | )
12 | ( # format capture
13 | (?:
14 | \\|\\ *(?::?-+:?|::)\\ * # format starting w/pipe
15 | |\\|?(?:\\ *(?::?-+:?|::)\\ *\\|)+ # or separated by pipe
16 | )
17 | (?:\\ *(?::?-+:?|::)\\ *)? # maybe w/o trailing pipe
18 | \\ * # maybe trailing whitespace
19 | \\r?\\n # newline
20 | )
21 | ( # body capture
22 | (?:
23 | (?:[^\\r\\n]*?\\|[^\\r\\n]*) # line w/ at least one pipe
24 | \\ * # maybe trailing whitespace
25 | (?:\\r?\\n|$) # newline
26 | )+ # at least one
27 | )
28 | `,
29 | 'gx',
30 | )
31 |
--------------------------------------------------------------------------------
/src/table-formatter.ts:
--------------------------------------------------------------------------------
1 | import { CompositeDisposable, Range, TextEditor, BufferScanResult } from 'atom'
2 | import { regex } from './regex'
3 | import { formatTable } from './formatTable'
4 |
5 | export function getAllSettings() {
6 | return atom.config.get('markdown-table-formatter')
7 | }
8 |
9 | export class TableFormatter {
10 | private readonly subscriptions = new CompositeDisposable()
11 | constructor() {
12 | atom.workspace.observeTextEditors(editor =>
13 | this.subscriptions.add(
14 | editor.getBuffer().onWillSave(() => {
15 | if (atom.config.get('markdown-table-formatter.formatOnSave')) {
16 | this.format(editor, true)
17 | }
18 | }),
19 | ),
20 | )
21 | }
22 |
23 | public destroy() {
24 | this.subscriptions.dispose()
25 | }
26 |
27 | public format(editor: TextEditor, force: boolean = false) {
28 | let selectionsRanges = editor.getSelectedBufferRanges()
29 | const settings = getAllSettings()
30 |
31 | const bufferRange = editor.getBuffer().getRange()
32 | const selectionsRangesEmpty = selectionsRanges.every(i => i.isEmpty())
33 | if (
34 | !settings.markdownGrammarScopes.includes(editor.getGrammar().scopeName) &&
35 | selectionsRangesEmpty
36 | ) {
37 | return undefined
38 | }
39 | if (force || (selectionsRangesEmpty && settings.autoSelectEntireDocument)) {
40 | selectionsRanges = [bufferRange]
41 | } else {
42 | selectionsRanges = selectionsRanges
43 | .filter(
44 | srange => !(srange.isEmpty() && settings.autoSelectEntireDocument),
45 | )
46 | .map(srange => {
47 | let { start } = bufferRange
48 | let { end } = bufferRange
49 | editor.scanInBufferRange(
50 | /^$/m,
51 | new Range(srange.start, bufferRange.end),
52 | ({ range }) => (end = range.start),
53 | )
54 | editor.backwardsScanInBufferRange(
55 | /^$/m,
56 | new Range(bufferRange.start, srange.end),
57 | ({ range }) => (start = range.start),
58 | )
59 | if (end.isLessThan(srange.end)) end = srange.end
60 | if (start.isGreaterThan(srange.start)) start = srange.start
61 | return new Range(start, end)
62 | })
63 | }
64 |
65 | return editor.getBuffer().transact(() =>
66 | selectionsRanges.map(range =>
67 | editor.backwardsScanInBufferRange(regex, range, function(
68 | obj: BufferScanResult,
69 | ) {
70 | return editor.setTextInBufferRange(
71 | obj.range,
72 | formatTable(obj.match, settings),
73 | )
74 | }),
75 | ),
76 | )
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2017",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "isolatedModules": false,
7 | "jsx": "react",
8 | "experimentalDecorators": true,
9 | "emitDecoratorMetadata": true,
10 | "declaration": false,
11 | "noImplicitAny": true,
12 | "noImplicitUseStrict": false,
13 | "removeComments": true,
14 | "noLib": false,
15 | "jsxFactory": "etch.dom",
16 | "preserveConstEnums": true,
17 | "suppressImplicitAnyIndexErrors": true,
18 | "outDir": "lib",
19 | "inlineSources": true,
20 | "inlineSourceMap": true,
21 | "strictNullChecks": true,
22 | "allowJs": false,
23 | "lib": ["dom", "es2017"],
24 | "strict": true,
25 | "importHelpers": false,
26 | "noFallthroughCasesInSwitch": true,
27 | "noImplicitReturns": true,
28 | "noUnusedLocals": true,
29 | "noUnusedParameters": true
30 | },
31 | "exclude": ["node_modules", "spec"],
32 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.js"],
33 | "compileOnSave": false,
34 | "experimentalDecorators": true
35 | }
36 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "atom-haskell-tslint-rules",
3 | "rules": {
4 | "no-unsafe-any": true,
5 | "space-before-function-paren": [true, "asyncArrow"],
6 | "align": false
7 | }
8 | }
9 |
--------------------------------------------------------------------------------