├── .editorconfig
├── .github
├── workflows
│ └── push.yml
└── xvfb.init
├── .gitignore
├── LICENSE.md
├── README.md
├── imgs
├── codeaction.png
└── hover.png
├── package-lock.json
├── package.json
├── packages
├── .eslintrc.base.json
├── test-workspace
│ ├── simple
│ │ └── README.md
│ └── test.code-workspace
├── test
│ ├── .textlintignore
│ ├── .textlintrc
│ ├── .vscode
│ │ └── settings.json
│ ├── igignorenore.md
│ ├── package-lock.json
│ ├── package.json
│ ├── test.html
│ ├── test.md
│ ├── testtest.tex
│ ├── testtest.txt
│ └── testtest.vue
├── textlint-server
│ ├── .eslintignore
│ ├── .eslintrc.json
│ ├── .vscode
│ │ ├── launch.json
│ │ └── settings.json
│ ├── package.json
│ ├── src
│ │ ├── autofix.ts
│ │ ├── server.ts
│ │ ├── textlint.d.ts
│ │ ├── thenable.d.ts
│ │ └── types.ts
│ └── tsconfig.json
└── textlint
│ ├── .eslintignore
│ ├── .eslintrc.json
│ ├── .vscode
│ ├── launch.json
│ ├── settings.json
│ └── tasks.json
│ ├── .vscodeignore
│ ├── LICENSE.txt
│ ├── README.md
│ ├── package.json
│ ├── src
│ ├── extension.ts
│ ├── status.ts
│ └── types.ts
│ ├── test
│ ├── extension.test.ts
│ ├── index.ts
│ ├── runTest.ts
│ └── types.ts
│ ├── textlint-icon_128x128.png
│ ├── tsconfig.json
│ └── webpack.config.js
└── vscode-textlint.code-workspace
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | end_of_line = lf
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [{package.json,circle.yml}]
11 | indent_style = space
12 | indent_size = 2
13 |
--------------------------------------------------------------------------------
/.github/workflows/push.yml:
--------------------------------------------------------------------------------
1 | name: push
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | windows:
13 | name: Windows
14 | runs-on: windows-latest
15 | timeout-minutes: 30
16 |
17 | steps:
18 | - uses: actions/checkout@v2
19 |
20 | - uses: actions/setup-node@v2
21 | with:
22 | node-version: 16
23 |
24 | - name: Get npm cache directory
25 | id: npm-cache
26 | run: |
27 | echo "::set-output name=dir::$(npm config get cache)"
28 | - uses: actions/cache@v2
29 | with:
30 | path: ${{ steps.npm-cache.outputs.dir }}
31 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
32 | restore-keys: |
33 | ${{ runner.os }}-node-
34 |
35 | - run: npm install
36 | - run: cd packages/test && npm install
37 | - run: npm run lint
38 | - run: npm run compile
39 | - run: npm run webpack
40 | - run: npm test
41 |
42 | linux:
43 | name: Linux
44 | runs-on: ubuntu-latest
45 | timeout-minutes: 30
46 | env:
47 | DISPLAY: ":10"
48 | steps:
49 | - uses: actions/checkout@v2
50 |
51 | - name: Setup Build Environment
52 | run: |
53 | sudo apt-get update
54 | sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1
55 | sudo cp .github/xvfb.init /etc/init.d/xvfb
56 | sudo chmod +x /etc/init.d/xvfb
57 | sudo update-rc.d xvfb defaults
58 | sudo service xvfb start
59 | - uses: actions/setup-node@v2
60 | with:
61 | node-version: 16
62 |
63 | - uses: actions/cache@v2
64 | with:
65 | path: ~/.npm
66 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
67 | restore-keys: |
68 | ${{ runner.os }}-node-
69 |
70 | - run: npm install
71 | - run: cd packages/test && npm install
72 | - run: npm run lint
73 | - run: npm run compile
74 | - run: npm run webpack
75 | - run: npm test
76 |
77 | darwin:
78 | name: macOS
79 | runs-on: macos-latest
80 | timeout-minutes: 30
81 | env:
82 | DISPLAY: ":10"
83 | steps:
84 | - uses: actions/checkout@v2
85 | - uses: actions/setup-node@v2
86 | with:
87 | node-version: 16
88 |
89 | - uses: actions/cache@v2
90 | with:
91 | path: ~/.npm
92 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
93 | restore-keys: |
94 | ${{ runner.os }}-node-
95 |
96 | - run: npm install
97 | - run: cd packages/test && npm install
98 | - run: npm run lint
99 | - run: npm run compile
100 | - run: npm run webpack
101 | - run: npm test
102 |
--------------------------------------------------------------------------------
/.github/xvfb.init:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # /etc/rc.d/init.d/xvfbd
4 | #
5 | # chkconfig: 345 95 28
6 | # description: Starts/Stops X Virtual Framebuffer server
7 | # processname: Xvfb
8 | #
9 | ### BEGIN INIT INFO
10 | # Provides: xvfb
11 | # Required-Start: $remote_fs $syslog
12 | # Required-Stop: $remote_fs $syslog
13 | # Default-Start: 2 3 4 5
14 | # Default-Stop: 0 1 6
15 | # Short-Description: Start xvfb at boot time
16 | # Description: Enable xvfb provided by daemon.
17 | ### END INIT INFO
18 |
19 | [ "${NETWORKING}" = "no" ] && exit 0
20 |
21 | PROG="/usr/bin/Xvfb"
22 | PROG_OPTIONS=":10 -ac"
23 | PROG_OUTPUT="/tmp/Xvfb.out"
24 |
25 | case "$1" in
26 | start)
27 | echo "Starting : X Virtual Frame Buffer "
28 | $PROG $PROG_OPTIONS>>$PROG_OUTPUT 2>&1 &
29 | disown -ar
30 | ;;
31 | stop)
32 | echo "Shutting down : X Virtual Frame Buffer"
33 | killproc $PROG
34 | RETVAL=$?
35 | [ $RETVAL -eq 0 ] && /bin/rm -f /var/lock/subsys/Xvfb
36 | /var/run/Xvfb.pid
37 | echo
38 | ;;
39 | restart|reload)
40 | $0 stop
41 | $0 start
42 | RETVAL=$?
43 | ;;
44 | status)
45 | status Xvfb
46 | RETVAL=$?
47 | ;;
48 | *)
49 | echo $"Usage: $0 (start|stop|restart|reload|status)"
50 | exit 1
51 | esac
52 |
53 | exit $RETVAL
54 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Coverage directory used by tools like istanbul
12 | coverage
13 |
14 | # node-waf configuration
15 | .lock-wscript
16 |
17 | # Compiled binary addons (http://nodejs.org/api/addons.html)
18 | build/Release
19 |
20 | # Dependency directory
21 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
22 | node_modules
23 |
24 | out
25 | lib
26 | dist
27 | .vscode-test
28 | *.vsix
29 |
30 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 |
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2016 taichi
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vscode-textlint 
2 |
3 | This repository is no longer being actively maintained. I've passed the torch to the community, and the project has found a new home.
4 | If you're looking for the actively maintained version of vscode-textlint, please head over to:
5 |
6 | https://github.com/textlint/vscode-textlint
7 |
8 | Extension to integrate [textlint](https://textlint.github.io/) into VSCode.
9 |
10 | ## Development setup
11 |
12 | - open `vscode-textlint.code-workspace` by VS Code
13 | - run `npm install` inside the **root** folder
14 | - hit F5 to build and debug the extension
15 |
16 | ## How to release
17 |
18 | 1. run `npm upgrade` inside the **root** folder
19 | 2. run `npm install` inside the **root** folder
20 | 3. run `vsce publish` inside the **packages/textlint** folder
21 |
--------------------------------------------------------------------------------
/imgs/codeaction.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taichi/vscode-textlint/780d72c5f9e76310188a2375de4207368594f8af/imgs/codeaction.png
--------------------------------------------------------------------------------
/imgs/hover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taichi/vscode-textlint/780d72c5f9e76310188a2375de4207368594f8af/imgs/hover.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vscode-textlint-parent",
3 | "private": true,
4 | "workspaces": [
5 | "packages/textlint",
6 | "packages/textlint-server"
7 | ],
8 | "scripts": {
9 | "clean": "npm run clean --ws",
10 | "compile": "npm run compile --ws",
11 | "webpack": "npm run webpack -w vscode-textlint",
12 | "test": "npm run test --ws --if-present",
13 | "lint": "npm exec --ws -- eslint --config .eslintrc.json src/**.ts",
14 | "fix": "run-s fix:prettier fix:eslint",
15 | "fix:eslint": "npm exec --ws -- eslint . --ext .ts --fix",
16 | "fix:prettier": "npm exec --ws -- prettier --write . --config ../../package.json --ignore-path ../../.gitignore",
17 | "sort": "npm exec --include-workspace-root --ws -- sort-package-json",
18 | "version": "npm version --ws ",
19 | "upgrade": "npm exec --ws -- ncu -u"
20 | },
21 | "devDependencies": {
22 | "@typescript-eslint/eslint-plugin": "^5.2.0",
23 | "@typescript-eslint/parser": "^5.2.0",
24 | "eslint": "^8.1.0",
25 | "eslint-config-prettier": "^8.3.0",
26 | "npm-check-updates": "^11.8.5",
27 | "npm-run-all": "^4.1.5",
28 | "prettier": "^2.4.1",
29 | "sort-package-json": "^1.52.0"
30 | },
31 | "engines": {
32 | "node": ">=16.0.0"
33 | },
34 | "prettier": {
35 | "printWidth": 120
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/.eslintrc.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "parserOptions": {
4 | "ecmaVersion": 6,
5 | "sourceType": "module"
6 | },
7 | "plugins": ["@typescript-eslint"],
8 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
9 | "env": {
10 | "node": true
11 | },
12 | "rules": {
13 | "semi": "off",
14 | "@typescript-eslint/semi": "error",
15 | "no-extra-semi": "warn",
16 | "curly": "warn",
17 | "quotes": ["error", "double", { "allowTemplateLiterals": true }],
18 | "eqeqeq": "error",
19 | "indent": "off",
20 | "@typescript-eslint/no-namespace": "off"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/test-workspace/simple/README.md:
--------------------------------------------------------------------------------
1 | yuo
2 |
3 | yuo
4 |
5 | yuo
6 |
7 | gilr
8 |
--------------------------------------------------------------------------------
/packages/test-workspace/test.code-workspace:
--------------------------------------------------------------------------------
1 | {
2 | "folders": [
3 | {
4 | "path": "simple"
5 | },
6 | {
7 | "path": "second"
8 | },
9 | {
10 | "path": "../test"
11 | }
12 | ],
13 | "settings": {
14 | "textlint.trace": "verbose"
15 |
16 | },
17 | }
18 |
--------------------------------------------------------------------------------
/packages/test/.textlintignore:
--------------------------------------------------------------------------------
1 | igignorenore.md
2 |
--------------------------------------------------------------------------------
/packages/test/.textlintrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": {
3 | "html": true,
4 | "latex2e": true
5 | },
6 | "filters": {},
7 | "rules": {
8 | "no-todo": true,
9 | "common-misspellings": true,
10 | "preset-ja-technical-writing": true
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/test/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "textlint.run": "onType",
3 | "textlint.autoFixOnSave": false,
4 | "textlint.trace": "verbose"
5 | }
6 |
--------------------------------------------------------------------------------
/packages/test/igignorenore.md:
--------------------------------------------------------------------------------
1 | [JJUG CCC 2015 Spring(4月11日開催)](http://www.java-users.jp/?page_id=1647) で発表をしてきました。
2 |
3 | 一コマ目であり、エントランスから一番近い入り易い場所だったせいもあるとは思いますが立ち見が出る程の盛況ぶりでした。発表を聞いて下さった皆様、本当にありがとうございます。
4 |
5 | 発表資料はこちらです。
6 |
7 | * [Past & Future of null in Java](https://docs.google.com/presentation/d/1Zb-YYnGewELdsLMAjXZU-5bmI7LxTEm8PNnVUqZl0PI/edit)
8 |
9 | 発表者がどういう風に考えてコンテンツを作り、どういう準備をしているのか、というのは余り共有されていないように思います
10 |
11 | このエントリでは僕がどの様に事前準備を行い、当日はどんな風に考えながら発表していたのか記録しておきます。
12 |
13 | # 事前準備
14 |
15 | ## 内容の決め方
16 | まず、50分では前提条件の多くなる話はできませんので、凡そ言語仕様かライブラリの話をするのが妥当でしょうとアタリを付けます。
17 |
18 | 恐らくビギナー向けを標榜しつつも、設計方法論などメタモデルについて話をするのが良いのでは無いかと考えます。
19 |
20 | Javaの標準ライブラリは表層的な使い方の話をするのは簡単なんですけども、何故そういう設計になっているのかという話は、前提条件が多くなるので望ましく無い、つまり言語仕様について話すという方向性で固まります。
21 |
22 | その頃に、[脱ビギナー!Androidのnullな話](http://techlife.cookpad.com/entry/2015/02/20/195000)が良い感じにバズっていたのを思い出します。
23 |
24 | null の話はビギナー向けとして悪くないし、そこでの考え方や方法論について議論するのであれば、エキスパート同士の会話としても成立するだろうと類推します。
25 |
26 | nullの話をするのであれば、Java8で追加されたOptionalについて話すのが良いでしょう。そう思って手元にあるRSSリーダーからOptionalの記事をガシガシと抜き出します。そこで見つけたのが、今回の発表のネタ元となる [Embracing the Void: 6 Refined Tricks for Dealing with Nulls in Java](https://www.voxxed.com/blog/2015/01/embracing-void-6-refined-tricks-dealing-nulls-java/) です。
27 |
28 | このエントリを繰り返し読んで、自分なりの理解を作ります。[Should java 8 getters return optional type?](http://stackoverflow.com/questions/26327957/should-java-8-getters-return-optional-type/26328555#26328555) でOracleのブライアンによる返答は広く知られるべきものです。
29 |
30 | その時点におけるぼんやりとした僕の理解を箇条書きするとこうなります。
31 |
32 | * Optionalは便利であるけども濫用は避けるべきAPI
33 | * そもそもOptionalって何が便利なんだっけ?
34 | * Optionalを使うモチベーションの話とOptionalに至る話は分けて考えよう
35 | * ScalaのOption型との違いはなんだっけか?
36 |
37 | これで、アウトラインが見えてきましたのでCfPを書きます。
38 |
39 | ## 構成について
40 |
41 | スライドを書き始める前にmarkdownで5000文字程の文章を書いて論理的な構造に根源的な破たんが無いかどうかを簡単に確認します。
42 |
43 | この時点でスライドの中に含めるサンプルコードも併せて書いた上で、コードをmarkdownの中に入れておきます。サンプルコードは**28pt**で表示して画面内に収まるような量にします。ディスプレイだけで見ているとフォントサイズが少々小さくてもコードは読めますが、プロジェクタに映すと小さい文字は基本的に読めません。
44 |
45 | この時点で、サンプルコードの難易度も調整します。コードの量が多いと細かい説明をキチンとしないと伝わらないので、一つのコードでは一つの事だけを表現し、できるだけ余分なものを削ります。
46 |
47 | ## スライドのデザインについて
48 |
49 | 僕にはデザイン能力が欠如しているので、ネタ元となるカッコいいデザインのスライドを探します。今回のスライドで参考にしたスライドはこれらです。
50 |
51 | * http://www.slideshare.net/lafarge777/aea-2013recaphd
52 | * http://noteandpoint.com/2011/06/psfk-presents-future-of-mobile-tagging-report/
53 |
54 | 今回僕の能力でこれらのスライドから読み取れたのは、
55 |
56 | * 写真をテキトーにボカしてスライドの背景にするとカッケェ
57 | * 半透明のボックスを文字の背景に置くとカッケェし見易い
58 |
59 | 僕のヴィジュアルデザイン能力の欠如が明確に分かってもらえると思います。
60 |
61 | これで、Googlesスライドのテンプレをせっせと作ります。尚、「表示」メニューにある「マスター」を選択するとテンプレを編集できます。
62 |
63 | テンプレをちゃんと作らないとスライドを書く速度を上げられないのでテンプレは作りましょう。
64 |
65 | ## 発表時に進捗が分かるようにする
66 |
67 | スライド一枚辺りの情報量をなるべく揃えると、単純にページ数だけで進捗が分かるので喋り易くなります。
68 |
69 | 一定のテンポでスライドをめくるれるように資料を作ると少々緊張していても、終了時間を適切にコントロールできます。
70 |
71 | 単に右下辺りに分数を出しても良いのですが、背景や文字の色で自分だけに進捗が分かるようにすると、カッコつけられます。
72 |
73 | # 発表開始前
74 |
75 | ## 飲み物について
76 | 普段はウーロン茶を飲んでいるのですけども、大きな声で発表する場合には喉が渇き易いウーロン茶はあまり望ましくありません。
77 |
78 | なので、頭の片隅にあった「リンゴジュースを飲みながらしゃべるとマイクにペチャクチャした音が入り辛い」という謎情報を試してみました。Rebuild.fmか何かで言ってたような気がします。
79 |
80 | これが非常に上手くいったので、発表中に飲むのはリンゴジュースがオススメです。
81 |
82 | ## 10分おきのタイムキーピング
83 | 今回の発表時間は50分でしたが、こういう長時間の発表では時間の管理が非常に難しいので10分おきに時間を連絡して貰いましょう。
84 |
85 | 最後の10分とか20分で時間が分かった所でリカバリはできません。変にリカバリしようとペースを上げたり下げたりすると、テンポがおかしくなるので聞き辛い発表になります。
86 |
87 | ## 声をだす
88 |
89 | 会場に入ると100人以上入る部屋が割り当てられています。つまり、見知らぬ人が大量に見ている場で一時間近く孤軍奮闘する訳です。それなりに場馴れしているとは言え僕だって緊張します。
90 |
91 | そこで僕は、緊張を和らげるために何か理由をつけてマイクを使わずに大きな声で発言するようにしています。今回は前の方の席が余っていたので、それについて何かテキトーな事を喋ることで緊張状態を緩和していました。
92 |
93 | 発表者の皆様におかれましては出来るだけ大きな声で発声しましょう。僕はマイクなしでも100人程度の会場であれば全員に聞こえるレベルの声量でしゃべるようにしています。
94 |
95 | 自信が無かったり、緊張していると声が小さくなりがちですけども、大きな声で喋ると自信が無いことが聴衆に伝わり辛くなります。
96 |
97 | # 発表中
98 |
99 | ## トラブル発生したら、どうする?
100 |
101 | 今回は午前中にA~Dまでが繋がっていた影響で、僕のCD部屋にAB部屋のマイクが入っていました。エンジニア向けのイベントに週末に態々来るような人達は機材トラブルに対しては寛容です。むしろ発表者を応援する気持ちにすらなってくれます。
102 |
103 | こういう時は、少し大げさに腕を振り上げたり、前を歩いたりして大きな声でウケを狙いにいきましょう。皆さん応援する気持ちがありますので、相当変な事を言っても笑って貰えます。今回も、機材トラブルのおかげで始まるなり、凄い一体感があったように思います。
104 |
105 | 変な風に慌てたり、バタつくと見ている側の方がより不安になりますので虚勢を張るくらいで丁度いいです。
106 |
107 | 今回はJJUGのスタッフが迅速に対応してくれたので、数分ロスするだけでトラブルを解消できました。本当にありがたいことです。
108 |
109 | # 終わりに
110 | 発表の未経験者の皆様が発表する際の準備の参考にして頂けると幸いです。
111 |
112 | また、より多くの発表者がより良いコンテンツを提供できるように願っています。
113 |
114 |
115 |
--------------------------------------------------------------------------------
/packages/test/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test",
3 | "version": "0.0.1",
4 | "description": "",
5 | "private": true,
6 | "dependencies": {},
7 | "devDependencies": {
8 | "textlint": "^13.3.0",
9 | "textlint-plugin-html": "^1.0.0",
10 | "textlint-plugin-latex2e": "1.1.4",
11 | "textlint-rule-common-misspellings": "^1.0.1",
12 | "textlint-rule-no-todo": "^2.0.1",
13 | "textlint-rule-preset-ja-technical-writing": "^7.0.0"
14 | },
15 | "scripts": {
16 | "fix": "textlint --fix testtest.txt",
17 | "lint": "textlint testtest.txt"
18 | },
19 | "author": "taichi",
20 | "license": "MIT"
21 | }
22 |
--------------------------------------------------------------------------------
/packages/test/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Title
7 |
8 |
9 |
10 |
11 |
12 | TODO: This is TODO
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/packages/test/test.md:
--------------------------------------------------------------------------------
1 | [JJUG CCC 2015 Spring(4月11日開催)](http://www.java-users.jp/?page_id=1647) で発表をしてきました。
2 |
3 | 一コマ目であり、エントランスから一番近い入り易い場所だったせいもあるとは思いますが立ち見が出る程の盛況ぶりでした。発表を聞いて下さった皆様、本当にありがとうございます。
4 |
5 | 発表資料はこちらです。
6 |
7 | * [Past & Future of null in Java](https://docs.google.com/presentation/d/1Zb-YYnGewELdsLMAjXZU-5bmI7LxTEm8PNnVUqZl0PI/edit)
8 |
9 | 発表者がどういう風に考えてコンテンツを作り、どういう準備をしているのか、というのは余り共有されていないように思います
10 |
11 | このエントリでは僕がどの様に事前準備を行い、当日はどんな風に考えながら発表していたのか記録しておきます。
12 |
13 | # 事前準備
14 |
15 | ## 内容の決め方
16 | まず、50分では前提条件の多くなる話はできませんので、凡そ言語仕様かライブラリの話をするのが妥当でしょうとアタリを付けます。
17 |
18 | 恐らくビギナー向けを標榜しつつも、設計方法論などメタモデルについて話をするのが良いのでは無いかと考えます。
19 |
20 | Javaの標準ライブラリは表層的な使い方の話をするのは簡単なんですけども、何故そういう設計になっているのかという話は、前提条件が多くなるので望ましく無い、つまり言語仕様について話すという方向性で固まります。
21 |
22 | その頃に、[脱ビギナー!Androidのnullな話](http://techlife.cookpad.com/entry/2015/02/20/195000)が良い感じにバズっていたのを思い出します。
23 |
24 | null の話はビギナー向けとして悪くないし、そこでの考え方や方法論について議論するのであれば、エキスパート同士の会話としても成立するだろうと類推します。
25 |
26 | nullの話をするのであれば、Java8で追加されたOptionalについて話すのが良いでしょう。そう思って手元にあるRSSリーダーからOptionalの記事をガシガシと抜き出します。そこで見つけたのが、今回の発表のネタ元となる [Embracing the Void: 6 Refined Tricks for Dealing with Nulls in Java](https://www.voxxed.com/blog/2015/01/embracing-void-6-refined-tricks-dealing-nulls-java/) です。
27 |
28 | このエントリを繰り返し読んで、自分なりの理解を作ります。[Should java 8 getters return optional type?](http://stackoverflow.com/questions/26327957/should-java-8-getters-return-optional-type/26328555#26328555) でOracleのブライアンによる返答は広く知られるべきものです。
29 |
30 | その時点におけるぼんやりとした僕の理解を箇条書きするとこうなります。
31 |
32 | * Optionalは便利であるけども濫用は避けるべきAPI
33 | * そもそもOptionalって何が便利なんだっけ?
34 | * Optionalを使うモチベーションの話とOptionalに至る話は分けて考えよう
35 | * ScalaのOption型との違いはなんだっけか?
36 |
37 | これで、アウトラインが見えてきましたのでCfPを書きます。
38 |
39 | ## 構成について
40 |
41 | スライドを書き始める前にmarkdownで5000文字程の文章を書いて論理的な構造に根源的な破たんが無いかどうかを簡単に確認します。
42 |
43 | この時点でスライドの中に含めるサンプルコードも併せて書いた上で、コードをmarkdownの中に入れておきます。サンプルコードは**28pt**で表示して画面内に収まるような量にします。ディスプレイだけで見ているとフォントサイズが少々小さくてもコードは読めますが、プロジェクタに映すと小さい文字は基本的に読めません。
44 |
45 | この時点で、サンプルコードの難易度も調整します。コードの量が多いと細かい説明をキチンとしないと伝わらないので、一つのコードでは一つの事だけを表現し、できるだけ余分なものを削ります。
46 |
47 | ## スライドのデザインについて
48 |
49 | 僕にはデザイン能力が欠如しているので、ネタ元となるカッコいいデザインのスライドを探します。今回のスライドで参考にしたスライドはこれらです。
50 |
51 | * http://www.slideshare.net/lafarge777/aea-2013recaphd
52 | * http://noteandpoint.com/2011/06/psfk-presents-future-of-mobile-tagging-report/
53 |
54 | 今回僕の能力でこれらのスライドから読み取れたのは、
55 |
56 | * 写真をテキトーにボカしてスライドの背景にするとカッケェ
57 | * 半透明のボックスを文字の背景に置くとカッケェし見易い
58 |
59 | 僕のヴィジュアルデザイン能力の欠如が明確に分かってもらえると思います。
60 |
61 | これで、Googlesスライドのテンプレをせっせと作ります。尚、「表示」メニューにある「マスター」を選択するとテンプレを編集できます。
62 |
63 | テンプレをちゃんと作らないとスライドを書く速度を上げられないのでテンプレは作りましょう。
64 |
65 | ## 発表時に進捗が分かるようにする
66 |
67 | スライド一枚辺りの情報量をなるべく揃えると、単純にページ数だけで進捗が分かるので喋り易くなります。
68 |
69 | 一定のテンポでスライドをめくるれるように資料を作ると少々緊張していても、終了時間を適切にコントロールできます。
70 |
71 | 単に右下辺りに分数を出しても良いのですが、背景や文字の色で自分だけに進捗が分かるようにすると、カッコつけられます。
72 |
73 | # 発表開始前
74 |
75 | ## 飲み物について
76 | 普段はウーロン茶を飲んでいるのですけども、大きな声で発表する場合には喉が渇き易いウーロン茶はあまり望ましくありません。
77 |
78 | なので、頭の片隅にあった「リンゴジュースを飲みながらしゃべるとマイクにペチャクチャした音が入り辛い」という謎情報を試してみました。Rebuild.fmか何かで言ってたような気がします。
79 |
80 | これが非常に上手くいったので、発表中に飲むのはリンゴジュースがオススメです。
81 |
82 | ## 10分おきのタイムキーピング
83 | 今回の発表時間は50分でしたが、こういう長時間の発表では時間の管理が非常に難しいので10分おきに時間を連絡して貰いましょう。
84 |
85 | 最後の10分とか20分で時間が分かった所でリカバリはできません。変にリカバリしようとペースを上げたり下げたりすると、テンポがおかしくなるので聞き辛い発表になります。
86 |
87 | ## 声をだす
88 |
89 | 会場に入ると100人以上入る部屋が割り当てられています。つまり、見知らぬ人が大量に見ている場で一時間近く孤軍奮闘する訳です。それなりに場馴れしているとは言え僕だって緊張します。
90 |
91 | そこで僕は、緊張を和らげるために何か理由をつけてマイクを使わずに大きな声で発言するようにしています。今回は前の方の席が余っていたので、それについて何かテキトーな事を喋ることで緊張状態を緩和していました。
92 |
93 | 発表者の皆様におかれましては出来るだけ大きな声で発声しましょう。僕はマイクなしでも100人程度の会場であれば全員に聞こえるレベルの声量でしゃべるようにしています。
94 |
95 | 自信が無かったり、緊張していると声が小さくなりがちですけども、大きな声で喋ると自信が無いことが聴衆に伝わり辛くなります。
96 |
97 | # 発表中
98 |
99 | ## トラブル発生したら、どうする?
100 |
101 | 今回は午前中にA~Dまでが繋がっていた影響で、僕のCD部屋にAB部屋のマイクが入っていました。エンジニア向けのイベントに週末に態々来るような人達は機材トラブルに対しては寛容です。むしろ発表者を応援する気持ちにすらなってくれます。
102 |
103 | こういう時は、少し大げさに腕を振り上げたり、前を歩いたりして大きな声でウケを狙いにいきましょう。皆さん応援する気持ちがありますので、相当変な事を言っても笑って貰えます。今回も、機材トラブルのおかげで始まるなり、凄い一体感があったように思います。
104 |
105 | 変な風に慌てたり、バタつくと見ている側の方がより不安になりますので虚勢を張るくらいで丁度いいです。
106 |
107 | 今回はJJUGのスタッフが迅速に対応してくれたので、数分ロスするだけでトラブルを解消できました。本当にありがたいことです。
108 |
109 | # 終わりに
110 | 発表の未経験者の皆様が発表する際の準備の参考にして頂けると幸いです。
111 |
112 | また、より多くの発表者がより良いコンテンツを提供できるように願っています。
113 |
114 |
115 |
--------------------------------------------------------------------------------
/packages/test/testtest.tex:
--------------------------------------------------------------------------------
1 | \\documentclass{article}
2 | \\begin{document}
3 | Yuo have missspeling
4 | \\hoge
5 | I has a pens.
6 | \\end{document}
7 |
--------------------------------------------------------------------------------
/packages/test/testtest.txt:
--------------------------------------------------------------------------------
1 | yuo
2 |
3 | yuo
4 |
5 | yuo
6 |
7 | gilr
8 |
--------------------------------------------------------------------------------
/packages/test/testtest.vue:
--------------------------------------------------------------------------------
1 |
2 | TODO: Expect error.
3 |
4 |
--------------------------------------------------------------------------------
/packages/textlint-server/.eslintignore:
--------------------------------------------------------------------------------
1 | out
2 | node_modules
3 |
--------------------------------------------------------------------------------
/packages/textlint-server/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": "../.eslintrc.base.json",
4 | "parserOptions": {
5 | "project": ["./tsconfig.json"]
6 | },
7 | "rules": {
8 | "no-console": "error"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/textlint-server/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible Node.js debug attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Attach",
9 | "type": "node",
10 | "request": "attach",
11 | "port": 6004,
12 | "sourceMaps": true,
13 | "outDir": "${workspaceRoot}/../textlint/server"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/packages/textlint-server/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "lib": false
4 | },
5 | "search.exclude": {
6 | "lib": true
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/textlint-server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vscode-textlint-server",
3 | "version": "0.11.0",
4 | "description": "Textlint Linter Server",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/taichi/vscode-textlint"
8 | },
9 | "license": "MIT",
10 | "main": "lib/server.js",
11 | "files": [
12 | "lib"
13 | ],
14 | "scripts": {
15 | "clean": "rimraf lib node_modules",
16 | "compile": "tsc -p .",
17 | "watch": "tsc -watch -p ./"
18 | },
19 | "dependencies": {
20 | "glob": "^7.2.0",
21 | "vscode-languageserver": "^7.0.0",
22 | "vscode-languageserver-textdocument": "1.0.2",
23 | "vscode-uri": "^3.0.2"
24 | },
25 | "devDependencies": {
26 | "@types/glob": "^7.2.0",
27 | "@types/node": "^16.11.4",
28 | "rimraf": "^3.0.2",
29 | "typescript": "^4.4.4"
30 | },
31 | "engines": {
32 | "node": "*"
33 | },
34 | "publishConfig": {
35 | "access": "public"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/textlint-server/src/autofix.ts:
--------------------------------------------------------------------------------
1 | import { Diagnostic } from "vscode-languageserver";
2 | import { TextDocument } from "vscode-languageserver-textdocument";
3 | import { TextLintFixCommand, TextLintMessage } from "./textlint";
4 | export interface AutoFix {
5 | version: number;
6 | ruleId: string;
7 | fix: TextLintFixCommand;
8 | }
9 | export class TextlintFixRepository {
10 | map: Map = new Map();
11 |
12 | register(doc: TextDocument, diag: Diagnostic, msg: TextLintMessage) {
13 | if (msg.fix && msg.ruleId) {
14 | const fix = {
15 | version: doc.version,
16 | ruleId: msg.ruleId,
17 | fix: msg.fix,
18 | };
19 | this.map.set(this.toKey(diag), fix);
20 | }
21 | }
22 |
23 | find(diags: Diagnostic[]): AutoFix[] {
24 | return diags.map((d) => this.map.get(this.toKey(d))).filter((af) => af);
25 | }
26 |
27 | clear = () => this.map.clear();
28 |
29 | toKey(diagnostic: Diagnostic): string {
30 | const range = diagnostic.range;
31 | return `[${range.start.line},${range.start.character},${range.end.line},${range.end.character}]-${diagnostic.code}`;
32 | }
33 |
34 | isEmpty(): boolean {
35 | return this.map.size < 1;
36 | }
37 |
38 | get version(): number {
39 | const af = this.map.values().next().value;
40 | return af ? af.version : -1;
41 | }
42 |
43 | sortedValues(): AutoFix[] {
44 | const a = Array.from(this.map.values());
45 | return a.sort((left, right) => {
46 | const lr = left.fix.range;
47 | const rr = right.fix.range;
48 | if (lr[0] === rr[0]) {
49 | if (lr[1] === rr[1]) {
50 | return 0;
51 | }
52 | return lr[1] < rr[1] ? -1 : 1;
53 | }
54 | return lr[0] < rr[0] ? -1 : 1;
55 | });
56 | }
57 |
58 | static overlaps(lastEdit: AutoFix, newEdit: AutoFix): boolean {
59 | return !!lastEdit && lastEdit.fix.range[1] > newEdit.fix.range[0];
60 | }
61 |
62 | separatedValues(filter: (fix) => boolean = () => true): AutoFix[] {
63 | const sv = this.sortedValues().filter(filter);
64 | if (sv.length < 1) {
65 | return sv;
66 | }
67 | const result: AutoFix[] = [];
68 | result.push(sv[0]);
69 | sv.reduce((prev, cur) => {
70 | if (TextlintFixRepository.overlaps(prev, cur) === false) {
71 | result.push(cur);
72 | return cur;
73 | }
74 | return prev;
75 | });
76 | return result;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/packages/textlint-server/src/server.ts:
--------------------------------------------------------------------------------
1 | import {
2 | createConnection,
3 | CodeAction,
4 | CodeActionKind,
5 | Command,
6 | Diagnostic,
7 | DiagnosticSeverity,
8 | Position,
9 | Range,
10 | Files,
11 | TextDocuments,
12 | TextEdit,
13 | TextDocumentSyncKind,
14 | ErrorMessageTracker,
15 | ProposedFeatures,
16 | WorkspaceFolder,
17 | } from "vscode-languageserver/node";
18 | import { TextDocument } from "vscode-languageserver-textdocument";
19 |
20 | import { Trace, LogTraceNotification } from "vscode-jsonrpc";
21 | import { URI, Utils as URIUtils } from "vscode-uri";
22 |
23 | import * as os from "os";
24 | import * as fs from "fs";
25 | import * as path from "path";
26 | import * as glob from "glob";
27 | import * as minimatch from "minimatch";
28 |
29 | import {
30 | NoConfigNotification,
31 | NoLibraryNotification,
32 | AllFixesRequest,
33 | StatusNotification,
34 | StartProgressNotification,
35 | StopProgressNotification,
36 | } from "./types";
37 |
38 | import { TextlintFixRepository, AutoFix } from "./autofix";
39 | import type { createLinter, TextLintMessage } from "./textlint";
40 |
41 | const connection = createConnection(ProposedFeatures.all);
42 | const documents = new TextDocuments(TextDocument);
43 | let trace: number;
44 | let settings;
45 | documents.listen(connection);
46 |
47 | type TextlintLinter = {
48 | linter: ReturnType;
49 | availableExtensions: string[];
50 | };
51 |
52 | const linterRepo: Map = new Map();
53 | const fixRepo: Map = new Map();
54 |
55 | connection.onInitialize(async (params) => {
56 | settings = params.initializationOptions;
57 | trace = Trace.fromString(settings.trace);
58 | return {
59 | capabilities: {
60 | textDocumentSync: TextDocumentSyncKind.Full,
61 | codeActionProvider: true,
62 | workspace: {
63 | workspaceFolders: {
64 | supported: true,
65 | changeNotifications: true,
66 | },
67 | },
68 | },
69 | };
70 | });
71 |
72 | connection.onInitialized(async () => {
73 | const folders = await connection.workspace.getWorkspaceFolders();
74 | await configureEngine(folders);
75 | connection.workspace.onDidChangeWorkspaceFolders(async (event) => {
76 | for (const folder of event.removed) {
77 | linterRepo.delete(folder.uri);
78 | }
79 | await reConfigure();
80 | });
81 | });
82 |
83 | async function configureEngine(folders: WorkspaceFolder[]) {
84 | for (const folder of folders) {
85 | TRACE(`configureEngine ${folder.uri}`);
86 | const root = URI.parse(folder.uri).fsPath;
87 | try {
88 | const configFile = lookupConfig(root);
89 | const ignoreFile = lookupIgnore(root);
90 |
91 | const mod = await resolveModule(root);
92 | const hasLinterAPI = "createLinter" in mod && "loadTextlintrc" in mod;
93 | // textlint v13+
94 | if (hasLinterAPI) {
95 | const descriptor = await mod.loadTextlintrc({
96 | configFilePath: configFile,
97 | });
98 | const linter = mod.createLinter({
99 | descriptor,
100 | ignoreFilePath: ignoreFile,
101 | });
102 | linterRepo.set(folder.uri, {
103 | linter,
104 | availableExtensions: descriptor.availableExtensions,
105 | });
106 | } else {
107 | // TODO: These APIs are deprecated. Remove this code in the future.
108 | // textlint v12 or older - deprecated engingles API
109 | const engine = new mod.TextLintEngine({
110 | configFile,
111 | ignoreFile,
112 | });
113 | // polyfill for textlint v12
114 | const linter: ReturnType = {
115 | lintText: (text, filePath) => {
116 | return engine.executeOnText(text, filePath);
117 | },
118 | lintFiles: (files) => {
119 | return engine.executeOnFiles(files);
120 | },
121 | fixFiles: (files) => {
122 | return engine.fixFiles(files);
123 | },
124 | fixText(text, filePath) {
125 | return engine.fixText(text, filePath);
126 | },
127 | };
128 | linterRepo.set(folder.uri, {
129 | linter,
130 | availableExtensions: engine.availableExtensions,
131 | });
132 | }
133 | } catch (e) {
134 | TRACE("failed to configureEngine", e);
135 | }
136 | }
137 | }
138 |
139 | function lookupConfig(root: string): string | undefined {
140 | const roots = [
141 | candidates(root),
142 | () => {
143 | return fs.existsSync(settings.configPath) ? [settings.configPath] : [];
144 | },
145 | candidates(os.homedir()),
146 | ];
147 | for (const fn of roots) {
148 | const files = fn();
149 | if (0 < files.length) {
150 | return files[0];
151 | }
152 | }
153 | connection.sendNotification(NoConfigNotification.type, {
154 | workspaceFolder: root,
155 | });
156 | }
157 |
158 | function lookupIgnore(root: string): string | undefined {
159 | const ignorePath = settings.ignorePath || path.resolve(root, ".textlintignore");
160 | if (fs.existsSync(ignorePath)) {
161 | return ignorePath;
162 | }
163 | }
164 |
165 | async function resolveModule(root: string) {
166 | try {
167 | TRACE(`Module textlint resolve from ${root}`);
168 | const path = await Files.resolveModulePath(root, "textlint", settings.nodePath, TRACE);
169 | TRACE(`Module textlint got resolved to ${path}`);
170 | return loadModule(path);
171 | } catch (e) {
172 | connection.sendNotification(NoLibraryNotification.type, {
173 | workspaceFolder: root,
174 | });
175 | throw e;
176 | }
177 | }
178 |
179 | declare const __webpack_require__: typeof require;
180 | declare const __non_webpack_require__: typeof require;
181 | function loadModule(moduleName: string) {
182 | const r = typeof __webpack_require__ === "function" ? __non_webpack_require__ : require;
183 | try {
184 | return r(moduleName);
185 | } catch (err) {
186 | TRACE("load failed", err);
187 | }
188 | return undefined;
189 | }
190 |
191 | async function reConfigure() {
192 | TRACE(`reConfigure`);
193 | await configureEngine(await connection.workspace.getWorkspaceFolders());
194 | const docs = [];
195 | for (const uri of fixRepo.keys()) {
196 | TRACE(`reConfigure:push ${uri}`);
197 | connection.sendDiagnostics({ uri, diagnostics: [] });
198 | docs.push(documents.get(uri));
199 | }
200 | return validateMany(docs);
201 | }
202 |
203 | connection.onDidChangeConfiguration(async (change) => {
204 | const newone = change.settings.textlint;
205 | TRACE(`onDidChangeConfiguration ${JSON.stringify(newone)}`);
206 | settings = newone;
207 | trace = Trace.fromString(newone.trace);
208 | await reConfigure();
209 | });
210 |
211 | connection.onDidChangeWatchedFiles(async () => {
212 | TRACE("onDidChangeWatchedFiles");
213 | await reConfigure();
214 | });
215 |
216 | documents.onDidChangeContent(async (event) => {
217 | const uri = event.document.uri;
218 | TRACE(`onDidChangeContent ${uri}`, settings.run);
219 | if (settings.run === "onType") {
220 | return validateSingle(event.document);
221 | }
222 | });
223 | documents.onDidSave(async (event) => {
224 | const uri = event.document.uri;
225 | TRACE(`onDidSave ${uri}`, settings.run);
226 | if (settings.run === "onSave") {
227 | return validateSingle(event.document);
228 | }
229 | });
230 |
231 | documents.onDidOpen(async (event) => {
232 | const uri = event.document.uri;
233 | TRACE(`onDidOpen ${uri}`);
234 | if (uri.startsWith("file:") && fixRepo.has(uri) === false) {
235 | fixRepo.set(uri, new TextlintFixRepository());
236 | return validateSingle(event.document);
237 | }
238 | });
239 |
240 | function clearDiagnostics(uri) {
241 | TRACE(`clearDiagnostics ${uri}`);
242 | if (uri.startsWith("file:")) {
243 | fixRepo.delete(uri);
244 | connection.sendDiagnostics({ uri, diagnostics: [] });
245 | }
246 | }
247 | documents.onDidClose((event) => {
248 | const uri = event.document.uri;
249 | TRACE(`onDidClose ${uri}`);
250 | clearDiagnostics(uri);
251 | });
252 |
253 | async function validateSingle(textDocument: TextDocument) {
254 | sendStartProgress();
255 | return validate(textDocument)
256 | .then(sendOK, (error) => {
257 | sendError(error);
258 | })
259 | .then(sendStopProgress);
260 | }
261 |
262 | async function validateMany(textDocuments: TextDocument[]) {
263 | const tracker = new ErrorMessageTracker();
264 | sendStartProgress();
265 | for (const doc of textDocuments) {
266 | try {
267 | await validate(doc);
268 | } catch (err) {
269 | tracker.add(err.message);
270 | }
271 | }
272 | tracker.sendErrors(connection);
273 | sendStopProgress();
274 | }
275 |
276 | function candidates(root: string) {
277 | return () => glob.sync(`${root}/.textlintr{c.js,c.yaml,c.yml,c,c.json}`);
278 | }
279 |
280 | function isTarget(root: string, file: string): boolean {
281 | const relativePath = file.substring(root.length);
282 | return (
283 | settings.targetPath === "" ||
284 | minimatch(relativePath, settings.targetPath, {
285 | matchBase: true,
286 | })
287 | );
288 | }
289 |
290 | function startsWith(target, prefix: string): boolean {
291 | if (target.length < prefix.length) {
292 | return false;
293 | }
294 | const tElements = target.split("/");
295 | const pElements = prefix.split("/");
296 | for (let i = 0; i < pElements.length; i++) {
297 | if (pElements[i] !== tElements[i]) {
298 | return false;
299 | }
300 | }
301 |
302 | return true;
303 | }
304 |
305 | function lookupEngine(doc: TextDocument): [string, TextlintLinter] {
306 | TRACE(`lookupEngine ${doc.uri}`);
307 | for (const ent of linterRepo.entries()) {
308 | if (startsWith(doc.uri, ent[0])) {
309 | TRACE(`lookupEngine ${doc.uri} => ${ent[0]}`);
310 | return ent;
311 | }
312 | }
313 | TRACE(`lookupEngine ${doc.uri} not found`);
314 | return ["", undefined];
315 | }
316 |
317 | async function validate(doc: TextDocument) {
318 | TRACE(`validate ${doc.uri}`);
319 | const uri = URI.parse(doc.uri);
320 | if (doc.uri.startsWith("file:") === false) {
321 | TRACE("validation skipped...");
322 | return;
323 | }
324 |
325 | const repo = fixRepo.get(doc.uri);
326 | if (repo) {
327 | const [folder, engine] = lookupEngine(doc);
328 | const ext = URIUtils.extname(uri);
329 | if (engine && -1 < engine.availableExtensions.findIndex((s) => s === ext) && isTarget(folder, uri.fsPath)) {
330 | repo.clear();
331 | try {
332 | const results = [await engine.linter.lintText(doc.getText(), uri.fsPath)];
333 | TRACE("results", results);
334 | for (const result of results) {
335 | const diagnostics = result.messages.map(toDiagnostic).map(([msg, diag]) => {
336 | repo.register(doc, diag, msg);
337 | return diag;
338 | });
339 | TRACE(`sendDiagnostics ${doc.uri}`);
340 | connection.sendDiagnostics({ uri: doc.uri, diagnostics });
341 | }
342 | } catch (e) {
343 | sendError(e);
344 | }
345 | }
346 | }
347 | }
348 |
349 | function toDiagnosticSeverity(severity?: number): DiagnosticSeverity {
350 | switch (severity) {
351 | case 2:
352 | return DiagnosticSeverity.Error;
353 | case 1:
354 | return DiagnosticSeverity.Warning;
355 | case 0:
356 | return DiagnosticSeverity.Information;
357 | }
358 | return DiagnosticSeverity.Information;
359 | }
360 |
361 | function toDiagnostic(message: TextLintMessage): [TextLintMessage, Diagnostic] {
362 | const txt = message.ruleId ? `${message.message} (${message.ruleId})` : message.message;
363 | const pos_start = Position.create(Math.max(0, message.line - 1), Math.max(0, message.column - 1));
364 | let offset = 0;
365 | if (message.message.indexOf("->") >= 0) {
366 | offset = message.message.indexOf(" ->");
367 | }
368 | const quoteIndex = message.message.indexOf(`"`);
369 | if (quoteIndex >= 0) {
370 | offset = Math.max(0, message.message.indexOf(`"`, quoteIndex + 1) - quoteIndex - 1);
371 | }
372 | const pos_end = Position.create(Math.max(0, message.line - 1), Math.max(0, message.column - 1) + offset);
373 | const diag: Diagnostic = {
374 | message: txt,
375 | severity: toDiagnosticSeverity(message.severity),
376 | source: "textlint",
377 | range: Range.create(pos_start, pos_end),
378 | code: message.ruleId,
379 | };
380 | return [message, diag];
381 | }
382 |
383 | connection.onCodeAction((params) => {
384 | TRACE("onCodeAction", params);
385 | const result: CodeAction[] = [];
386 | const uri = params.textDocument.uri;
387 | const repo = fixRepo.get(uri);
388 | if (repo && repo.isEmpty() === false) {
389 | const doc = documents.get(uri);
390 | const toAction = (title, edits) => {
391 | const cmd = Command.create(title, "textlint.applyTextEdits", uri, repo.version, edits);
392 | return CodeAction.create(title, cmd, CodeActionKind.QuickFix);
393 | };
394 | const toTE = (af) => toTextEdit(doc, af);
395 |
396 | repo.find(params.context.diagnostics).forEach((af) => {
397 | result.push(toAction(`Fix this ${af.ruleId} problem`, [toTE(af)]));
398 | const same = repo.separatedValues((v) => v.ruleId === af.ruleId);
399 | if (0 < same.length) {
400 | result.push(toAction(`Fix all ${af.ruleId} problems`, same.map(toTE)));
401 | }
402 | });
403 | const all = repo.separatedValues();
404 | if (0 < all.length) {
405 | result.push(toAction(`Fix all auto-fixable problems`, all.map(toTE)));
406 | }
407 | }
408 | return result;
409 | });
410 |
411 | function toTextEdit(textDocument: TextDocument, af: AutoFix): TextEdit {
412 | return TextEdit.replace(
413 | Range.create(textDocument.positionAt(af.fix.range[0]), textDocument.positionAt(af.fix.range[1])),
414 | af.fix.text || ""
415 | );
416 | }
417 |
418 | connection.onRequest(AllFixesRequest.type, (params: AllFixesRequest.Params) => {
419 | const uri = params.textDocument.uri;
420 | TRACE(`AllFixesRequest ${uri}`);
421 | const textDocument = documents.get(uri);
422 | const repo = fixRepo.get(uri);
423 | if (repo && repo.isEmpty() === false) {
424 | return {
425 | documentVersion: repo.version,
426 | edits: repo.separatedValues().map((af) => toTextEdit(textDocument, af)),
427 | };
428 | }
429 | });
430 |
431 | let inProgress = 0;
432 | function sendStartProgress() {
433 | TRACE(`sendStartProgress ${inProgress}`);
434 | if (inProgress < 1) {
435 | inProgress = 0;
436 | connection.sendNotification(StartProgressNotification.type);
437 | }
438 | inProgress++;
439 | }
440 |
441 | function sendStopProgress() {
442 | TRACE(`sendStopProgress ${inProgress}`);
443 | if (--inProgress < 1) {
444 | inProgress = 0;
445 | connection.sendNotification(StopProgressNotification.type);
446 | }
447 | }
448 |
449 | function sendOK() {
450 | TRACE("sendOK");
451 | connection.sendNotification(StatusNotification.type, {
452 | status: StatusNotification.Status.OK,
453 | });
454 | }
455 | function sendError(error) {
456 | TRACE(`sendError ${error}`);
457 | const msg = error.message ? error.message : error;
458 | connection.sendNotification(StatusNotification.type, {
459 | status: StatusNotification.Status.ERROR,
460 | message: msg,
461 | cause: error.stack,
462 | });
463 | }
464 |
465 | function toVerbose(data?: unknown): string {
466 | let verbose = "";
467 | if (data) {
468 | verbose = typeof data === "string" ? data : JSON.stringify(data, Object.getOwnPropertyNames(data));
469 | }
470 | return verbose;
471 | }
472 |
473 | export function TRACE(message: string, data?: unknown) {
474 | switch (trace) {
475 | case Trace.Messages:
476 | connection.sendNotification(LogTraceNotification.type, {
477 | message,
478 | });
479 | break;
480 | case Trace.Verbose:
481 | connection.sendNotification(LogTraceNotification.type, {
482 | message,
483 | verbose: toVerbose(data),
484 | });
485 | break;
486 | case Trace.Off:
487 | // do nothing.
488 | break;
489 | default:
490 | break;
491 | }
492 | }
493 |
494 | connection.listen();
495 |
--------------------------------------------------------------------------------
/packages/textlint-server/src/textlint.d.ts:
--------------------------------------------------------------------------------
1 | interface TextLintFixCommand {
2 | text: string;
3 | range: [number, number];
4 | isAbsolute: boolean;
5 | }
6 |
7 | interface TextLintMessage {
8 | // See src/shared/type/MessageType.js
9 | // Message Type
10 | type: string;
11 | // Rule Id
12 | ruleId: string;
13 | message: string;
14 | // optional data
15 | data?: unknown;
16 | // FixCommand
17 | fix?: TextLintFixCommand;
18 | // location info
19 | // Text -> AST TxtNode(0-based columns) -> textlint -> TextLintMessage(**1-based columns**)
20 | line: number; // start with 1
21 | column: number; // start with 1
22 | // indexed-location
23 | index: number; // start with 0
24 | // Severity Level
25 | // See src/shared/type/SeverityLevel.js
26 | severity?: number;
27 | }
28 | export interface TextlintFixResult {
29 | filePath: string;
30 | // fixed content
31 | output: string;
32 | // all messages = pre-applyingMessages + remainingMessages
33 | // it is same with one of `TextlintResult`
34 | messages: TextLintMessage[];
35 | // applied fixable messages
36 | applyingMessages: TextLintMessage[];
37 | // original means original for applyingMessages and remainingMessages
38 | // pre-applyingMessages + remainingMessages
39 | remainingMessages: TextLintMessage[];
40 | }
41 |
42 | interface TextLintResult {
43 | filePath: string;
44 | messages: TextLintMessage[];
45 | }
46 |
47 | interface TextLintEngine {
48 | availableExtensions: string[];
49 |
50 | executeOnText(text: string, ext: string): Thenable;
51 | }
52 | type TextlintKernelDescriptor = unknown;
53 | export type CreateLinterOptions = {
54 | descriptor: TextlintKernelDescriptor;
55 | ignoreFilePath?: string;
56 | quiet?: boolean;
57 | cache?: boolean;
58 | cacheLocation?: string;
59 | };
60 | export type createLinter = (options: CreateLinterOptions) => {
61 | lintFiles(files: string[]): Promise;
62 | lintText(text: string, filePath: string): Promise;
63 | fixFiles(files: string[]): Promise;
64 | fixText(text: string, filePath: string): Promise;
65 | };
66 |
--------------------------------------------------------------------------------
/packages/textlint-server/src/thenable.d.ts:
--------------------------------------------------------------------------------
1 | /* --------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | * ------------------------------------------------------------------------------------------ */
5 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
6 | interface Thenable extends PromiseLike {}
7 |
--------------------------------------------------------------------------------
/packages/textlint-server/src/types.ts:
--------------------------------------------------------------------------------
1 | import { NotificationType0, NotificationType, RequestType } from "vscode-jsonrpc";
2 | import { TextDocumentIdentifier, TextEdit } from "vscode-languageserver-types";
3 |
4 | export namespace ExitNotification {
5 | export interface ExitParams {
6 | code: number;
7 | message: string;
8 | }
9 | export const type = new NotificationType("textlint/exit");
10 | }
11 |
12 | export namespace StatusNotification {
13 | export enum Status {
14 | OK = 1,
15 | WARN = 2,
16 | ERROR = 3,
17 | }
18 | export interface StatusParams {
19 | status: Status;
20 | message?: string;
21 | cause?: unknown;
22 | }
23 | export const type = new NotificationType("textlint/status");
24 | }
25 |
26 | export namespace NoConfigNotification {
27 | export const type = new NotificationType("textlint/noconfig");
28 |
29 | export interface Params {
30 | workspaceFolder: string;
31 | }
32 | }
33 |
34 | export namespace NoLibraryNotification {
35 | export const type = new NotificationType("textlint/nolibrary");
36 | export interface Params {
37 | workspaceFolder: string;
38 | }
39 | }
40 |
41 | export namespace AllFixesRequest {
42 | export interface Params {
43 | textDocument: TextDocumentIdentifier;
44 | }
45 |
46 | export interface Result {
47 | documentVersion: number;
48 | edits: TextEdit[];
49 | }
50 |
51 | export const type = new RequestType("textDocument/textlint/allFixes");
52 | }
53 |
54 | export namespace StartProgressNotification {
55 | export const type = new NotificationType0("textlint/progress/start");
56 | }
57 |
58 | export namespace StopProgressNotification {
59 | export const type = new NotificationType0("textlint/progress/stop");
60 | }
61 |
--------------------------------------------------------------------------------
/packages/textlint-server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "sourceMap": true,
7 | "outDir": "lib",
8 | "lib": ["es6", "DOM"]
9 | },
10 | "exclude": ["node_modules", "lib"]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/textlint/.eslintignore:
--------------------------------------------------------------------------------
1 | out
2 | node_modules
3 |
--------------------------------------------------------------------------------
/packages/textlint/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": "../.eslintrc.base.json",
4 | "parserOptions": {
5 | "project": ["./tsconfig.json"]
6 | },
7 | "rules": {}
8 | }
9 |
--------------------------------------------------------------------------------
/packages/textlint/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | {
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "Run Extension",
7 | "type": "extensionHost",
8 | "request": "launch",
9 | "runtimeExecutable": "${execPath}",
10 | "args": ["${workspaceFolder}/../test", "--extensionDevelopmentPath=${workspaceFolder}", "--disable-extensions"],
11 | "outFiles": ["${workspaceFolder}/dist/*.js"],
12 | "stopOnEntry": false,
13 | "sourceMaps": true,
14 | "preLaunchTask": "npm: watch"
15 | },
16 | {
17 | "name": "Run Extension with workspace",
18 | "type": "extensionHost",
19 | "request": "launch",
20 | "runtimeExecutable": "${execPath}",
21 | "args": [
22 | "${workspaceFolder}/../test-workspace/test.code-workspace",
23 | "--extensionDevelopmentPath=${workspaceFolder}",
24 | "--disable-extensions"
25 | ],
26 | "outFiles": ["${workspaceFolder}/dist/*.js"],
27 | "stopOnEntry": false,
28 | "sourceMaps": true,
29 | "preLaunchTask": "npm: watch"
30 | },
31 | {
32 | "type": "node",
33 | "request": "attach",
34 | "name": "Attach to Server",
35 | "address": "localhost",
36 | "protocol": "inspector",
37 | "port": 6011,
38 | "sourceMaps": true,
39 | "outFiles": ["${workspaceFolder}/dist/*.js"]
40 | },
41 | {
42 | "name": "Test Extension",
43 | "type": "extensionHost",
44 | "request": "launch",
45 | "runtimeExecutable": "${execPath}",
46 | "args": [
47 | "${workspaceFolder}/../test",
48 | "--extensionDevelopmentPath=${workspaceFolder}",
49 | "--extensionTestsPath=${workspaceFolder}/out/test",
50 | "--disable-extensions"
51 | ],
52 | "outFiles": ["${workspaceFolder}/dist/*.js"],
53 | "preLaunchTask": "npm: pretest"
54 | }
55 | ],
56 | "compounds": [
57 | {
58 | "name": "Run Extension + Attach Server",
59 | "configurations": ["Run Extension", "Attach to Server"]
60 | }
61 | ]
62 | }
63 |
--------------------------------------------------------------------------------
/packages/textlint/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "out": false
4 | },
5 | "search.exclude": {
6 | "out": true
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/textlint/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // Available variables which can be used inside of strings.
2 | // ${workspaceRoot}: the root folder of the team
3 | // ${file}: the current opened file
4 | // ${fileBasename}: the current opened file's basename
5 | // ${fileDirname}: the current opened file's dirname
6 | // ${fileExtname}: the current opened file's extension
7 | // ${cwd}: the current working directory of the spawned process
8 | // A task runner that calls a custom npm script that compiles the extension.
9 | {
10 | "version": "2.0.0",
11 | "tasks": [
12 | {
13 | "type": "npm",
14 | "script": "compile",
15 | "group": "build",
16 | "presentation": {
17 | "panel": "dedicated",
18 | "reveal": "never"
19 | },
20 | "problemMatcher": ["$tsc", "$ts-webpack"]
21 | },
22 | {
23 | "type": "npm",
24 | "script": "watch",
25 | "isBackground": true,
26 | "group": {
27 | "kind": "build",
28 | "isDefault": true
29 | },
30 | "presentation": {
31 | "panel": "dedicated",
32 | "reveal": "never"
33 | },
34 | "problemMatcher": ["$tsc-watch", "$ts-webpack-watch"]
35 | }
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/packages/textlint/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | out/test/**
4 | test/**
5 | src/**
6 | **/*.map
7 | .gitignore
8 | tsconfig.json
9 | *.vsix
10 | node_modules/**
11 |
--------------------------------------------------------------------------------
/packages/textlint/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 sato taichi
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.
22 |
--------------------------------------------------------------------------------
/packages/textlint/README.md:
--------------------------------------------------------------------------------
1 | # VS Code textlint extension
2 |
3 | Integrates [textlint](https://textlint.github.io/) into VS Code. If you are new to textlint check the [documentation](https://textlint.github.io/).
4 |
5 | 
6 |
7 | 
8 |
9 | The extension uses the textlint library installed in the opened workspace folder. If the folder doesn't provide one the
10 | extension looks for a global install version. If you haven't installed textlint either locally or globally do so by running
11 | `npm install textlint` in the workspace folder for a local install or `npm install -g textlint` for a global install.
12 |
13 | On new folders you might also need to create a `.textlintrc` configuration file. You can do this by either running
14 | [`textlint --init`](https://github.com/textlint/textlint/blob/master/docs/getting-started.md#configuration) in a terminal or by using the VS Code
15 | command `Create '.textlintrc' file`.
16 |
17 | ## Settings Options
18 |
19 | - `textlint.autoFixOnSave`
20 | - by default is `false`. if you set `true`, Automatically fix auto-fixable errors on save.
21 | - `textlint.run`
22 | - run the linter `onSave` or `onType`, default is `onType`.
23 | - `textlint.nodePath`
24 | - use this setting if an installed textlint package can't be detected, for example `/myGlobalNodePackages/node_modules`.
25 | - `textlint.trace`
26 | - Traces the communication between VSCode and the textlint linter service.
27 | - `textlint.configPath`
28 | - absolute path to textlint config file.
29 | - workspace settings are prioritize.
30 | - `textlint.ignorePath`
31 | - absolute path to textlint ignore file.
32 | - see [here](https://textlint.github.io/docs/ignore.html#ignoring-files-textlintignore) for ignore file.
33 | - `textlint.targetPath`
34 | - set a glob pattern.
35 | - `textlint.languages`
36 | - Languages to lint with textlint.
37 |
38 | ## Commands
39 |
40 | This extension contributes the following commands to the Command palette.
41 |
42 | - Create '.textlintrc' File
43 | - creates a new .textlintrc file.
44 | - Fix all auto-fixable Problems
45 | - applies textlint auto-fix resolutions to all fixable problems.
46 |
47 | ## Release Notes
48 |
49 | ### 0.11.0
50 | - Fix highlight range issue
51 | - thanks for @Yuiki
52 |
53 | ### 0.10.0
54 |
55 | - vscode workspace support
56 | - prepare for web-extension
57 |
58 | ### 0.9.0
59 |
60 | - add .textlintignore support
61 | - thanks for @frozenbonito
62 |
63 | ### 0.8.0
64 |
65 | - add option to choose languages and improve positioning of highlighted
66 | - thanks for @linhtto
67 |
68 | ### 0.7.0
69 |
70 | - add sets a target path support.
71 | - thanks for @bells17
72 |
73 | ### 0.6.8
74 |
75 | - change default value of `textlint.run` to `onSave`
76 | - run tests on Azure Pipelines.
77 |
78 | ### 0.6.5
79 |
80 | - add tex file support including `.tex`, `.latex`, `.doctex`.
81 | - this feature works with [LaTeX Workshop](https://marketplace.visualstudio.com/items?itemName=James-Yu.latex-workshop) and [textlint-plugin-latex2e](https://github.com/ta2gch/textlint-plugin-latex2e).
82 |
83 | ### 0.5.0
84 |
85 | - add `configPath` to configuration. recommend to use your user settings.
86 |
87 | ### 0.4.0
88 |
89 | - read configuration file from `HOME` dir
90 | - if you want to use global configuration, you should install textlint and plugins globally.
91 |
92 | ### 0.3.0
93 |
94 | - update runtime dependencies
95 |
96 | ### 0.2.3
97 |
98 | - add tracing option.
99 |
100 | ### 0.2.2
101 |
102 | - fix some bug.
103 |
104 | ### 0.2.1
105 |
106 | - add progress notification to StatusBar
107 |
108 | ### 0.2.0
109 |
110 | - Supports fixing errors.
111 |
112 | ### 0.1.0
113 |
114 | - Initial Release
115 |
--------------------------------------------------------------------------------
/packages/textlint/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vscode-textlint",
3 | "displayName": "vscode-textlint",
4 | "version": "0.11.0",
5 | "description": "Integrates Textlint into VS Code.",
6 | "categories": [
7 | "Linters"
8 | ],
9 | "homepage": "https://github.com/taichi/vscode-textlint",
10 | "bugs": {
11 | "url": "https://github.com/taichi/vscode-textlint/issues"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "https://github.com/taichi/vscode-textlint"
16 | },
17 | "license": "MIT",
18 | "publisher": "taichi",
19 | "main": "./dist/extension",
20 | "scripts": {
21 | "clean": "rimraf dist out node_modules",
22 | "clean:server": "cd ../textlint-server && npm run clean",
23 | "compile": "tsc -p ./",
24 | "webpack": "webpack --mode production",
25 | "pretest": "npm-run-all --serial clean --parallel compile webpack",
26 | "test": "node ./out/test/runTest.js",
27 | "setup": "npm install --production && cd ../textlint-server && npm install --production",
28 | "vscode:prepublish": "npm-run-all --parallel clean clean:server --serial setup webpack",
29 | "watch": "webpack --mode development --watch"
30 | },
31 | "contributes": {
32 | "commands": [
33 | {
34 | "title": "Fix all auto-fixable Problems",
35 | "category": "textlint",
36 | "command": "textlint.executeAutofix"
37 | },
38 | {
39 | "title": "Create '.textlintrc' File",
40 | "category": "textlint",
41 | "command": "textlint.createConfig"
42 | },
43 | {
44 | "title": "Show Output Channel",
45 | "category": "textlint",
46 | "command": "textlint.showOutputChannel"
47 | }
48 | ],
49 | "configuration": {
50 | "type": "object",
51 | "title": "textlint",
52 | "properties": {
53 | "textlint.languages": {
54 | "default": [
55 | "markdown",
56 | "plaintext",
57 | "html",
58 | "tex",
59 | "latex",
60 | "doctex"
61 | ],
62 | "type": [
63 | "array"
64 | ],
65 | "items": {
66 | "type": "string"
67 | },
68 | "description": "Languages to lint with textlint."
69 | },
70 | "textlint.configPath": {
71 | "type": "string",
72 | "default": null,
73 | "description": "A absolute path to textlint config file."
74 | },
75 | "textlint.ignorePath": {
76 | "type": "string",
77 | "default": null,
78 | "description": "A absolute path to textlint ignore file."
79 | },
80 | "textlint.nodePath": {
81 | "type": "string",
82 | "default": null,
83 | "description": "A path added to NODE_PATH when resolving the textlint module."
84 | },
85 | "textlint.run": {
86 | "type": "string",
87 | "enum": [
88 | "onSave",
89 | "onType"
90 | ],
91 | "default": "onSave",
92 | "description": "Run the linter on save (onSave) or on type (onType)"
93 | },
94 | "textlint.autoFixOnSave": {
95 | "type": "boolean",
96 | "default": false,
97 | "description": "Turns auto fix on save on or off."
98 | },
99 | "textlint.trace": {
100 | "type": "string",
101 | "enum": [
102 | "off",
103 | "messages",
104 | "verbose"
105 | ],
106 | "default": "off",
107 | "description": "Traces the communication between VSCode and the textlint linter service."
108 | },
109 | "textlint.targetPath": {
110 | "type": "string",
111 | "default": "",
112 | "description": "Target files path that runs lint."
113 | }
114 | }
115 | }
116 | },
117 | "activationEvents": [
118 | "onLanguage:html",
119 | "onLanguage:plaintext",
120 | "onLanguage:markdown",
121 | "onLanguage:latex",
122 | "onLanguage:tex",
123 | "onLanguage:pdf",
124 | "onLanguage:django-txt",
125 | "onLanguage:django-html",
126 | "onLanguage:doctex",
127 | "onLanguage:restructuredtext",
128 | "onCommand:textlint.showOutputChannel",
129 | "onCommand:textlint.createConfig",
130 | "onCommand:textlint.executeAutofix"
131 | ],
132 | "dependencies": {
133 | "minimatch": "^3.0.4",
134 | "vscode-languageclient": "^7.0.0",
135 | "vscode-uri": "^3.0.2"
136 | },
137 | "devDependencies": {
138 | "@types/fs-extra": "9.0.13",
139 | "@types/mocha": "^9.0.0",
140 | "@types/node": "^16.11.4",
141 | "@types/vscode": "^1.61.0",
142 | "@vscode/test-electron": "^1.6.2",
143 | "fs-extra": "^10.0.0",
144 | "merge-options": "^3.0.4",
145 | "mocha": "^9.1.3",
146 | "npm-run-all": "^4.1.5",
147 | "rimraf": "^3.0.2",
148 | "ts-loader": "^9.2.6",
149 | "typescript": "^4.4.4",
150 | "webpack": "^5.61.0",
151 | "webpack-cli": "^4.9.1"
152 | },
153 | "engines": {
154 | "vscode": "^1.61.0"
155 | },
156 | "icon": "textlint-icon_128x128.png",
157 | "galleryBanner": {
158 | "color": "#5acbe3",
159 | "theme": "light"
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/packages/textlint/src/extension.ts:
--------------------------------------------------------------------------------
1 | import * as minimatch from "minimatch";
2 |
3 | import {
4 | workspace,
5 | window,
6 | commands,
7 | ExtensionContext,
8 | Disposable,
9 | QuickPickItem,
10 | WorkspaceFolder,
11 | TextDocumentSaveReason,
12 | TextEditor,
13 | } from "vscode";
14 |
15 | import {
16 | TextEdit,
17 | State as ServerState,
18 | ErrorHandler,
19 | ErrorAction,
20 | CloseAction,
21 | RevealOutputChannelOn,
22 | } from "vscode-languageclient";
23 |
24 | import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from "vscode-languageclient/node";
25 |
26 | import { LogTraceNotification } from "vscode-jsonrpc";
27 |
28 | import { Utils as URIUtils } from "vscode-uri";
29 |
30 | import {
31 | StatusNotification,
32 | NoConfigNotification,
33 | NoLibraryNotification,
34 | ExitNotification,
35 | AllFixesRequest,
36 | StartProgressNotification,
37 | StopProgressNotification,
38 | } from "./types";
39 |
40 | import { Status, StatusBar } from "./status";
41 |
42 | export interface ExtensionInternal {
43 | client: LanguageClient;
44 | statusBar: StatusBar;
45 | onAllFixesComplete(fn: (te: TextEditor, edits: TextEdit[], ok: boolean) => void);
46 | }
47 |
48 | export function activate(context: ExtensionContext): ExtensionInternal {
49 | const client = newClient(context);
50 | const statusBar = new StatusBar(getConfig("languages"));
51 | client.onReady().then(() => {
52 | client.onDidChangeState((event) => {
53 | statusBar.serverRunning = event.newState === ServerState.Running;
54 | });
55 | client.onNotification(StatusNotification.type, (p: StatusNotification.StatusParams) => {
56 | statusBar.status = to(p.status);
57 | if (p.message || p.cause) {
58 | statusBar.status.log(client, p.message, p.cause);
59 | }
60 | });
61 | client.onNotification(NoConfigNotification.type, (p) => {
62 | statusBar.status = Status.WARN;
63 | statusBar.status.log(
64 | client,
65 | `No textlint configuration (e.g .textlintrc) found in ${p.workspaceFolder} .
66 | File will not be validated. Consider running the 'Create .textlintrc file' command.`
67 | );
68 | });
69 | client.onNotification(NoLibraryNotification.type, (p) => {
70 | statusBar.status = Status.ERROR;
71 | statusBar.status.log(
72 | client,
73 | `Failed to load the textlint library in ${p.workspaceFolder} .
74 | To use textlint in this workspace please install textlint using 'npm install textlint' or globally using 'npm install -g textlint'.
75 | You need to reopen the workspace after installing textlint.`
76 | );
77 | });
78 | client.onNotification(StartProgressNotification.type, () => statusBar.startProgress());
79 | client.onNotification(StopProgressNotification.type, () => statusBar.stopProgress());
80 |
81 | client.onNotification(LogTraceNotification.type, (p) => client.info(p.message, p.verbose));
82 | const changeConfigHandler = () => configureAutoFixOnSave(client);
83 | workspace.onDidChangeConfiguration(changeConfigHandler);
84 | changeConfigHandler();
85 | });
86 | context.subscriptions.push(
87 | commands.registerCommand("textlint.createConfig", createConfig),
88 | commands.registerCommand("textlint.applyTextEdits", makeApplyFixFn(client)),
89 | commands.registerCommand("textlint.executeAutofix", makeAutoFixFn(client)),
90 | commands.registerCommand("textlint.showOutputChannel", () => client.outputChannel.show()),
91 | client.start(),
92 | statusBar
93 | );
94 | // for testing purpose
95 | return {
96 | client,
97 | statusBar,
98 | onAllFixesComplete,
99 | };
100 | }
101 |
102 | function newClient(context: ExtensionContext): LanguageClient {
103 | const module = URIUtils.joinPath(context.extensionUri, "dist", "server.js").fsPath;
104 | const debugOptions = { execArgv: ["--nolazy", "--inspect=6011"] };
105 |
106 | const serverOptions: ServerOptions = {
107 | run: { module, transport: TransportKind.ipc },
108 | debug: { module, transport: TransportKind.ipc, options: debugOptions },
109 | };
110 |
111 | // eslint-disable-next-line prefer-const
112 | let defaultErrorHandler: ErrorHandler;
113 | let serverCalledProcessExit = false;
114 | const clientOptions: LanguageClientOptions = {
115 | documentSelector: getConfig("languages").map((id) => {
116 | return { language: id, scheme: "file" };
117 | }),
118 | diagnosticCollectionName: "textlint",
119 | revealOutputChannelOn: RevealOutputChannelOn.Error,
120 | synchronize: {
121 | configurationSection: "textlint",
122 | fileEvents: [
123 | workspace.createFileSystemWatcher("**/package.json"),
124 | workspace.createFileSystemWatcher("**/.textlintrc"),
125 | workspace.createFileSystemWatcher("**/.textlintrc.{js,json,yml,yaml}"),
126 | workspace.createFileSystemWatcher("**/.textlintignore"),
127 | ],
128 | },
129 | initializationOptions: () => {
130 | return {
131 | configPath: getConfig("configPath"),
132 | ignorePath: getConfig("ignorePath"),
133 | nodePath: getConfig("nodePath"),
134 | run: getConfig("run"),
135 | trace: getConfig("trace", "off"),
136 | };
137 | },
138 | initializationFailedHandler: (error) => {
139 | client.error("Server initialization failed.", error);
140 | return false;
141 | },
142 | errorHandler: {
143 | error: (error, message, count): ErrorAction => {
144 | return defaultErrorHandler.error(error, message, count);
145 | },
146 | closed: (): CloseAction => {
147 | if (serverCalledProcessExit) {
148 | return CloseAction.DoNotRestart;
149 | }
150 | return defaultErrorHandler.closed();
151 | },
152 | },
153 | };
154 |
155 | const client = new LanguageClient("textlint", serverOptions, clientOptions);
156 | defaultErrorHandler = client.createDefaultErrorHandler();
157 | client.onReady().then(() => {
158 | client.onNotification(ExitNotification.type, () => {
159 | serverCalledProcessExit = true;
160 | });
161 | });
162 | return client;
163 | }
164 |
165 | async function createConfig() {
166 | const folders = workspace.workspaceFolders;
167 | if (!folders) {
168 | await window.showErrorMessage(
169 | "An textlint configuration can only be generated if VS Code is opened on a workspace folder."
170 | );
171 | return;
172 | }
173 |
174 | const noConfigs = await filterNoConfigFolders(folders);
175 |
176 | if (noConfigs.length < 1 && 0 < folders.length) {
177 | await window.showErrorMessage("textlint configuration file already exists in this workspace.");
178 | return;
179 | }
180 |
181 | if (noConfigs.length === 1) {
182 | await emitConfig(noConfigs[0]);
183 | } else {
184 | const item = await window.showQuickPick(toQuickPickItems(noConfigs));
185 | if (item) {
186 | await emitConfig(item.folder);
187 | }
188 | }
189 | }
190 |
191 | async function filterNoConfigFolders(folders: readonly WorkspaceFolder[]): Promise {
192 | const result = [];
193 | outer: for (const folder of folders) {
194 | const candidates = ["", ".js", ".yaml", ".yml", ".json"].map((ext) =>
195 | URIUtils.joinPath(folder.uri, ".textlintrc" + ext)
196 | );
197 | for (const configPath of candidates) {
198 | try {
199 | await workspace.fs.stat(configPath);
200 | continue outer;
201 | // eslint-disable-next-line no-empty
202 | } catch {}
203 | }
204 | result.push(folder);
205 | }
206 | return result;
207 | }
208 |
209 | async function emitConfig(folder: WorkspaceFolder) {
210 | if (folder) {
211 | await workspace.fs.writeFile(
212 | URIUtils.joinPath(folder.uri, ".textlintrc"),
213 | Buffer.from(
214 | `{
215 | "filters": {},
216 | "rules": {}
217 | }`,
218 | "utf8"
219 | )
220 | );
221 | }
222 | }
223 |
224 | function toQuickPickItems(folders: WorkspaceFolder[]): ({ folder: WorkspaceFolder } & QuickPickItem)[] {
225 | return folders.map((folder) => {
226 | return {
227 | label: folder.name,
228 | description: folder.uri.path,
229 | folder,
230 | };
231 | });
232 | }
233 |
234 | let autoFixOnSave: Disposable;
235 |
236 | function configureAutoFixOnSave(client: LanguageClient) {
237 | const auto = getConfig("autoFixOnSave", false);
238 | disposeAutoFixOnSave();
239 |
240 | if (auto) {
241 | const languages = new Set(getConfig("languages"));
242 | autoFixOnSave = workspace.onWillSaveTextDocument((event) => {
243 | const doc = event.document;
244 | const target = getConfig("targetPath", null);
245 | if (
246 | languages.has(doc.languageId) &&
247 | event.reason !== TextDocumentSaveReason.AfterDelay &&
248 | (target === "" ||
249 | minimatch(workspace.asRelativePath(doc.uri), target, {
250 | matchBase: true,
251 | }))
252 | ) {
253 | const version = doc.version;
254 | const uri: string = doc.uri.toString();
255 | event.waitUntil(
256 | client.sendRequest(AllFixesRequest.type, { textDocument: { uri } }).then((result: AllFixesRequest.Result) => {
257 | return result && result.documentVersion === version
258 | ? client.protocol2CodeConverter.asTextEdits(result.edits)
259 | : [];
260 | })
261 | );
262 | }
263 | });
264 | }
265 | }
266 |
267 | function disposeAutoFixOnSave() {
268 | if (autoFixOnSave) {
269 | autoFixOnSave.dispose();
270 | autoFixOnSave = undefined;
271 | }
272 | }
273 |
274 | function makeAutoFixFn(client: LanguageClient) {
275 | return () => {
276 | const textEditor = window.activeTextEditor;
277 | if (textEditor) {
278 | const uri: string = textEditor.document.uri.toString();
279 | client.sendRequest(AllFixesRequest.type, { textDocument: { uri } }).then(
280 | async (result: AllFixesRequest.Result) => {
281 | if (result) {
282 | await applyTextEdits(client, uri, result.documentVersion, result.edits);
283 | }
284 | },
285 | (error) => {
286 | client.error("Failed to apply textlint fixes to the document.", error);
287 | }
288 | );
289 | }
290 | };
291 | }
292 |
293 | function makeApplyFixFn(client: LanguageClient) {
294 | return async (uri: string, documentVersion: number, edits: TextEdit[]) => {
295 | await applyTextEdits(client, uri, documentVersion, edits);
296 | };
297 | }
298 |
299 | const allFixesCompletes = [];
300 | function onAllFixesComplete(fn: (te: TextEditor, edits: TextEdit[], ok: boolean) => void) {
301 | allFixesCompletes.push(fn);
302 | }
303 |
304 | async function applyTextEdits(
305 | client: LanguageClient,
306 | uri: string,
307 | documentVersion: number,
308 | edits: TextEdit[]
309 | ): Promise {
310 | const textEditor = window.activeTextEditor;
311 | if (textEditor && textEditor.document.uri.toString() === uri) {
312 | if (textEditor.document.version === documentVersion) {
313 | return textEditor
314 | .edit((mutator) => {
315 | edits.forEach((ed) => mutator.replace(client.protocol2CodeConverter.asRange(ed.range), ed.newText));
316 | })
317 | .then(
318 | (ok) => {
319 | client.info("AllFixesComplete");
320 | allFixesCompletes.forEach((fn) => fn(textEditor, edits, ok));
321 | return true;
322 | },
323 | (errors) => {
324 | client.error(errors.message, errors.stack);
325 | }
326 | );
327 | } else {
328 | window.showInformationMessage(`textlint fixes are outdated and can't be applied to ${uri}`);
329 | return true;
330 | }
331 | }
332 | }
333 |
334 | export function deactivate() {
335 | disposeAutoFixOnSave();
336 | }
337 |
338 | function config() {
339 | return workspace.getConfiguration("textlint");
340 | }
341 |
342 | function getConfig(section: string, defaults?: T) {
343 | return config().get(section, defaults);
344 | }
345 |
346 | function to(status: StatusNotification.Status): Status {
347 | switch (status) {
348 | case StatusNotification.Status.OK:
349 | return Status.OK;
350 | case StatusNotification.Status.WARN:
351 | return Status.WARN;
352 | case StatusNotification.Status.ERROR:
353 | return Status.ERROR;
354 | default:
355 | return Status.ERROR;
356 | }
357 | }
358 |
--------------------------------------------------------------------------------
/packages/textlint/src/status.ts:
--------------------------------------------------------------------------------
1 | import { window, StatusBarAlignment, TextEditor } from "vscode";
2 |
3 | export interface Status {
4 | label: string;
5 | color: string;
6 | log: (
7 | logger: {
8 | info(message: string, data?: unknown): void;
9 | warn(message: string, data?: unknown): void;
10 | error(message: string, data?: unknown): void;
11 | },
12 | msg: string,
13 | data?: unknown
14 | ) => void;
15 | }
16 |
17 | export namespace Status {
18 | export const OK: Status = {
19 | label: "textlint",
20 | color: "",
21 | log: (logger, msg, data?) => logger.info(msg, data),
22 | };
23 | export const WARN: Status = {
24 | label: "textlint: Warning",
25 | color: "yellow",
26 | log: (logger, msg, data?) => logger.warn(msg, data),
27 | };
28 | export const ERROR: Status = {
29 | label: "textlint: Error",
30 | color: "darkred",
31 | log: (logger, msg, data?) => logger.error(msg, data),
32 | };
33 | }
34 |
35 | export class StatusBar {
36 | private _delegate = window.createStatusBarItem(StatusBarAlignment.Right, 0);
37 | private _supports: string[];
38 | private _status = Status.OK;
39 | private _serverRunning = false;
40 | private _intervalToken;
41 | constructor(supports) {
42 | this._supports = supports;
43 | this._delegate.text = this._status.label;
44 | window.onDidChangeActiveTextEditor((te) => this.updateWith(te));
45 | this.update();
46 | }
47 |
48 | dispose() {
49 | this.stopProgress();
50 | this._delegate.dispose();
51 | }
52 |
53 | show(show: boolean) {
54 | if (show) {
55 | this._delegate.show();
56 | } else {
57 | this._delegate.hide();
58 | }
59 | }
60 |
61 | get status(): Status {
62 | return this._status;
63 | }
64 |
65 | set status(s: Status) {
66 | this._status = s;
67 | this.update();
68 | }
69 |
70 | get serverRunning(): boolean {
71 | return this._serverRunning;
72 | }
73 |
74 | set serverRunning(sr: boolean) {
75 | this._serverRunning = sr;
76 | this._delegate.tooltip = sr ? "textlint server is running." : "textlint server stopped.";
77 | this.update();
78 | }
79 |
80 | update() {
81 | this.updateWith(window.activeTextEditor);
82 | }
83 |
84 | updateWith(editor: TextEditor) {
85 | this._delegate.text = this.status.label;
86 | this.show(
87 | this.serverRunning === false ||
88 | this._status !== Status.OK ||
89 | (editor && 0 < this._supports.indexOf(editor.document.languageId))
90 | );
91 | }
92 |
93 | startProgress() {
94 | if (!this._intervalToken) {
95 | let c = 0;
96 | const orig = this._delegate.text;
97 | const chars = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏";
98 | const l = chars.length;
99 | this._intervalToken = setInterval(() => {
100 | const t = c++ % l;
101 | this._delegate.text = chars[t] + " " + orig;
102 | }, 300);
103 | }
104 | }
105 |
106 | stopProgress() {
107 | if (this._intervalToken) {
108 | const tk = this._intervalToken;
109 | this._intervalToken = null;
110 | clearInterval(tk);
111 | this.update();
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/packages/textlint/src/types.ts:
--------------------------------------------------------------------------------
1 | import { NotificationType0, NotificationType, RequestType } from "vscode-jsonrpc";
2 | import { TextDocumentIdentifier, TextEdit } from "vscode-languageserver-types";
3 |
4 | export namespace ExitNotification {
5 | export interface ExitParams {
6 | code: number;
7 | message: string;
8 | }
9 | export const type = new NotificationType("textlint/exit");
10 | }
11 |
12 | export namespace StatusNotification {
13 | export enum Status {
14 | OK = 1,
15 | WARN = 2,
16 | ERROR = 3,
17 | }
18 | export interface StatusParams {
19 | status: Status;
20 | message?: string;
21 | cause?: unknown;
22 | }
23 | export const type = new NotificationType("textlint/status");
24 | }
25 |
26 | export namespace NoConfigNotification {
27 | export const type = new NotificationType("textlint/noconfig");
28 |
29 | export interface Params {
30 | workspaceFolder: string;
31 | }
32 | }
33 |
34 | export namespace NoLibraryNotification {
35 | export const type = new NotificationType("textlint/nolibrary");
36 | export interface Params {
37 | workspaceFolder: string;
38 | }
39 | }
40 |
41 | export namespace AllFixesRequest {
42 | export interface Params {
43 | textDocument: TextDocumentIdentifier;
44 | }
45 |
46 | export interface Result {
47 | documentVersion: number;
48 | edits: TextEdit[];
49 | }
50 |
51 | export const type = new RequestType("textDocument/textlint/allFixes");
52 | }
53 |
54 | export namespace StartProgressNotification {
55 | export const type = new NotificationType0("textlint/progress/start");
56 | }
57 |
58 | export namespace StopProgressNotification {
59 | export const type = new NotificationType0("textlint/progress/stop");
60 | }
61 |
--------------------------------------------------------------------------------
/packages/textlint/test/extension.test.ts:
--------------------------------------------------------------------------------
1 | import * as assert from "assert";
2 | import * as fs from "fs-extra";
3 | import * as path from "path";
4 |
5 | import { workspace, window, commands, Extension, extensions, Disposable } from "vscode";
6 | import { ExtensionInternal } from "../src/extension";
7 |
8 | import { PublishDiagnosticsNotification } from "./types";
9 |
10 | suite("Extension Tests", () => {
11 | let extension: Extension;
12 | let internals: ExtensionInternal;
13 | setup((done) => {
14 | commands.executeCommand("textlint.showOutputChannel");
15 |
16 | function waitForActive(resolve): void {
17 | const ext = extensions.getExtension("taichi.vscode-textlint");
18 | if (typeof ext === "undefined" || ext.isActive === false) {
19 | setTimeout(waitForActive.bind(null, resolve), 50);
20 | } else {
21 | extension = ext;
22 | internals = ext.exports;
23 | resolve();
24 | }
25 | }
26 | waitForActive(done);
27 | });
28 |
29 | suite("basic behavior", () => {
30 | test("activate extension", () => {
31 | assert(extension.isActive);
32 | assert(internals.client);
33 | assert(internals.statusBar);
34 | });
35 | });
36 |
37 | suite("with server", function () {
38 | const rootPath = workspace.workspaceFolders[0].uri.fsPath;
39 | const original = path.join(rootPath, "testtest.txt");
40 | const newfile = path.join(rootPath, "testtest2.txt");
41 | //const timelag = (ms = 100) => new Promise((resolve) => setTimeout(resolve, ms));
42 | const disposables: Disposable[] = [];
43 | setup(async () => {
44 | await fs.copy(original, newfile);
45 | await internals.client.onReady();
46 | });
47 | teardown(async () => {
48 | const p = new Promise((resolve) => {
49 | fs.unlink(newfile, () => {
50 | resolve(0);
51 | });
52 | });
53 | await commands.executeCommand("workbench.action.closeAllEditors");
54 | await p;
55 | disposables.forEach((d) => d.dispose());
56 | });
57 | test("lint file", async () => {
58 | const waiter = new Promise((resolve) => {
59 | internals.client.onNotification(PublishDiagnosticsNotification.type, (p) => {
60 | const d = p.diagnostics;
61 | if (d.length === 0) {
62 | return; // skip empty diagnostics
63 | }
64 | if (0 < d.length) {
65 | resolve(0);
66 | } else {
67 | console.log(`assertion failed length:${d.length}`, d);
68 | }
69 | });
70 | });
71 | const doc = await workspace.openTextDocument(newfile);
72 | await window.showTextDocument(doc);
73 | await waiter;
74 | });
75 | test("fix file", async () => {
76 | const p = new Promise((resolve, reject) => {
77 | internals.onAllFixesComplete((ed, edits, ok) => {
78 | if (ok && 0 < edits.length) {
79 | resolve("ok");
80 | } else {
81 | let s = `length:${edits.length} `;
82 | s += edits.map((ed) => `newText:${ed.newText}`).join(" ");
83 | reject(`assertion failed ${ok} ${ed.document.getText()} edits=${s}`);
84 | }
85 | });
86 | });
87 | const doc = await workspace.openTextDocument(newfile);
88 | await window.showTextDocument(doc);
89 | await commands.executeCommand("textlint.executeAutofix");
90 | await p;
91 | });
92 | });
93 | });
94 |
95 | // https://github.com/Microsoft/vscode-mssql/blob/dev/test/initialization.test.ts
96 | // https://github.com/HookyQR/VSCodeBeautify/blob/master/test/extension.test.js
97 | // https://github.com/Microsoft/vscode-docs/blob/master/docs/extensionAPI/vscode-api-commands.md
98 | // https://github.com/Microsoft/vscode-docs/blob/master/docs/extensionAPI/vscode-api.md
99 |
--------------------------------------------------------------------------------
/packages/textlint/test/index.ts:
--------------------------------------------------------------------------------
1 | import * as path from "path";
2 | import * as Mocha from "mocha";
3 | import * as glob from "glob";
4 |
5 | export function run(): Promise {
6 | // Create the mocha test
7 | const mocha = new Mocha({
8 | ui: "tdd",
9 | color: true,
10 | timeout: 30000,
11 | });
12 |
13 | const testsRoot = path.resolve(__dirname, ".");
14 |
15 | return new Promise((c, e) => {
16 | glob("**/**.test.js", { cwd: testsRoot }, (err, files) => {
17 | if (err) {
18 | return e(err);
19 | }
20 |
21 | // Add files to the test suite
22 | files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f)));
23 |
24 | try {
25 | // Run the mocha test
26 | mocha.run((failures) => {
27 | if (failures > 0) {
28 | e(new Error(`${failures} tests failed.`));
29 | } else {
30 | c();
31 | }
32 | });
33 | } catch (err) {
34 | console.error(err);
35 | e(err);
36 | }
37 | });
38 | });
39 | }
40 |
--------------------------------------------------------------------------------
/packages/textlint/test/runTest.ts:
--------------------------------------------------------------------------------
1 | import * as path from "path";
2 |
3 | import { runTests } from "@vscode/test-electron";
4 |
5 | async function main() {
6 | try {
7 | // The folder containing the Extension Manifest package.json
8 | // Passed to `--extensionDevelopmentPath`
9 | const extensionDevelopmentPath = path.resolve(__dirname, "../../");
10 |
11 | // The path to the extension test script
12 | // Passed to --extensionTestsPath
13 | const extensionTestsPath = path.resolve(__dirname, "./index");
14 |
15 | const workspaceFolder = path.resolve(__dirname, "../../../test");
16 |
17 | // Download VS Code, unzip it and run the integration test
18 | await runTests({
19 | extensionDevelopmentPath,
20 | extensionTestsPath,
21 | launchArgs: [workspaceFolder, "--disable-extensions"],
22 | });
23 | } catch (err) {
24 | console.error("Failed to run tests");
25 | process.exit(1);
26 | }
27 | }
28 |
29 | main();
30 |
--------------------------------------------------------------------------------
/packages/textlint/test/types.ts:
--------------------------------------------------------------------------------
1 | import { NotificationType } from "vscode-jsonrpc";
2 | import { Diagnostic } from "vscode-languageserver-types";
3 |
4 | export namespace PublishDiagnosticsNotification {
5 | export const type = new NotificationType("textDocument/publishDiagnostics");
6 | }
7 |
8 | export interface PublishDiagnosticsParams {
9 | uri: string;
10 | diagnostics: Diagnostic[];
11 | }
12 |
--------------------------------------------------------------------------------
/packages/textlint/textlint-icon_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taichi/vscode-textlint/780d72c5f9e76310188a2375de4207368594f8af/packages/textlint/textlint-icon_128x128.png
--------------------------------------------------------------------------------
/packages/textlint/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es6",
5 | "outDir": "out",
6 | "lib": ["es6", "DOM"],
7 | "sourceMap": true
8 | },
9 | "exclude": ["node_modules", ".vscode-test"]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/textlint/webpack.config.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const webpack = require("webpack");
4 | const path = require("path");
5 | const extensionPackage = require("./package.json");
6 | const merge = require("merge-options");
7 |
8 | /**@type {import('webpack').Configuration}*/
9 | const config = {
10 | target: "node",
11 | output: {
12 | path: path.resolve(__dirname, "dist"),
13 | libraryTarget: "commonjs",
14 | },
15 | stats: {
16 | errorDetails: true,
17 | },
18 | devtool: "source-map",
19 | externals: [
20 | {
21 | vscode: "commonjs vscode",
22 | textlint: "commonjs textlint",
23 | },
24 | ],
25 | resolve: {
26 | extensions: [".ts", ".js"],
27 | },
28 | module: {
29 | rules: [
30 | {
31 | test: /\.ts$/,
32 | exclude: /node_modules/,
33 | use: [
34 | {
35 | loader: "ts-loader",
36 | },
37 | ],
38 | },
39 | ],
40 | },
41 | };
42 |
43 | /**@type {import('webpack').Configuration}*/
44 | const client = merge(config, {
45 | entry: "./src/extension.ts",
46 | output: {
47 | filename: "extension.js",
48 | },
49 | plugins: [
50 | new webpack.EnvironmentPlugin({
51 | EXTENSION_NAME: `${extensionPackage.publisher}.${extensionPackage.name}`,
52 | EXTENSION_VERSION: extensionPackage.version,
53 | }),
54 | ],
55 | });
56 |
57 | /**@type {import('webpack').Configuration}*/
58 | const server = merge(config, {
59 | entry: "../textlint-server/src/server.ts",
60 | output: {
61 | filename: "server.js",
62 | },
63 | });
64 |
65 | module.exports = [client, server];
66 |
--------------------------------------------------------------------------------
/vscode-textlint.code-workspace:
--------------------------------------------------------------------------------
1 | {
2 | "folders": [
3 | {
4 | "path": "."
5 | },
6 | {
7 | "path": "packages/textlint"
8 | },
9 | {
10 | "path": "packages/textlint-server"
11 | },
12 | {
13 | "path": "packages/test"
14 | },
15 | {
16 | "path": "packages/test-workspace"
17 | }
18 | ],
19 | "settings": {
20 | "editor.formatOnSave": false,
21 | "editor.codeActionsOnSave": {
22 | "source.fixAll.eslint": true
23 | },
24 | "[json]": {
25 | "editor.defaultFormatter": "esbenp.prettier-vscode",
26 | "editor.formatOnSave": true
27 | },
28 | "[javascript]": {
29 | "editor.defaultFormatter": "esbenp.prettier-vscode",
30 | "editor.formatOnSave": true
31 | },
32 | "[typescript]": {
33 | "editor.defaultFormatter": "esbenp.prettier-vscode",
34 | "editor.formatOnSave": true
35 | },
36 | "[typescriptreact]": {
37 | "editor.defaultFormatter": "esbenp.prettier-vscode",
38 | "editor.formatOnSave": true
39 | },
40 | "typescript.tsdk": "./node_modules/typescript/lib"
41 | },
42 | "extensions": {
43 | "recommendations": [
44 | "amodio.tsl-problem-matcher",
45 | "dbaeumer.vscode-eslint",
46 | "editorconfig.editorconfig",
47 | "esbenp.prettier-vscode"
48 | ]
49 | },
50 | }
51 |
--------------------------------------------------------------------------------