dtab 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,18 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dtab
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Delta Tab: config files made of tab-separated paths. One line is one path into a tree.
5
5
  Project-URL: Homepage, https://github.com/RyannDaGreat/dtab
6
6
  Requires-Python: >=3.8
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: fire
9
9
 
10
- <p align="center"><img src="https://raw.githubusercontent.com/RyannDaGreat/dtab/main/assets/logo.jpg" alt="dtab" width="640"></p>
10
+ <!-- <p align="center"><img src="https://raw.githubusercontent.com/RyannDaGreat/dtab/main/assets/logo.jpg" alt="dtab" width="640"></p> -->
11
+ <!-- <img width="6912" height="4188" alt="image" src="https://github.com/user-attachments/assets/db10af67-b7ba-4e42-a808-946c8cdcc2c7" /> -->
12
+ <img width="6912" height="2940" alt="image" src="https://github.com/user-attachments/assets/24f2187b-9e71-45e6-9a4b-427ad00e48c8" />
13
+
14
+
15
+
11
16
  <p align="center"><sub>Logo made with <a href="https://github.com/RyannDaGreat/SvelteLib">PowerRP</a>. <a href="https://ryanndagreat.github.io/SvelteLib/?repo=RyannDaGreat/dtab">Open it in PowerRP</a> to edit it (source: <code>assets/doc.json</code>).</sub></p>
12
17
 
13
18
  # dtab
@@ -32,7 +37,7 @@ dtab.parse(open("scene.dtab").read())
32
37
 
33
38
  - Less to look at. No braces, quotes, or commas between values. A file with its tabs aligned reads like pseudocode, and is easy to write by hand, even on paper.
34
39
  - Simple. Six rules, one pass, about 70 lines per implementation.
35
- - Everything is addressable. There are no lists, so every value has a dotted path: `config.deltas.l1.position.x` works with EasyDict in Python and plain property access in JavaScript.
40
+ - Everything is addressable. There are no lists, so every value has a path: `config.deltas.l1.position.x` with EasyDict in Python or plain property access in JavaScript, brackets for keys like `file.json`.
36
41
  - You choose the shape. Lines stack, and `c,d` writes one value under several keys, so the same tree can be written wide, deep, or on one line, trading horizontal space for vertical. These are the same file:
37
42
 
