├── .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 ![push](https://github.com/taichi/vscode-textlint/actions/workflows/push.yml/badge.svg) 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 | 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 | ![hover](https://github.com/taichi/vscode-textlint/raw/master/imgs/hover.png?raw=true) 6 | 7 | ![codeaction](https://github.com/taichi/vscode-textlint/raw/master/imgs/codeaction.png?raw=true) 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 | --------------------------------------------------------------------------------