Skip to content

Commit 928e6fe

Browse files
authored
Merge pull request #98 from vim-denops/imp-bufname
💥 Improve `bufname` module (with breaking changes)
2 parents a962607 + 6a7196a commit 928e6fe

File tree

8 files changed

+416
-128
lines changed

8 files changed

+416
-128
lines changed

denops_std/bufname/README.md

Lines changed: 165 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,50 @@
11
# bufname
22

3-
`bufname` is a module to provide manage Vim's buffer name.
3+
`bufname` is a module to provide functions to handle Vim's buffer name.
44

55
- [API documentation](https://doc.deno.land/https/deno.land/x/denops_std/bufname/mod.ts)
66

7+
The format of the buffer name assumed in this module is like
8+
9+
```text
10+
{scheme}://{expr}[;{params}][#{fragment}]
11+
```
12+
13+
Where
14+
15+
- `{scheme}` is used to distinguish a buffer kind. It contains only alphabet
16+
characters.
17+
- `{expr}` is used to identify a buffer itself. _Unusable characters_,
18+
semicolons (;), and sharps (#) are replaced with percent-encoded characters.
19+
- `{params}` (Optional) is used to add meta information to the buffer name like
20+
query parameters of URL. _Unusable characters_ and sharps (#) are replaced
21+
with percent-encoded characters.
22+
- `{fragment}` (Optional) is used to add a suffix to the buffer name for file
23+
type detection or so on. _Unusable characters_ are replaced with
24+
percent-encoded characters.
25+
- _Unusable characters_ are `"`, `<`, `>`, `|`, `?`, or `*`. It is caused by the
26+
limitations of Vim on Windows.
27+
28+
For example,
29+
30+
```text
31+
denops:///Users/John Titor/test.git
32+
└─┬──┘ └───────────┬────────────┘
33+
scheme expr
34+
35+
denops:///Users/John Titor/test.git;foo=foo&bar=bar1&bar=bar2
36+
└─┬──┘ └───────────┬────────────┘ └───────────┬───────────┘
37+
scheme expr params
38+
39+
denops:///Users/John Titor/test.git#README.md
40+
└─┬──┘ └───────────┬────────────┘ └───┬───┘
41+
scheme expr fragment
42+
43+
denops:///Users/John Titor/test.git;foo=foo&bar=bar1&bar=bar2#README.md
44+
└─┬──┘ └───────────┬────────────┘ └───────────┬───────────┘ └───┬───┘
45+
scheme expr params fragment
46+
```
47+
748
## Usage
849

950
### format
@@ -13,88 +54,144 @@ instance like:
1354

1455
```typescript
1556
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
16-
import { Denops } from "https://deno.land/x/denops_std/mod.ts";
1757
import { format } from "https://deno.land/x/denops_std/bufname/mod.ts";
1858

19-
export async function main(denops: Denops): Promise<void> {
20-
assertEquals(
21-
format({ scheme: "denops", path: "/Users/johntitor/.vimrc" }),
22-
"denops:///Users/johntitor/.vimrc",
23-
);
24-
25-
assertEquals(
26-
format({
27-
scheme: "denops",
28-
path: "/Users/johntitor/.vimrc",
29-
params: {
30-
foo: "foo",
31-
bar: ["bar", "bar"],
32-
hoge: undefined,
33-
},
34-
}),
35-
"denops:///Users/johntitor/.vimrc;foo=foo&bar=bar&bar=bar",
36-
);
37-
38-
assertEquals(
39-
format({
40-
scheme: "denops",
41-
path: "/Users/johntitor/.vimrc",
42-
params: {
43-
foo: "foo",
44-
bar: ["bar", "bar"],
45-
hoge: undefined,
46-
},
47-
fragment: "README.md",
48-
}),
49-
"denops:///Users/johntitor/.vimrc;foo=foo&bar=bar&bar=bar#README.md",
50-
);
51-
}
59+
assertEquals(
60+
format({
61+
scheme: "denops",
62+
expr: "/Users/John Titor/test.git",
63+
}),
64+
"denops:///Users/John Titor/test.git",
65+
);
66+
67+
assertEquals(
68+
format({
69+
scheme: "denops",
70+
expr: "/Users/John Titor/test.git",
71+
params: {
72+
foo: "foo",
73+
bar: ["bar1", "bar2"],
74+
},
75+
}),
76+
"denops:///Users/John Titor/test.git;foo=foo&bar=bar1&bar=bar2",
77+
);
78+
79+
assertEquals(
80+
format({
81+
scheme: "denops",
82+
expr: "/Users/John Titor/test.git",
83+
fragment: "README.md",
84+
}),
85+
"denops:///Users/John Titor/test.git#README.md",
86+
);
87+
88+
assertEquals(
89+
format({
90+
scheme: "denops",
91+
expr: "/Users/John Titor/test.git",
92+
params: {
93+
foo: "foo",
94+
bar: ["bar1", "bar2"],
95+
},
96+
fragment: "README.md",
97+
}),
98+
"denops:///Users/John Titor/test.git;foo=foo&bar=bar1&bar=bar2#README.md",
99+
);
52100
```
53101

54-
Note that unusable characters (`"`, `<`, `>`, `|`, `?`, `*`) are replaced with
55-
percent-encoded characters.
102+
This function does not handle path separator differences among platforms (Unix
103+
uses `/` but Windows uses `\`). That's why it's recommended to normalize the
104+
`expr` with [`toFileUrl`](https://deno.land/std/path#tofileurl) before when
105+
constructing a buffer name from a real path. For example
106+
107+
```typescript
108+
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
109+
import * as path from "https://deno.land/std/path/mod.ts";
110+
import { format } from "https://deno.land/x/denops_std/bufname/mod.ts";
111+
112+
// NOTE:
113+
// Works only on Windows (Use path.win32.toFileUrl instead on other platforms)
114+
assertEquals(
115+
format({
116+
scheme: "denops",
117+
expr: path.toFileUrl("C:\\Users\John Titor\test.git").pathname,
118+
}),
119+
"denops:///C:/Users/John%20Titor/test.git",
120+
);
121+
```
56122

57123
### parse
58124

59125
Use `parse()` to parse Vim's buffer name and get a `Bufname` instance like
60126

61127
```typescript
62128
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
63-
import { Denops } from "https://deno.land/x/denops_std/mod.ts";
64129
import { parse } from "https://deno.land/x/denops_std/bufname/mod.ts";
65130

66-
export async function main(denops: Denops): Promise<void> {
67-
assertEquals(parse("denops:///Users/johntitor/.vimrc"), {
131+
assertEquals(
132+
parse("denops:///Users/John Titor/test.git"),
133+
{
68134
scheme: "denops",
69-
path: "/Users/johntitor/.vimrc",
70-
});
71-
72-
assertEquals(
73-
parse("denops:///Users/johntitor/.vimrc;foo=foo&bar=bar&bar=bar"),
74-
{
75-
scheme: "denops",
76-
path: "/Users/johntitor/.vimrc",
77-
params: {
78-
foo: "foo",
79-
bar: ["bar", "bar"],
80-
},
135+
expr: "/Users/John Titor/test.git",
136+
},
137+
);
138+
139+
assertEquals(
140+
parse("denops:///Users/John Titor/test.git;foo=foo&bar=bar1&bar=bar2"),
141+
{
142+
scheme: "denops",
143+
expr: "/Users/John Titor/test.git",
144+
params: {
145+
foo: "foo",
146+
bar: ["bar1", "bar2"],
81147
},
82-
);
83-
84-
assertEquals(
85-
parse("denops:///Users/johntitor/.vimrc;foo=foo&bar=bar&bar=bar#README.md"),
86-
{
87-
scheme: "denops",
88-
path: "/Users/johntitor/.vimrc",
89-
params: {
90-
foo: "foo",
91-
bar: ["bar", "bar"],
92-
},
93-
fragment: "README.md",
148+
},
149+
);
150+
151+
assertEquals(
152+
parse("denops:///Users/John Titor/test.git#README.md"),
153+
{
154+
scheme: "denops",
155+
expr: "/Users/John Titor/test.git",
156+
fragment: "README.md",
157+
},
158+
);
159+
160+
assertEquals(
161+
parse(
162+
"denops:///Users/John Titor/test.git;foo=foo&bar=bar1&bar=bar2#README.md",
163+
),
164+
{
165+
scheme: "denops",
166+
expr: "/Users/John Titor/test.git",
167+
params: {
168+
foo: "foo",
169+
bar: ["bar1", "bar2"],
94170
},
95-
);
96-
}
171+
fragment: "README.md",
172+
},
173+
);
97174
```
98175

99-
Note that percent-encoded characters of unusable characters (`"`, `<`, `>`, `|`,
100-
`?`, `*`) are restored.
176+
This function does not handle path separator differences among platforms. That's
177+
why it's recommended to restore the `expr` with
178+
[`fromFileUrl`](https://deno.land/std/path#fromfileurl) after if a buffer name
179+
was constructed from a real path. For example
180+
181+
```typescript
182+
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
183+
import * as path from "https://deno.land/std/path/mod.ts";
184+
import { parse } from "https://deno.land/x/denops_std/bufname/mod.ts";
185+
186+
const bufname = parse("denops:///C:/Users/John%20Titor/test.git");
187+
assertEquals(bufname, {
188+
scheme: "denops",
189+
expr: "/C:/Users/John Titor/test.git",
190+
});
191+
// NOTE:
192+
// Works only on Windows (Use path.win32.fromFileUrl instead on other platforms)
193+
assertEquals(
194+
path.fromFileUrl(`file://${bufname.expr}`),
195+
"C:\\Users\\John Titor\\test.git",
196+
);
197+
```

denops_std/bufname/bufname.ts

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ export type BufnameParams = Record<string, string | string[] | undefined>;
55
export type Bufname = {
66
// Scheme part of a buffer name. Note that Vim supports only alphabets in scheme part.
77
scheme: string;
8-
// Path part of a buffer name. Note that '<>|?*' are not supported in Vim on Windows.
9-
path: string;
8+
// Expression part of a buffer name. Note that '<>|?*' are not supported in Vim on Windows.
9+
expr: string;
1010
// Params part of a buffer name. While '?' is not supported, the params part are splitted by ';' instead.
1111
params?: BufnameParams;
1212
// Fragment part of a buffer name. This is mainly used to regulate the suffix of the buffer name.
@@ -23,19 +23,28 @@ const schemeUnusablePattern = /[^a-zA-Z]/;
2323
// https://github.com/vim/vim/blob/36698e34aacee4186e6f5f87f431626752fcb337/src/misc1.c#L2567-L2580
2424
const schemePattern = /^(\S+):\/\//;
2525

26-
// The path part might contains query and/or fragment
27-
const pathPattern = /^(.*?)(?:;(.*?))?(?:#(.*))?$/;
26+
// The expr part might contains query and/or fragment
27+
const exprPattern = /^(.*?)(?:;(.*?))?(?:#(.*))?$/;
2828

2929
/**
30-
* Format Bufname instance to construct Vim's buffer name.
30+
* Format a `Bufname` instance and return a safe string as Vim's buffer name.
31+
*
32+
* It throws errors when `scheme` contains unusable characters (non-alphabet characters).
33+
*
34+
* All unusable characters ("<>|?*) are replaced with percent-encoded characters.
35+
* In addition to the above, all semicolons (;) and sharps (#) in `path` are replaced with
36+
* percent-encoded characters. It's required to distinguish `params` and or `fragment`.
37+
* ```
3138
*/
32-
export function format({ scheme, path, params, fragment }: Bufname): string {
39+
export function format(
40+
{ scheme, expr, params, fragment }: Bufname,
41+
): string {
3342
if (schemeUnusablePattern.test(scheme)) {
3443
throw new Error(
3544
`Scheme '${scheme}' contains unusable characters. Only alphabets are allowed.`,
3645
);
3746
}
38-
const encodedPath = encode(path).replaceAll(";", "%3b").replaceAll(
47+
const encodedPath = encode(expr).replaceAll(";", "%3B").replaceAll(
3948
"#",
4049
"%23",
4150
);
@@ -47,18 +56,23 @@ export function format({ scheme, path, params, fragment }: Bufname): string {
4756
}
4857

4958
/**
50-
* Parse Vim's buffer name to construct Bufname instance.
59+
* Parse Vim's buffer name and return a `Bufname` instance.
60+
*
61+
* It throws errors when a given `bufname` is not valid Vim's buffer name.
62+
* For example, if it contains unusable characters ("<>|?*).
5163
*/
52-
export function parse(expr: string): Bufname {
53-
if (bufnameUnusablePattern.test(expr)) {
64+
export function parse(bufname: string): Bufname {
65+
if (bufnameUnusablePattern.test(bufname)) {
5466
throw new Error(
55-
`Expression '${expr}' contains unusable characters. Vim (on Windows) does not support '<>|?*' in a buffer name.`,
67+
`A buffer name '${bufname}' contains unusable characters. Vim (on Windows) does not support '<>|?*' in a buffer name.`,
5668
);
5769
}
5870
// Check if the bufname is remote
59-
const m1 = expr.match(schemePattern);
71+
const m1 = bufname.match(schemePattern);
6072
if (!m1) {
61-
throw new Error(`Expression '${expr}' does not start from '{scheme}://'.`);
73+
throw new Error(
74+
`A buffer name '${bufname}' does not start from '{scheme}://'.`,
75+
);
6276
}
6377
const scheme = m1[1];
6478
// Vim (less than 8.2.3153) only supports alphabets in scheme part
@@ -67,19 +81,19 @@ export function parse(expr: string): Bufname {
6781
`Scheme '${scheme}' contains unusable characters. Only alphabets are allowed.`,
6882
);
6983
}
70-
const remain = decode(expr.substring(`${scheme}://`.length), [
71-
"%3b", // ;
84+
const remain = decode(bufname.substring(`${scheme}://`.length), [
85+
"%3B", // ;
7286
"%23", // #
7387
]);
74-
const m2 = remain.match(pathPattern)!;
75-
const path = decode(m2[1]);
88+
const m2 = remain.match(exprPattern)!;
89+
const expr = decode(m2[1]);
7690
const params = m2[2]
7791
? fromURLSearchParams(new URLSearchParams(decode(m2[2])))
7892
: undefined;
7993
const fragment = m2[3] ? decode(m2[3]) : undefined;
8094
return {
8195
scheme,
82-
path,
96+
expr,
8397
...(params ? { params } : {}),
8498
...(fragment ? { fragment } : {}),
8599
};

0 commit comments

Comments
 (0)