38
43
  ```
@@ -59,8 +64,9 @@ dtab.parse(open("scene.dtab").read())
59
64
  - An entry that starts with a space is a comment.
60
65
  - `$key` starts a multiline string. See below.
61
66
 
62
- Every value is a string. Cast the ones you need. Keys are identifiers (letters, digits, underscores),
63
- so attribute access like `config.deltas.l1` works with EasyDict and friends.
67
+ Every value is a string. Cast the ones you need. A key is any run of letters, digits, `_`, `.` and `-`,
68
+ so `file.json`, `2026-09-07` and `0` are keys. Keys that happen to be identifiers work as attributes
69
+ (`config.deltas.l1` with EasyDict and friends); the rest are reached with brackets (`config["file.json"]`).
64
70
 
65
71
  ## Multiline strings
66
72
 
@@ -92,8 +98,8 @@ the value. Unknown tags are plain text, never an error.
92
98
 
93
99
  - **Python**: `pip install dtab` then `import dtab`
94
100
  - **JavaScript**: `npm install deltatab` then `const dtab = require('deltatab')`, or `<script src="https://cdn.jsdelivr.net/npm/deltatab/dtab.js">` for `window.dtab`
95
- - **Vim**: `Plugin 'RyannDaGreat/dtab'` (Vundle) or `Plug 'RyannDaGreat/dtab'` (vim-plug), or paste `dtab.vim` into your vimrc. Highlights `*.dtab`, flags bad keys and trailing tabs, and gives the file a Δ icon in NERDTree if vim-devicons is installed.
96
- - **VS Code**: search "dtab" in Extensions, or `code --install-extension RyannDaGreat.dtab`. Highlighting plus a Δ file icon.
101
+ - **Vim**: `Plugin 'RyannDaGreat/dtab'` (Vundle) or `Plug 'RyannDaGreat/dtab'` (vim-plug), or paste `dtab.vim` into your vimrc. Highlights `*.dtab`, flags bad keys and trailing tabs, gives the file a Δ icon in NERDTree if vim-devicons is installed, and `:DtabPreview` opens a split showing the tree as JSON that follows your edits (needs `+python3`).
102
+ - **VS Code**: search "dtab" in Extensions, or `code --install-extension RyannDaGreat.dtab`. Highlighting, a Δ file icon, and a live JSON preview of the file beside it (the preview button in the title bar, or Cmd+K V).
97
103
 
98
104
  ## API
99
105
 
@@ -1,4 +1,9 @@
1
- <p align="center"><img src="https://raw.githubusercontent.com/RyannDaGreat/dtab/main/assets/logo.jpg" alt="dtab" width="640"></p>
1
+ <!-- <p align="center"><img src="https://raw.githubusercontent.com/RyannDaGreat/dtab/main/assets/logo.jpg" alt="dtab" width="640"></p> -->
2
+ <!-- <img width="6912" height="4188" alt="image" src="https://github.com/user-attachments/assets/db10af67-b7ba-4e42-a808-946c8cdcc2c7" /> -->
3
+ <img width="6912" height="2940" alt="image" src="https://github.com/user-attachments/assets/24f2187b-9e71-45e6-9a4b-427ad00e48c8" />
4
+
5
+
6
+
2
7
  <p align="center"><sub>Logo made with <a href="https://github.com/RyannDaGreat/SvelteLib">PowerRP</a>. <a href="https://ryanndagreat.github.io/SvelteLib/?repo=RyannDaGreat/dtab">Open it in PowerRP</a> to edit it (source: <code>assets/doc.json</code>).</sub></p>
3
8
 
4
9
  # dtab
@@ -23,7 +28,7 @@ dtab.parse(open("scene.dtab").read())
23
28
 
24
29
  - Less to look at. No braces, quotes, or commas between values. A file with its tabs aligned reads like pseudocode, and is easy to write by hand, even on paper.
25
30
  - Simple. Six rules, one pass, about 70 lines per implementation.
26
- - Everything is addressable. There are no lists, so every value has a dotted path: `config.deltas.l1.position.x` works with EasyDict in Python and plain property access in JavaScript.
31
+ - Everything is addressable. There are no lists, so every value has a path: `config.deltas.l1.position.x` with EasyDict in Python or plain property access in JavaScript, brackets for keys like `file.json`.
27
32
  - You choose the shape. Lines stack, and `c,d` writes one value under several keys, so the same tree can be written wide, deep, or on one line, trading horizontal space for vertical. These are the same file:
28
33
 
29
34
  ```
@@ -50,8 +55,9 @@ dtab.parse(open("scene.dtab").read())
50
55
  - An entry that starts with a space is a comment.
51
56
  - `$key` starts a multiline string. See below.
52
57
 
53
- Every value is a string. Cast the ones you need. Keys are identifiers (letters, digits, underscores),
54
- so attribute access like `config.deltas.l1` works with EasyDict and friends.
58
+ Every value is a string. Cast the ones you need. A key is any run of letters, digits, `_`, `.` and `-`,
59
+ so `file.json`, `2026-09-07` and `0` are keys. Keys that happen to be identifiers work as attributes
60
+ (`config.deltas.l1` with EasyDict and friends); the rest are reached with brackets (`config["file.json"]`).
55
61
 
56
62
  ## Multiline strings
57
63
 
@@ -83,8 +89,8 @@ the value. Unknown tags are plain text, never an error.
83
89
 
84
90
  - **Python**: `pip install dtab` then `import dtab`
85
91
  - **JavaScript**: `npm install deltatab` then `const dtab = require('deltatab')`, or `<script src="https://cdn.jsdelivr.net/npm/deltatab/dtab.js">` for `window.dtab`
86
- - **Vim**: `Plugin 'RyannDaGreat/dtab'` (Vundle) or `Plug 'RyannDaGreat/dtab'` (vim-plug), or paste `dtab.vim` into your vimrc. Highlights `*.dtab`, flags bad keys and trailing tabs, and gives the file a Δ icon in NERDTree if vim-devicons is installed.
87
- - **VS Code**: search "dtab" in Extensions, or `code --install-extension RyannDaGreat.dtab`. Highlighting plus a Δ file icon.
92
+ - **Vim**: `Plugin 'RyannDaGreat/dtab'` (Vundle) or `Plug 'RyannDaGreat/dtab'` (vim-plug), or paste `dtab.vim` into your vimrc. Highlights `*.dtab`, flags bad keys and trailing tabs, gives the file a Δ icon in NERDTree if vim-devicons is installed, and `:DtabPreview` opens a split showing the tree as JSON that follows your edits (needs `+python3`).
93
+ - **VS Code**: search "dtab" in Extensions, or `code --install-extension RyannDaGreat.dtab`. Highlighting, a Δ file icon, and a live JSON preview of the file beside it (the preview button in the title bar, or Cmd+K V).
88
94
 
