Skip to content

Commit 674fb09

Browse files
authored
Merge pull request #95 from vim-denops/add-bufname
👍 Add `bufname` module
2 parents c4d19f3 + 9c2d2ad commit 674fb09

File tree

10 files changed

+598
-3
lines changed

10 files changed

+598
-3
lines changed

denops_std/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ for more details.
5959
| [`anonymous`](./anonymous) | A module to provide anonymous function |
6060
| [`autocmd`](./autocmd) | A module to provide helper functions to manage `autocmd` |
6161
| [`batch`](./batch) | A module to provide wrapper functions of `denops.batch()` |
62+
| [`bufname`](./bufname) | A module to provide helper functions to manage Vim's buffer name |
6263
| [`function`](./function) | A module to provide functions of Vim and Neovim native functions |
6364
| [`helper`](./helper) | A module to provide helper functions |
6465
| [`mapping`](./mapping) | A module to provide helper functions to manage mappings |

denops_std/bufname/README.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# bufname
2+
3+
`bufname` is a module to provide manage Vim's buffer name.
4+
5+
- [API documentation](https://doc.deno.land/https/deno.land/x/denops_std/bufname/mod.ts)
6+
7+
## Usage
8+
9+
### format
10+
11+
Use `format()` to format a Vim's buffer name in a safe way from `Bufname`
12+
instance like:
13+
14+
```typescript
15+
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
16+
import { Denops } from "https://deno.land/x/denops_std/mod.ts";
17+
import { format } from "https://deno.land/x/denops_std/bufname/mod.ts";
18+
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+
}
52+
```
53+
54+
Note that unusable characters (`"`, `<`, `>`, `|`, `?`, `*`) are replaced with
55+
percent-encoded characters.
56+
57+
### parse
58+
59+
Use `parse()` to parse Vim's buffer name and get a `Bufname` instance like
60+
61+
```typescript
62+
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
63+
import { Denops } from "https://deno.land/x/denops_std/mod.ts";
64+
import { parse } from "https://deno.land/x/denops_std/bufname/mod.ts";
65+
66+
export async function main(denops: Denops): Promise<void> {
67+
assertEquals(parse("denops:///Users/johntitor/.vimrc"), {
68+
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+
},
81+
},
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",
94+
},
95+
);
96+
}
97+
```
98+
99+
Note that percent-encoded characters of unusable characters (`"`, `<`, `>`, `|`,
100+
`?`, `*`) are restored.

denops_std/bufname/bufname.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { bufnameUnusablePattern, decode, encode } from "./utils.ts";
2+
3+
export type BufnameParams = Record<string, string | string[] | undefined>;
4+
5+
export type Bufname = {
6+
// Scheme part of a buffer name. Note that Vim supports only alphabets in scheme part.
7+
scheme: string;
8+
// Path part of a buffer name. Note that '<>|?*' are not supported in Vim on Windows.
9+
path: string;
10+
// Params part of a buffer name. While '?' is not supported, the params part are splitted by ';' instead.
11+
params?: BufnameParams;
12+
// Fragment part of a buffer name. This is mainly used to regulate the suffix of the buffer name.
13+
fragment?: string;
14+
};
15+
16+
// Vim only supports alphabet characters in the scheme part of a buffer name.
17+
// That behavior has slightly improved from Vim 8.2.3153 but we suppor Vim 8.2.0662
18+
// thus we need to stack old behavior
19+
// https://github.com/vim/vim/pull/8299
20+
const schemeUnusablePattern = /[^a-zA-Z]/;
21+
22+
// Vim assumes a bufname is URL when it starts with "name://"
23+
// https://github.com/vim/vim/blob/36698e34aacee4186e6f5f87f431626752fcb337/src/misc1.c#L2567-L2580
24+
const schemePattern = /^(\S+):\/\//;
25+
26+
// The path part might contains query and/or fragment
27+
const pathPattern = /^(.*?)(?:;(.*?))?(?:#(.*))?$/;
28+
29+
/**
30+
* Format Bufname instance to construct Vim's buffer name.
31+
*/
32+
export function format({ scheme, path, params, fragment }: Bufname): string {
33+
if (schemeUnusablePattern.test(scheme)) {
34+
throw new Error(
35+
`Scheme '${scheme}' contains unusable characters. Only alphabets are allowed.`,
36+
);
37+
}
38+
const encodedPath = encode(path).replaceAll(";", "%3b").replaceAll(
39+
"#",
40+
"%23",
41+
);
42+
const suffix1 = params
43+
? `;${encode(toURLSearchParams(params).toString())}`
44+
: "";
45+
const suffix2 = fragment ? `#${encode(fragment)}` : "";
46+
return `${scheme}://${encodedPath}${suffix1}${suffix2}`;
47+
}
48+
49+
/**
50+
* Parse Vim's buffer name to construct Bufname instance.
51+
*/
52+
export function parse(expr: string): Bufname {
53+
if (bufnameUnusablePattern.test(expr)) {
54+
throw new Error(
55+
`Expression '${expr}' contains unusable characters. Vim (on Windows) does not support '<>|?*' in a buffer name.`,
56+
);
57+
}
58+
// Check if the bufname is remote
59+
const m1 = expr.match(schemePattern);
60+
if (!m1) {
61+
throw new Error(`Expression '${expr}' does not start from '{scheme}://'.`);
62+
}
63+
const scheme = m1[1];
64+
// Vim (less than 8.2.3153) only supports alphabets in scheme part
65+
if (schemeUnusablePattern.test(scheme)) {
66+
throw new Error(
67+
`Scheme '${scheme}' contains unusable characters. Only alphabets are allowed.`,
68+
);
69+
}
70+
const remain = decode(expr.substring(`${scheme}://`.length), [
71+
"%3b", // ;
72+
"%23", // #
73+
]);
74+
const m2 = remain.match(pathPattern)!;
75+
const path = decode(m2[1]);
76+
const params = m2[2]
77+
? fromURLSearchParams(new URLSearchParams(decode(m2[2])))
78+
: undefined;
79+
const fragment = m2[3] ? decode(m2[3]) : undefined;
80+
return {
81+
scheme,
82+
path,
83+
...(params ? { params } : {}),
84+
...(fragment ? { fragment } : {}),
85+
};
86+
}
87+
88+
function toURLSearchParams(params: BufnameParams): URLSearchParams {
89+
const cs = Object.entries(params).flatMap(
90+
([key, value]): [string, string][] => {
91+
if (value == undefined) {
92+
return [];
93+
} else if (Array.isArray(value)) {
94+
return value.map((v) => [key, v]);
95+
} else {
96+
return [[key, value]];
97+
}
98+
},
99+
);
100+
return new URLSearchParams(cs);
101+
}
102+
103+
function fromURLSearchParams(search: URLSearchParams): BufnameParams {
104+
const params: BufnameParams = {};
105+
search.forEach((value, key) => {
106+
if (Object.prototype.hasOwnProperty.call(params, key)) {
107+
const previous = params[key];
108+
if (Array.isArray(previous)) {
109+
previous.push(value);
110+
return;
111+
} else if (previous != undefined) {
112+
params[key] = [previous, value];
113+
return;
114+
}
115+
}
116+
params[key] = value;
117+
});
118+
return params;
119+
}

0 commit comments

Comments
 (0)