Skip to content

Commit 46a4c2b

Browse files
committed
initial commit
0 parents  commit 46a4c2b

File tree

10 files changed

+288
-0
lines changed

10 files changed

+288
-0
lines changed

.github/workflows/test.yaml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: Test
2+
3+
env:
4+
DENO_VERSION: 1.x
5+
6+
on:
7+
push:
8+
branches:
9+
- main
10+
pull_request:
11+
branches:
12+
- main
13+
14+
jobs:
15+
lint:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v2
19+
- uses: denoland/setup-deno@main
20+
with:
21+
deno-version: ${{ env.DENO_VERSION }}
22+
- name: Lint
23+
run: deno lint
24+
25+
format:
26+
runs-on: ubuntu-latest
27+
steps:
28+
- uses: actions/checkout@v2
29+
- uses: denoland/setup-deno@main
30+
with:
31+
deno-version: ${{ env.DENO_VERSION }}
32+
- name: Format
33+
run: |
34+
deno fmt --check
35+
test:
36+
runs-on: ubuntu-latest
37+
steps:
38+
- uses: actions/checkout@v2
39+
- uses: denoland/setup-deno@main
40+
with:
41+
deno-version: ${{ env.DENO_VERSION }}
42+
- name: Test
43+
run: |
44+
deno test
45+
timeout-minutes: 5
46+
47+
typecheck:
48+
runs-on: ubuntu-latest
49+
steps:
50+
- uses: actions/checkout@v2
51+
- uses: denoland/setup-deno@main
52+
with:
53+
deno-version: ${{ env.DENO_VERSION }}
54+
- name: Type check
55+
run: |
56+
deno test --unstable --no-run ./*.ts

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 tennashi
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# WIP: denops_app
2+
UI module for [denops.vim](https://github.com/vim-denops/denops.vim).

app.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { Denops, execute, group, GroupHelper } from "./deps.ts";
2+
import { Buffer } from "./buffer.ts";
3+
import { Component, Route } from "./router.ts";
4+
5+
export class DenopsApp {
6+
#appName: string;
7+
#routes: {
8+
pathRegexp: RegExp;
9+
component: Component;
10+
}[];
11+
#denops: Denops;
12+
#commands: {
13+
name: string;
14+
path: string;
15+
}[];
16+
17+
constructor(appName: string, denops: Denops) {
18+
this.#appName = appName;
19+
this.#routes = [];
20+
this.#denops = denops;
21+
this.#commands = [];
22+
}
23+
24+
setRoutes(routes: Route[]) {
25+
this.#routes = routes.map((route) => ({
26+
pathRegexp: new RegExp(
27+
"^" + route.path.replaceAll(/\/:(\w+)/g, "/(?<$1>\\w+)") + "$",
28+
),
29+
component: route.component,
30+
}));
31+
console.log(this.#routes);
32+
}
33+
34+
addCommand(name: string, path: string) {
35+
this.#commands.push({ name: name, path: path });
36+
}
37+
38+
matchRoute(path: string): Promise<Buffer> {
39+
for (const route of this.#routes) {
40+
const matched = path.match(route.pathRegexp);
41+
42+
if (!matched) {
43+
continue;
44+
}
45+
46+
const pathParams = matched.groups || {};
47+
return route.component(pathParams);
48+
}
49+
50+
throw Error("not found");
51+
}
52+
53+
async initialize() {
54+
this.#denops.dispatcher = {
55+
renderContents: async (path: unknown): Promise<void> => {
56+
const buffer = await this.matchRoute(
57+
(path as string).substring(`denopsapp://${this.#denops.name}`.length),
58+
);
59+
await buffer.render(this.#denops);
60+
},
61+
};
62+
63+
this.#commands.forEach(async (cmd) => {
64+
await execute(
65+
this.#denops,
66+
`command! ${cmd.name} split denopsapp://${this.#denops.name}${cmd.path}`,
67+
);
68+
});
69+
70+
await group(
71+
this.#denops,
72+
`${this.#appName}-buffer`,
73+
(helper: GroupHelper) => {
74+
helper.define(
75+
"BufReadCmd",
76+
`denopsapp://${this.#appName}/*`,
77+
`call denops#notify('${this.#denops.name}', 'renderContents', [expand('<amatch>')])`,
78+
);
79+
},
80+
);
81+
}
82+
}

buffer.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { Denops } from "./deps.ts";
2+
3+
export interface Buffer {
4+
render: (denops: Denops) => Promise<void>;
5+
}

deps.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export { Denops } from "https://deno.land/x/denops_std@v1.0.0-alpha.0/mod.ts";
2+
3+
export {
4+
execute,
5+
} from "https://deno.land/x/denops_std@v1.0.0-alpha.0/helper/mod.ts";
6+
7+
export {
8+
group,
9+
GroupHelper,
10+
} from "https://deno.land/x/denops_std@v1.0.0-alpha.0/autocmd/mod.ts";

list.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { Denops, execute } from "./deps.ts";
2+
import { Buffer } from "./buffer.ts";
3+
4+
export class ListWidget<T> implements Buffer {
5+
#items: {
6+
item: T;
7+
renderFn: (item: T) => string;
8+
}[];
9+
10+
#keybinds: {
11+
[key: string]: (denops: Denops, item: T) => Promise<void>;
12+
};
13+
14+
constructor() {
15+
this.#items = [];
16+
this.#keybinds = {};
17+
}
18+
19+
setItem(item: T, renderFn: (item: T) => string) {
20+
this.#items.push({ item, renderFn });
21+
}
22+
23+
handleKey(key: string, handler: (denops: Denops, item: T) => Promise<void>) {
24+
this.#keybinds[key] = handler;
25+
}
26+
27+
async render(denops: Denops) {
28+
await denops.call(
29+
"setline",
30+
1,
31+
this.#items.map((item) => item.renderFn(item.item)),
32+
);
33+
34+
Object.keys(this.#keybinds).forEach(async (key) => {
35+
denops.dispatcher[`keyHandler`] = async (
36+
index: unknown,
37+
): Promise<void> => {
38+
await this.#keybinds[key](
39+
denops,
40+
this.#items[(index as number)].item,
41+
);
42+
};
43+
44+
await execute(
45+
denops,
46+
[
47+
`nmap <buffer> <expr> ${key} denops#notify('${denops.name}', 'keyHandler', [line('.') - 1])`,
48+
],
49+
);
50+
});
51+
52+
await execute(
53+
denops,
54+
[
55+
`setlocal bufhidden=hide`,
56+
`setlocal buftype=nofile`,
57+
`setlocal nobuckup`,
58+
`setlocal noswapfile`,
59+
`setlocal nomodified`,
60+
`setlocal nomodifiable`,
61+
],
62+
);
63+
}
64+
}

mod.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export * from "./app.ts";
2+
export * from "./router.ts";
3+
export * from "./buffer.ts";
4+
export * from "./list.ts";
5+
export * from "./text.ts";

router.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Buffer } from "./buffer.ts";
2+
3+
export type Context = { [key: string]: string };
4+
5+
export type Component = (context: Context) => Promise<Buffer>;
6+
7+
export interface Route {
8+
path: string;
9+
component: Component;
10+
}

text.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Denops, execute } from "./deps.ts";
2+
import { Buffer } from "./buffer.ts";
3+
4+
export class TextWidget implements Buffer {
5+
#content: string;
6+
constructor() {
7+
this.#content = "";
8+
}
9+
10+
setContent(content: string) {
11+
this.#content = content;
12+
}
13+
14+
async render(denops: Denops) {
15+
await denops.call(
16+
"setline",
17+
1,
18+
this.#content.split(/\r?\n/g),
19+
);
20+
21+
await execute(
22+
denops,
23+
[
24+
`setlocal bufhidden=hide`,
25+
`setlocal buftype=nofile`,
26+
`setlocal nobuckup`,
27+
`setlocal noswapfile`,
28+
`setlocal nomodified`,
29+
`setlocal nomodifiable`,
30+
],
31+
);
32+
}
33+
}

0 commit comments

Comments
 (0)