89
95
  ## API
90
96
 
@@ -1,13 +1,18 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dtab
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Delta Tab: config files made of tab-separated paths. One line is one path into a tree.
5
5
  Project-URL: Homepage, https://github.com/RyannDaGreat/dtab
6
6
  Requires-Python: >=3.8
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: fire
9
9
 
10
- <p align="center"><img src="https://raw.githubusercontent.com/RyannDaGreat/dtab/main/assets/logo.jpg" alt="dtab" width="640"></p>
10
+ <!-- <p align="center"><img src="https://raw.githubusercontent.com/RyannDaGreat/dtab/main/assets/logo.jpg" alt="dtab" width="640"></p> -->
11
+ <!-- <img width="6912" height="4188" alt="image" src="https://github.com/user-attachments/assets/db10af67-b7ba-4e42-a808-946c8cdcc2c7" /> -->
12
+ <img width="6912" height="2940" alt="image" src="https://github.com/user-attachments/assets/24f2187b-9e71-45e6-9a4b-427ad00e48c8" />
13
+
14
+
15
+
11
16
  <p align="center"><sub>Logo made with <a href="https://github.com/RyannDaGreat/SvelteLib">PowerRP</a>. <a href="https://ryanndagreat.github.io/SvelteLib/?repo=RyannDaGreat/dtab">Open it in PowerRP</a> to edit it (source: <code>assets/doc.json</code>).</sub></p>
12
17
 
13
18
  # dtab
@@ -32,7 +37,7 @@ dtab.parse(open("scene.dtab").read())
32
37
 
33
38
  - Less to look at. No braces, quotes, or commas between values. A file with its tabs aligned reads like pseudocode, and is easy to write by hand, even on paper.
34
39
  - Simple. Six rules, one pass, about 70 lines per implementation.
35
- - Everything is addressable. There are no lists, so every value has a dotted path: `config.deltas.l1.position.x` works with EasyDict in Python and plain property access in JavaScript.
40
+ - Everything is addressable. There are no lists, so every value has a path: `config.deltas.l1.position.x` with EasyDict in Python or plain property access in JavaScript, brackets for keys like `file.json`.
36
41
  - You choose the shape. Lines stack, and `c,d` writes one value under several keys, so the same tree can be written wide, deep, or on one line, trading horizontal space for vertical. These are the same file:
37
42
 
