Repository: gera2ld/coc-markmap
Branch: master
Commit: 544d8686897b
Files: 15
Total size: 15.3 KB
Directory structure:
gitextract_1ur1piy3/
├── .editorconfig
├── .eslintignore
├── .eslintrc.cjs
├── .github/
│ └── workflows/
│ └── publish.yml
├── .gitignore
├── .husky/
│ └── pre-push
├── .npmrc
├── LICENSE
├── README.md
├── babel.config.cjs
├── package.json
├── rollup.config.mjs
├── src/
│ ├── bridge.ts
│ └── index.ts
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
quote_type = single
================================================
FILE: .eslintignore
================================================
/*
!/src
================================================
FILE: .eslintrc.cjs
================================================
module.exports = {
extends: [require.resolve('@gera2ld/plaid/eslint')],
rules: {},
};
================================================
FILE: .github/workflows/publish.yml
================================================
name: Publish to npmjs
on:
push:
tags:
- v*
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9
run_install: false
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
registry-url: 'https://registry.npmjs.org'
- run: pnpm i && pnpm publish --no-git-checks
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
================================================
FILE: .gitignore
================================================
node_modules
*.log
/.idea
/dist
/.nyc_output
/coverage
/types
================================================
FILE: .husky/pre-push
================================================
npm run lint
================================================
FILE: .npmrc
================================================
shamefully-hoist = true
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 Gerald
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# coc-markmap

Visualize your Markdown as mindmaps with [markmap](https://markmap.js.org/).
This is an extension for [coc.nvim](https://github.com/neoclide/coc.nvim).
If you prefer a CLI version, see [markmap-cli](https://markmap.js.org/docs/packages--markmap-cli).
Note: _coc-markmap_ uses _markmap-cli_ under the hood, and supports more features by connecting the Markmap with the current buffer, such as highlighting the node under cursor.
<img src="https://user-images.githubusercontent.com/3139113/72221499-52476a80-3596-11ea-8d15-c57fdfe04ce0.png" alt="markdown" width="300"> <img src="https://user-images.githubusercontent.com/3139113/72221508-7014cf80-3596-11ea-9b59-b8a97bba8e1c.png" alt="mindmap" width="300">
## Installation
First, make sure [coc.nvim](https://github.com/neoclide/coc.nvim) is started.
Then install with the Vim command:
```
:CocInstall coc-markmap
```
## Usage
You can run the commands below **in a buffer of Markdown file**.
### Generating a markmap HTML
```viml
:CocCommand markmap.create
```
Inline all assets to work offline:
```viml
:CocCommand markmap.create --offline
```
**This command will create an HTML file rendering the markmap and can be easily shared.**
The HTML file will have the same basename as the Markdown file and will be opened in your default browser. If there is a selection, it will be used instead of the file content.
### Watching mode
```viml
:CocCommand markmap.watch
```
**This command will start a development server, watch the current buffer and track your cursor.**
The markmap will update once the markdown file changes, and the node under cursor will always be visible in the viewport on cursor move.
```viml
:CocCommand markmap.unwatch
```
**The command will unwatch the current buffer.**
## Configurations
### CocConfig
You can change some global configurations for this extension in `coc-settings.json`.
First open the settings file with `:CocConfig`.
### Key mappings
There is no default key mapping, but you can easily add your own:
```viml
" Create markmap from the whole file
nmap <Leader>m <Plug>(coc-markmap-create)
```
### Commands
It is also possible to add a command to create markmaps.
```viml
command! -range=% Markmap CocCommand markmap.create <line1> <line2>
```
Now you have the `:Markmap` command to create a Markmap, either from the whole file or selected lines.
================================================
FILE: babel.config.cjs
================================================
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-typescript'],
};
================================================
FILE: package.json
================================================
{
"name": "coc-markmap",
"version": "0.8.0",
"description": "Visualize your Markdown as mindmaps with Markmap",
"author": "Gerald <gera2ld@live.com>",
"license": "MIT",
"scripts": {
"prepare": "husky install",
"dev": "rollup -cw",
"clean": "del-cli dist",
"prepublishOnly": "run-s build",
"ci": "run-s lint",
"build:js": "rollup -c",
"build": "run-s ci clean build:js",
"lint": "eslint --ext .ts . && prettier -c src",
"lint:fix": "eslint --ext .ts . --fix && prettier -c src -w"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"main": "dist/index.js",
"files": [
"dist"
],
"devDependencies": {
"@gera2ld/plaid": "~2.7.0",
"@gera2ld/plaid-rollup": "~2.7.0",
"@types/node": "^20.11.17",
"coc.nvim": "0.0.83-next.9",
"del-cli": "^6.0.0",
"es-toolkit": "^1.31.0",
"husky": "^9.1.7"
},
"dependencies": {
"markmap-cli": "0.18.7",
"open": "^10.1.0"
},
"engines": {
"coc": ">=0.0.80",
"node": ">=18"
},
"keywords": [
"coc.nvim",
"markmap"
],
"activationEvents": [
"onLanguage:markdown"
],
"contributes": {
"configuration": {
"title": "coc-markmap",
"properties": {}
}
},
"repository": "git@github.com:gera2ld/coc-markmap.git",
"browserslist": [
"node >= 18"
]
}
================================================
FILE: rollup.config.mjs
================================================
import { defineExternal, definePlugins } from '@gera2ld/plaid-rollup';
import { defineConfig } from 'rollup';
import pkg from './package.json' with { type: 'json' };
export default defineConfig([
{
input: './src/index.ts',
plugins: definePlugins({
esm: true,
}),
external: defineExternal(['coc.nvim', ...Object.keys(pkg.dependencies)]),
output: {
format: 'cjs',
dir: 'dist',
},
},
{
input: './src/bridge.ts',
plugins: definePlugins({
esm: true,
}),
external: defineExternal(['coc.nvim', ...Object.keys(pkg.dependencies)]),
output: {
format: 'es',
dir: 'dist',
},
},
]);
================================================
FILE: src/bridge.ts
================================================
import { createHash } from 'crypto';
import {
MarkmapDevServer,
config,
createMarkmap,
develop,
fetchAssets,
} from 'markmap-cli';
import open from 'open';
let devServer: MarkmapDevServer | undefined;
type MaybePromise<T> = T | Promise<T>;
const handlers: Record<string, (data: unknown) => MaybePromise<unknown>> = {
initialize(options: { assetsDir: string }) {
config.assetsDir = options.assetsDir;
},
async createMarkmap(options: Record<string, unknown>) {
await fetchAssets();
await createMarkmap({
open: true,
toolbar: true,
offline: false,
...options,
});
},
async startServer(options?: Record<string, unknown>) {
if (!devServer) {
await fetchAssets();
devServer = await develop({
toolbar: true,
offline: true,
...options,
});
}
return (
devServer.serverInfo && {
port: devServer.serverInfo.address.port,
}
);
},
addProvider(filePath: string) {
const key = createHash('sha256')
.update(filePath, 'utf8')
.digest('hex')
.slice(0, 7);
const provider = invariant(devServer).addProvider({ key });
return provider.key;
},
delProvider(key: string) {
invariant(devServer).delProvider(key);
},
setContent(data: { key: string; content: string }) {
const provider = invariant(devServer?.providers[data.key]);
provider.setContent(data.content);
},
setCursor(data: { key: string; line: number }) {
const provider = invariant(devServer?.providers[data.key]);
provider.setCursor(data.line);
},
stopServer() {
if (!devServer) return;
devServer.shutdown();
devServer = undefined;
},
openUrl(url: string) {
open(url);
},
};
process.on(
'message',
async ({ id, cmd, data }: { id: number; cmd: string; data: unknown }) => {
const handler = handlers[cmd];
let result: unknown;
let error: string | undefined;
try {
result = await handler?.(data);
} catch (err) {
error = `${err}`;
}
process.send?.({
id,
cmd: '_setResult',
data: { result, error },
});
},
);
function invariant<T>(input: T | undefined, message?: string): T {
if (!input) throw new Error(message || 'input is required');
return input;
}
================================================
FILE: src/index.ts
================================================
import {
Disposable,
ExtensionContext,
Logger,
commands,
events,
window,
workspace,
} from 'coc.nvim';
import { spawn } from 'node:child_process';
import { basename, extname, resolve } from 'node:path';
// Note: only CJS is supported by coc.nvim, so we must bundle it
import { debounce } from 'es-toolkit';
class CocMarkmapBridge {
private _child = spawn(process.execPath, [resolve(__dirname, 'bridge.js')], {
cwd: __dirname,
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
});
serverInfo: { port: number } | undefined;
id = 0;
private _callbacks: Record<
number,
(data: { result: unknown; error?: string }) => void
> = {};
private _connectedBuffers: Record<number, string> = {};
private _disposables: Disposable[] = [];
constructor(private logger: Logger) {
this._child.on(
'message',
(message: {
id: number;
cmd: string;
data: { result: unknown; error?: string };
}) => {
this._callbacks[message.id]?.(message.data);
delete this._callbacks[message.id];
},
);
this._disposables.push(Disposable.create(() => this.stopServer()));
this._disposables.push(events.on('TextChanged', this.handleTextChange));
this._disposables.push(events.on('TextChangedI', this.handleTextChange));
this._disposables.push(events.on('CursorMoved', this.handleCursorChange));
this._disposables.push(events.on('CursorMovedI', this.handleCursorChange));
}
private _send<T>(cmd: string, data?: unknown): Promise<T> {
this.id += 1;
this._child.send({ id: this.id, cmd, data });
return new Promise((resolve, reject) => {
this._callbacks[this.id] = (data) => {
if (data.error) reject(data.error);
else resolve(data.result as T);
};
});
}
initialize(assetsDir: string) {
return this._send('initialize', { assetsDir });
}
destroy() {
this._child.kill();
}
isServerStarted() {
return !!this.serverInfo;
}
async startServer() {
this.serverInfo = await this._send('startServer');
}
async stopServer() {
if (!this.serverInfo) return;
await this._send('stopServer');
this.serverInfo = undefined;
}
async setContent(key: string, content: string) {
await this._send('setContent', { key, content });
}
async setCursor(key: string, line: number) {
await this._send('setCursor', { key, line });
}
async connectBuffer() {
await this.startServer();
const { nvim } = workspace;
const buffer = await nvim.buffer;
const filePath = (await nvim.eval('expand("%:p")')) as string;
const filename = basename(filePath);
const key =
this._connectedBuffers[buffer.id] ||
(await this._send<string>('addProvider', filePath));
this._connectedBuffers[buffer.id] = key;
this.handleTextChange(buffer.id);
const url = `http://localhost:${this.serverInfo?.port}/?key=${key}&filename=${encodeURIComponent(filename)}`;
window.showInformationMessage(
`Buffer ${buffer.id}: Markmap is served at ${url}`,
);
this._send('openUrl', url);
}
async disconnectBuffer() {
const { nvim } = workspace;
const buffer = await nvim.buffer;
const key = this._connectedBuffers[buffer.id];
if (key) {
await this._send('delProvider', key);
delete this._connectedBuffers[buffer.id];
window.showInformationMessage(`Buffer ${buffer.id}: Markmap is disposed`);
}
}
async createMarkmap(options?: Record<string, unknown>) {
const { nvim } = workspace;
const filePath = (await nvim.eval('expand("%:p")')) as string;
const name = basename(filePath, extname(filePath));
const output = resolve(`${name}.html`);
const doc = await workspace.document;
const content = doc.textDocument.getText();
const createOptions = {
content,
output,
...options,
};
await this._send('createMarkmap', createOptions);
}
private _bufferIds = new Set<number>();
private _updateContents = debounce(async () => {
const { nvim } = workspace;
const buffers = await nvim.buffers;
const matchedBuffers = buffers.filter((buffer) =>
this._bufferIds.has(buffer.id),
);
this._bufferIds.clear();
for (const buffer of matchedBuffers) {
const key = this._connectedBuffers[buffer.id];
if (!key) continue;
const lines = await buffer.getLines();
await this.setContent(key, lines.join('\n'));
}
this.logger.info('Content updated');
}, 500);
handleTextChange = (bufferId: number) => {
if (!this._connectedBuffers[bufferId]) return;
this.logger.info(`Buffer ${bufferId}: text change`);
this._bufferIds.add(bufferId);
this._updateContents();
};
handleCursorChange = debounce(async () => {
const { nvim } = workspace;
const buffer = await nvim.buffer;
const key = this._connectedBuffers[buffer.id];
if (!key) return;
this.logger.info('Cursor change:', events.cursor.lnum);
await this._send('setCursor', { key, line: events.cursor.lnum - 1 });
}, 300);
}
export function activate(context: ExtensionContext) {
// const config = workspace.getConfiguration('markmap');
const { logger, storagePath } = context;
const loading = (async () => {
logger.info('Initialize bridge...');
const bridge = new CocMarkmapBridge(logger);
await bridge.initialize(storagePath);
logger.info('Bridge loaded');
return bridge;
})();
context.subscriptions.push(
workspace.registerKeymap(
['n'],
'markmap-create',
async () => {
const bridge = await loading;
await bridge.createMarkmap();
},
{ sync: false },
),
);
context.subscriptions.push(
commands.registerCommand('markmap.create', async (...args: string[]) => {
const options = {
offline: args.includes('--offline'),
};
const bridge = await loading;
await bridge.createMarkmap(options);
}),
);
context.subscriptions.push(
commands.registerCommand('markmap.watch', async () => {
const bridge = await loading;
await bridge.connectBuffer();
}),
);
context.subscriptions.push(
commands.registerCommand('markmap.unwatch', async () => {
const bridge = await loading;
await bridge.disconnectBuffer();
}),
);
context.subscriptions.push(
commands.registerCommand('markmap.stop', async () => {
const bridge = await loading;
await bridge.stopServer();
}),
);
}
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "Node",
"outDir": "dist",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"strictNullChecks": true,
"skipLibCheck": true
},
"include": [
"src/**/*"
]
}
gitextract_1ur1piy3/ ├── .editorconfig ├── .eslintignore ├── .eslintrc.cjs ├── .github/ │ └── workflows/ │ └── publish.yml ├── .gitignore ├── .husky/ │ └── pre-push ├── .npmrc ├── LICENSE ├── README.md ├── babel.config.cjs ├── package.json ├── rollup.config.mjs ├── src/ │ ├── bridge.ts │ └── index.ts └── tsconfig.json
SYMBOL INDEX (25 symbols across 2 files)
FILE: src/bridge.ts
type MaybePromise (line 13) | type MaybePromise<T> = T | Promise<T>;
method initialize (line 16) | initialize(options: { assetsDir: string }) {
method createMarkmap (line 19) | async createMarkmap(options: Record<string, unknown>) {
method startServer (line 28) | async startServer(options?: Record<string, unknown>) {
method addProvider (line 43) | addProvider(filePath: string) {
method delProvider (line 51) | delProvider(key: string) {
method setContent (line 54) | setContent(data: { key: string; content: string }) {
method setCursor (line 58) | setCursor(data: { key: string; line: number }) {
method stopServer (line 62) | stopServer() {
method openUrl (line 67) | openUrl(url: string) {
function invariant (line 91) | function invariant<T>(input: T | undefined, message?: string): T {
FILE: src/index.ts
class CocMarkmapBridge (line 15) | class CocMarkmapBridge {
method constructor (line 34) | constructor(private logger: Logger) {
method _send (line 53) | private _send<T>(cmd: string, data?: unknown): Promise<T> {
method initialize (line 64) | initialize(assetsDir: string) {
method destroy (line 68) | destroy() {
method isServerStarted (line 72) | isServerStarted() {
method startServer (line 76) | async startServer() {
method stopServer (line 80) | async stopServer() {
method setContent (line 86) | async setContent(key: string, content: string) {
method setCursor (line 90) | async setCursor(key: string, line: number) {
method connectBuffer (line 94) | async connectBuffer() {
method disconnectBuffer (line 112) | async disconnectBuffer() {
method createMarkmap (line 123) | async createMarkmap(options?: Record<string, unknown>) {
function activate (line 173) | function activate(context: ExtensionContext) {
Condensed preview — 15 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (17K chars).
[
{
"path": ".editorconfig",
"chars": 193,
"preview": "# http://editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_tr"
},
{
"path": ".eslintignore",
"chars": 9,
"preview": "/*\n!/src\n"
},
{
"path": ".eslintrc.cjs",
"chars": 90,
"preview": "module.exports = {\n extends: [require.resolve('@gera2ld/plaid/eslint')],\n rules: {},\n};\n"
},
{
"path": ".github/workflows/publish.yml",
"chars": 575,
"preview": "name: Publish to npmjs\n\non:\n push:\n tags:\n - v*\n\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - u"
},
{
"path": ".gitignore",
"chars": 62,
"preview": "node_modules\n*.log\n/.idea\n/dist\n/.nyc_output\n/coverage\n/types\n"
},
{
"path": ".husky/pre-push",
"chars": 13,
"preview": "npm run lint\n"
},
{
"path": ".npmrc",
"chars": 24,
"preview": "shamefully-hoist = true\n"
},
{
"path": "LICENSE",
"chars": 1063,
"preview": "MIT License\n\nCopyright (c) 2019 Gerald\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
},
{
"path": "README.md",
"chars": 2434,
"preview": "# coc-markmap\n\n\n\nVisualize your Markdown as mindmaps with [markmap]("
},
{
"path": "babel.config.cjs",
"chars": 84,
"preview": "module.exports = {\n presets: ['@babel/preset-env', '@babel/preset-typescript'],\n};\n"
},
{
"path": "package.json",
"chars": 1376,
"preview": "{\n \"name\": \"coc-markmap\",\n \"version\": \"0.8.0\",\n \"description\": \"Visualize your Markdown as mindmaps with Markmap\",\n "
},
{
"path": "rollup.config.mjs",
"chars": 663,
"preview": "import { defineExternal, definePlugins } from '@gera2ld/plaid-rollup';\nimport { defineConfig } from 'rollup';\nimport pkg"
},
{
"path": "src/bridge.ts",
"chars": 2293,
"preview": "import { createHash } from 'crypto';\nimport {\n MarkmapDevServer,\n config,\n createMarkmap,\n develop,\n fetchAssets,\n}"
},
{
"path": "src/index.ts",
"chars": 6510,
"preview": "import {\n Disposable,\n ExtensionContext,\n Logger,\n commands,\n events,\n window,\n workspace,\n} from 'coc.nvim';\nimp"
},
{
"path": "tsconfig.json",
"chars": 294,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"esnext\",\n \"module\": \"esnext\",\n \"moduleResolution\": \"Node\",\n \"outDir\": \""
}
]
About this extraction
This page contains the full source code of the gera2ld/coc-markmap GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 15 files (15.3 KB), approximately 4.4k tokens, and a symbol index with 25 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.