38
43
  ```
@@ -59,8 +64,9 @@ dtab.parse(open("scene.dtab").read())
59
64
  - An entry that starts with a space is a comment.
60
65
  - `$key` starts a multiline string. See below.
61
66
 
62
- Every value is a string. Cast the ones you need. Keys are identifiers (letters, digits, underscores),
63
- so attribute access like `config.deltas.l1` works with EasyDict and friends.
67
+ Every value is a string. Cast the ones you need. A key is any run of letters, digits, `_`, `.` and `-`,
68
+ so `file.json`, `2026-09-07` and `0` are keys. Keys that happen to be identifiers work as attributes
69
+ (`config.deltas.l1` with EasyDict and friends); the rest are reached with brackets (`config["file.json"]`).
64
70
 
65
71
  ## Multiline strings
66
72
 
@@ -92,8 +98,8 @@ the value. Unknown tags are plain text, never an error.
92
98
 
93
99
  - **Python**: `pip install dtab` then `import dtab`
94
100
  - **JavaScript**: `npm install deltatab` then `const dtab = require('deltatab')`, or `<script src="https://cdn.jsdelivr.net/npm/deltatab/dtab.js">` for `window.dtab`
95
- - **Vim**: `Plugin 'RyannDaGreat/dtab'` (Vundle) or `Plug 'RyannDaGreat/dtab'` (vim-plug), or paste `dtab.vim` into your vimrc. Highlights `*.dtab`, flags bad keys and trailing tabs, and gives the file a Δ icon in NERDTree if vim-devicons is installed.
96
- - **VS Code**: search "dtab" in Extensions, or `code --install-extension RyannDaGreat.dtab`. Highlighting plus a Δ file icon.
101
+ - **Vim**: `Plugin 'RyannDaGreat/dtab'` (Vundle) or `Plug 'RyannDaGreat/dtab'` (vim-plug), or paste `dtab.vim` into your vimrc. Highlights `*.dtab`, flags bad keys and trailing tabs, gives the file a Δ icon in NERDTree if vim-devicons is installed, and `:DtabPreview` opens a split showing the tree as JSON that follows your edits (needs `+python3`).
102
+ - **VS Code**: search "dtab" in Extensions, or `code --install-extension RyannDaGreat.dtab`. Highlighting, a Δ file icon, and a live JSON preview of the file beside it (the preview button in the title bar, or Cmd+K V).
97
103
 
98
104
  ## API
99
105
 
@@ -21,7 +21,8 @@ Rules:
21
21
  - `$key` is a multiline leaf: its value is the entries after it on its line plus every line indented
22
22
  under it, one line each, with their common indentation removed. A word after the key on the `$` line
23
23
  (`$query sql`) is a language tag for editors and is not part of the value.
24
- - Keys are identifiers (str.isidentifier), so trees are EasyDict-friendly. Every value is a string.
24
+ - Keys are one or more letters, digits, or KEY_PUNCTUATION (`_.-`), so `file.json` and `123aa` are keys. Keys that
25
+ are also Python identifiers work as attributes (config.deltas.l1). Every value is a string.
25
26
 
26
27
  Single pass, one stack, O(total characters).
27
28
  """
@@ -29,12 +30,14 @@ Single pass, one stack, O(total characters).
29
30
  import json
30
31
  import re
31
32
 
32
- __version__ = "0.2.0" # SEMANTIC BINDING: dtab-version (also package.json "version")
33
+ __version__ = "0.3.0" # SEMANTIC BINDING: dtab-version (also package.json "version")
33
34
 
34
35
  KEY_SEPARATOR = "," # a,b writes the same value under each key
35
36
  BLOCK_PREFIX = "$" # $key: a leaf whose value is the lines under it ($ as in string)
36
- KEY_RULE = "keys must be identifiers (letters, digits, underscores, not starting with a digit)"
37
+ KEY_PUNCTUATION = "_.-" # Allowed in keys besides letters and digits. SEMANTIC BINDING: dtab-key-punctuation
38
+ KEY_RULE = "keys may contain only letters, digits and " + " ".join(KEY_PUNCTUATION)
37
39
  _TAB_RUN = re.compile(r"\t+") # Several tabs in a row are one separator, so columns can be aligned
40
+ _KEY = re.compile(r"[\w" + re.escape(KEY_PUNCTUATION) + "]+") # \w: letters, digits, _ (Unicode); the rest is reserved for syntax
38
41
 
39
42
 
40
43
  def parse(text):
@@ -57,9 +60,11 @@ def parse(text):
57
60
  {'query': 'SELECT *\\n\\n\\tFROM users', 'next': '1'}
58
61
  >>> parse('$cmd\\tpip install rp\\tpython train.py')
59
62
  {'cmd': 'pip install rp\\npython train.py'}
60
- >>> parse('a\\tb 1\\nc.d\\te 2')
63
+ >>> parse('a\\tb 1\\nfile.json\\tsize 2\\n123aa 3')
64
+ {'a': {'b': '1'}, 'file.json': {'size': '2'}, '123aa': '3'}
65
+ >>> parse('a\\tb 1\\nc/d\\te 2')
61
66
  Traceback (most recent call last):
62
- ValueError: dtab line 2: invalid key 'c.d': keys must be identifiers (letters, digits, underscores, not starting with a digit)
67
+ ValueError: dtab line 2: invalid key 'c/d': keys may contain only letters, digits and _ . -
63
68
  """
64
69
  root = {}
65
70
  stack = [(-1, [root])] # (indent, nodes that deeper lines nest into)
@@ -126,19 +131,19 @@ def stringify(tree):
126
131
 
127
132
  def _key_names(key, line_number, allow_commas):
128
133
  """
129
- Pure function (raises ValueError). The identifiers a key stands for: `a,b` is two while parsing,
130
- and a stringify key must be a single bare identifier.
134
+ Pure function (raises ValueError). The names a key stands for: `a,b` is two while parsing, and a
135
+ stringify key must be a single name.
131
136
 
132
137
  Examples:
133
- >>> _key_names('l1,l2', 1, True), _key_names('table_bottom', None, False)
134
- (['l1', 'l2'], ['table_bottom'])
138
+ >>> _key_names('l1,l2', 1, True), _key_names('file-thing.json', None, False)
139
+ (['l1', 'l2'], ['file-thing.json'])
135
140
  >>> _key_names('a,b', None, False)
136
141
  Traceback (most recent call last):
137
- ValueError: dtab: invalid key 'a,b': keys must be identifiers (letters, digits, underscores, not starting with a digit)
142
+ ValueError: dtab: invalid key 'a,b': keys may contain only letters, digits and _ . -
138
143
  """
139
144
  names = key.split(KEY_SEPARATOR) if allow_commas else [key]
140
145
  for name in names:
141
- if not name.isidentifier():
146
+ if not _KEY.fullmatch(name):
142
147
  where = " line %d" % line_number if line_number else ""
143
148
  raise ValueError("dtab%s: invalid key %r: %s" % (where, key, KEY_RULE))
144
149
  return names
@@ -14,19 +14,23 @@ Checks:
14
14
  that is one leaf, deltas.initial.l1.intensity, where the original ignored the later `l1 intensity 1`),
15
15
  and `$` multiline blocks, which the original did not have.
16
16
  4. parse(stringify(parse(text))) == parse(text) for every sample.
17
- 5. The key rule: bad keys are rejected with a line number in parse and in stringify, and Python's
18
- str.isidentifier and the JS Unicode regex agree on a set of probes.
17
+ 5. The key rule (letters, digits, _ . -): bad keys are rejected with a line number in parse and in
18
+ stringify, and the Python and JS character classes agree on a set of Unicode probes.
19
19
  6. node test/test_dtab.js.
20
20
  7. Vim: the syntax groups over test/samples/highlight.dtab match test/expected/highlight.txt byte by byte
21
- (that sample deliberately contains invalid keys, so it is not parsed), and plugin/dtab.vim sets the
22
- filetype when the repo is on 'runtimepath', which is what Vundle and vim-plug do.
21
+ (that sample deliberately contains invalid keys, so it is not parsed), embedded languages, the Tab and
22
+ shift keys, :DtabPreview, and plugin/dtab.vim sets the filetype when the repo is on 'runtimepath',
23
+ which is what Vundle and vim-plug do.
23
24
  8. docs/index.html in headless Chrome (test/test_web.js), skipped with a message if puppeteer is not
24
25
  installed (`npm install --no-save puppeteer && npx puppeteer browsers install chrome`).
25
- 9. The VS Code extension's TextMate grammar tokenizes the vim highlight sample the same way vim does
26
- (test/test_vscode.js), skipped if vscode-textmate is not installed
26
+ 9. The VS Code extension's TextMate grammar tokenizes the vim highlight sample the same way vim does,
27
+ and its indentation and preview logic (test/test_vscode.js), skipped if vscode-textmate is not installed
27
28
  (`npm install --no-save vscode-textmate vscode-oniguruma`).
29
+ 10. The JSON preview inside the installed VS Code, in an isolated profile (test/test_vscode_live.js),
30
+ skipped if @vscode/test-electron is not installed.
28
31
 
29
- Needs: python 3, node, vim.
32
+ Needs: python 3, node, vim. The optional packages go in together, since an `npm install --no-save` removes
33
+ the ones it was not given: `npm install --no-save puppeteer vscode-textmate vscode-oniguruma @vscode/test-electron`.
30
34
  """
31
35
 
32
36
  import doctest
@@ -40,7 +44,7 @@ ROOT = Path(__file__).resolve().parent.parent
40
44
  HIGHLIGHT_SAMPLE = ROOT / "test" / "samples" / "highlight.dtab"
41
45
  SAMPLES = sorted(path for path in (ROOT / "test" / "samples").glob("*.dtab") if path != HIGHLIGHT_SAMPLE)
42
46
  DEVIATING = {"deviations.dtab", "game_config.dtab", "multiline.dtab"} # multiline: $ blocks did not exist in the original
43
- IDENTIFIER_PROBES = ["café", "变量", "x²", "_x", "0x", "from", "items", "a-b", "ok_1", "ª", "Ⅻ", "℘", "ℕ", "𝔸", "a.b", "é1", "1é"]
47
+ IDENTIFIER_PROBES = ["café", "变量", "x²", "_x", "0x", "a-b", "ok_1", "ª", "Ⅻ", "℘", "ℕ", "𝔸", "a.b", "é1", "1é", "a/b", "a:b", "ä-ö.ü", "١٢٣", "a~b"] # the key rule, py vs js
44
48
 
45
49
  sys.path.insert(0, str(ROOT))
46
50
  import dtab # noqa: E402
@@ -88,20 +92,28 @@ def test_round_trips():
88
92
 
89
93
  def test_key_rule():
90
94
  for text, fragments in [
91
- ("a\tcheckpoint.initial 1", ["line 1", "'checkpoint.initial'"]),
92
- ("ok 1\n\t2nd 2", ["line 2", "'2nd'"]),
93
- ("a-b 1", ["'a-b'"]),
95
+ ("a\tc/d 1", ["line 1", "'c/d'"]),
96
+ ("ok 1\n\tk:v 2", ["line 2", "'k:v'"]),
94
97
  ("~scope\n\tx 1", ["'~scope'"]),
95
98
  ("log\t@ e", ["'@'"]),
96
- ("a,b.c\tx 1", ["'a,b.c'"]),
99
+ ("a,b#c\tx 1", ["'a,b#c'"]),
100
+ ("\"quoted\" 1", ["'\"quoted\"'"]),
97
101
  ]:
98
102
  raises_value_error(lambda: dtab.parse(text), *fragments)
99
- assert dtab.parse("items 1\nfrom 2\n_private 3\ncafé 4") == {"items": "1", "from": "2", "_private": "3", "café": "4"}
100
- for tree in [{"a b": "1"}, {"a,b": "1"}, {0: "1"}, {"": "1"}]:
103
+ assert dtab.parse("123aa 1\nfile.json 2\nfile-thing.json 3\n123.json-yaml 4\nitems 5\n_p 6\ncafé 7\n-x 8") == {
104
+ "123aa": "1", "file.json": "2", "file-thing.json": "3", "123.json-yaml": "4", "items": "5", "_p": "6", "café": "7", "-x": "8"}
105
+ for tree in [{"a b": "1"}, {"a,b": "1"}, {"a/b": "1"}, {"": "1"}]:
101
106
  raises_value_error(lambda: dtab.stringify(tree), "dtab")
107
+ assert dtab.parse(dtab.stringify({0: "a", "file.json": "b"})) == {"0": "a", "file.json": "b"} # int keys are written as text
102
108
  for value in ["x\ny", "x\ty", "a\n\n\tb\n c", "#!/bin/bash\necho hi", ""]:
103
109
  assert dtab.parse(dtab.stringify({"a": value})) == {"a": value}, "multiline round trip failed for %r" % value
104
- python_verdicts = [probe.isidentifier() for probe in IDENTIFIER_PROBES]
110
+ def accepted(probe):
111
+ try:
112
+ dtab.parse(probe + " 1")
113
+ return True
114
+ except ValueError:
115
+ return False
116
+ python_verdicts = [accepted(probe) for probe in IDENTIFIER_PROBES]
105
117
  js_verdicts = json.loads(run(
106
118
  "node", "-e",
107
119
  "const d = require('./dtab.js'); console.log(JSON.stringify(%s.map(p => { try { d.parse(p + ' 1'); return true } catch { return false } })))"
@@ -159,6 +171,38 @@ def test_vim_tab_key():
159
171
  assert lines[1] == "after\t 1", repr(lines[1]) # a tab outside the block
160
172
 
161
173
 
174
+ def test_vim_shift_keys():
175
+ """>> and << (and visual > <) shift block lines by four spaces, other lines by a tab."""
176
+ with tempfile.TemporaryDirectory() as directory:
177
+ sample = Path(directory) / "shift.dtab"
178
+ sample.write_text("before 1\n$code python\n\tdef f():\n\t return 1\n")
179
+ out = Path(directory) / "lines.txt"
180
+ vim("syntax on", "source dtab.vim", "edit " + str(sample),
181
+ "execute '1normal >>' | execute '1normal <<' | execute '3normal >>' | execute '4normal <<' | execute '2normal Vjj>'",
182
+ "call writefile(getline(1, '$'), '%s')" % out)
183
+ lines = out.read_text().split("\n")
184
+ assert lines[0] == "before 1", repr(lines[0]) # >> gave it a tab, << took it away
185
+ assert lines[1] == "\t$code python", repr(lines[1]) # visual > over the $ line and its block: tabs for all
186
+ assert lines[2] == "\t\t def f():", repr(lines[2]) # >> inside the block: spaces; then the block's tab
187
+ assert lines[3] == "\t\treturn 1", repr(lines[3]) # << inside the block removed the spaces
188
+
189
+
190
+ def test_vim_preview():
191
+ """:DtabPreview opens a JSON split of the buffer's tree that follows edits (showing the parser's message when the text is broken), and closes it when repeated."""
192
+ sample = SAMPLES[0]
193
+ with tempfile.TemporaryDirectory() as directory:
194
+ out = Path(directory) / "preview.txt"
195
+ vim("syntax on", "source dtab.vim", "edit " + str(sample),
196
+ "DtabPreview | call writefile(getbufline(b:dtab_preview, 1, '$') + ['---'], '%s')" % out,
197
+ "call setline(1, 'bad/key 1') | doautocmd TextChanged",
198
+ "call writefile(getbufline(b:dtab_preview, 1, '$') + ['---', getbufvar(b:dtab_preview, '&filetype')], '%s', 'a')" % out,
199
+ "DtabPreview | call writefile([bufwinnr(b:dtab_preview)], '%s', 'a')" % out)
200
+ tree, message, closed = out.read_text().split("---\n")
201
+ assert json.loads(tree) == dtab.parse(sample.read_text()), "the preview is not the parsed tree"
202
+ assert message.startswith("dtab line 1: invalid key 'bad/key'"), "the preview did not follow the edit: %r" % message
203
+ assert closed == "json\n-1\n", "expected a json split that the second :DtabPreview closes: %r" % closed
204
+
205
+
162
206
  def test_vim_plugin_shim():
163
207
  with tempfile.TemporaryDirectory() as directory:
164
208
  out = Path(directory) / "filetype.txt"
@@ -175,15 +219,30 @@ def test_web_demo():
175
219
 
176
220
 
177
221
  def test_vscode_grammar():
222
+ # The committed grammar and language configuration are what vscode/make_grammar.py generates.
223
+ with tempfile.TemporaryDirectory() as directory:
224
+ sys.path.insert(0, str(ROOT / "vscode"))
225
+ import make_grammar # noqa: E402
226
+ for written in make_grammar.write(directory):
227
+ committed = ROOT / "vscode" / Path(written).relative_to(directory)
228
+ assert Path(written).read_text() == committed.read_text(), "%s is stale: run python vscode/make_grammar.py" % committed
178
229
  if not (ROOT / "node_modules" / "vscode-textmate").is_dir():
179
230
  print(" (skipped: vscode-textmate not installed)")
180
231
  return
181
232
  subprocess.run(["node", "test/test_vscode.js"], check=True, cwd=ROOT)
182
233
 
183
234
 
235
+ def test_vscode_live():
236
+ """The JSON preview inside the installed VS Code (test/test_vscode_live.js), skipped without @vscode/test-electron or the app."""
237
+ if not (ROOT / "node_modules" / "@vscode" / "test-electron").is_dir() or not Path("/Applications/Visual Studio Code.app").is_dir():
238
+ print(" (skipped: @vscode/test-electron or VS Code not installed)")
239
+ return
240
+ subprocess.run(["node", "test/test_vscode_live.js"], check=True, cwd=ROOT)
241
+
242
+
184
243
  if __name__ == "__main__":
185
244
  for test in [test_doctests, test_readers_agree, test_round_trips, test_key_rule, test_js_suite,
186
- test_vim_highlighting, test_vim_embedded_languages, test_vim_tab_key, test_vim_plugin_shim, test_web_demo, test_vscode_grammar]:
245
+ test_vim_highlighting, test_vim_embedded_languages, test_vim_tab_key, test_vim_shift_keys, test_vim_preview, test_vim_plugin_shim, test_web_demo, test_vscode_grammar, test_vscode_live]:
187
246
  test()
188
247
  print("ok " + test.__name__)
189
248
  print("All dtab tests passed")
File without changes
File without changes
File without changes
File without changes
File without changes