├── .gitignore ├── README.md ├── resources ├── META-INF │ ├── plugin.xml │ └── pluginIcon.svg ├── icons │ ├── weapp.png │ ├── wxml.png │ └── wxss.png └── lib │ └── weixin-app │ ├── index.d.ts │ ├── tsconfig.json │ ├── tslint.json │ └── weixin-app-tests.ts ├── src └── com │ └── ytw88 │ └── weappsupport │ ├── IconsLoader.java │ ├── WeappFramework.java │ ├── WeappJSPredefinedLibraryProvider.java │ ├── WeappModuleBuilder.java │ ├── WeappModuleType.java │ ├── WeappModuleWizardStep.java │ ├── WeappProjectStructureDetector.java │ ├── WeappStartupActivity.java │ ├── wxml │ ├── WxmlColorSettingsPage.java │ ├── WxmlFileType.java │ ├── WxmlFileTypeFactory.java │ ├── WxmlLanguage.java │ ├── WxmlLexerAdapter.java │ ├── WxmlParserDefinition.java │ ├── WxmlSyntaxHighlighter.java │ ├── WxmlSyntaxHighlighterFactory.java │ ├── grammer │ │ ├── WxmlLexer.flex │ │ ├── tutorial.bnf │ │ ├── tutorial2.bnf │ │ └── wxml.bnf │ ├── parser │ │ └── WxmlParserUtil.java │ └── psi │ │ ├── WxmlElementType.java │ │ ├── WxmlFile.java │ │ └── WxmlTokenType.java │ └── wxss │ ├── WxssElementDescriptorProvider.java │ ├── WxssElementType.java │ ├── WxssElementTypes.java │ ├── WxssFileElementType.java │ ├── WxssFileType.java │ ├── WxssFileTypeFactory.java │ ├── WxssLanguage.java │ ├── parser │ ├── WxssParser.java │ └── WxssParserDefinition.java │ └── psi │ ├── WxssFile.java │ └── impl │ └── WxssFileImpl.java └── weapp-support-plugin.iml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | gen/ 3 | weapp-support-plugin.jar 4 | idea-flex.skeleton 5 | jflex-1.7.0-2.jar 6 | out/ 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WeApp Support Plugin 微信小程序支持插件 2 | 3 | 想要实现一个微信小程序的JetBrain IDE插件,但是经过尝试发现写BNF的Grammar对我来说实在有点困 4 | 难了,短时间之内没有可能全面的学习这个东东,近期工作上的事情也还比较忙,只好将这个项目暂时搁置了。 5 | 6 | JetBrains的Plugin开发虽然官方有很多文档,但是工程太庞大了,而且这几天学习研究过程中似乎搜索的 7 | 结论都提到无法对现有的语言支持做扩展。其实微信小程序的支持,只需要对xml(wxml),css(wxss), 8 | js做一些扩展就可以用了,可惜的是没有发现支持的扩展路径。这两天尝试写wxml的bnf语法文件,已经有点 9 | 焦头烂额的感觉。 10 | 11 | IDEA官方文档中,对于语言支持插件开发的文档"Part VII - Custom Languages"包括两大部分: 12 | 13 | [介绍框架](http://www.jetbrains.org/intellij/sdk/docs/reference_guide/custom_language_support.html) 14 | 15 | [具体的教程](http://www.jetbrains.org/intellij/sdk/docs/tutorials/custom_language_support_tutorial.html) 16 | 17 | ## TODO list 18 | 近期希望完成以下功能列表,如果有熟悉idea插件开发的大牛能够协助一二自然是最好不过的了。实在是难以找到有效的文档来发现最佳的实现方案。 19 | 20 | * [ ] css中增加对rpx单位的支持 21 | * [ ] wxml中增加对wx:if/wx:else/wx:elif的支持 22 | * [ ] 增加对微信小程序工程检查的支持 23 | 24 | ## 更新日记 25 | 26 | ### 2018.12.18 27 | 今天发现jetbrains还有一个社区的js库定义,专门辅助完善一些js框架的类库定义。可以在`Preferences | 28 | Languages & Frameworks | JavaScript | Libraries`设置中添加一些library,而且这个设置界面 29 | 直接提供了下载功能,列举了git上该开源项目相关的定义,其中找到三个可能和微信小程序有关的 30 | * weapp-api 31 | * wegame-api 32 | * wexin-app 33 | 34 | git上的开源项目地址: [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) 35 | 36 | 关于ide的javascript library介绍: 37 | 38 | [How WebStorm Works: Completion for JavaScript Libraries](https://blog.jetbrains.com/webstorm/2014/07/how-webstorm-works-completion-for-javascript-libraries/) 39 | 40 | [Configuring JavaScript Libraries](https://www.jetbrains.com/help/idea/configuring-javascript-libraries.html) 41 | 42 | 文2中注明了 43 | > This feature is only supported in the Ultimate edition. 44 | 45 | 目前在webstorm中测试过,三个library都可以用,但是weapp-api定义没有wexin-app定义全面 46 | 47 | ### 2018.12.19 48 | 终于找到了一个合适的方案来把wexin-app的api添加到plugin,直接添加到javascript的支持 49 | 首先要从IDEA的plugin目录中找到JavascriptLanguage,从其子目录lib中找到 50 | JavaScriptLanguage.jar,最好是将这个jar包添加到sdk的library中去,这样才能够在开发插件 51 | 的时候使用 52 | 53 | 在`plugin.xml`中加入 54 | ``` 55 | JavaScript 56 | ... 57 | 58 | 59 | 60 | 61 | ``` 62 | * depends不是必须的,这个会使得插件只能在支持Javascript的平台上使用 63 | * extensions的声明,是为javascript的`extensionPoint:predefinedLibraryProvider`添加实现 64 | 65 | #### 弹窗显示内容 66 | 跟怒官方文档的提示,可以在插件代码中使用来弹出对话框,显示一些信息,另外用logger来输出的log暂时没找到显示窗口 67 | > Messages.showInfoMessage("Message", "Title"); 68 | 69 | #### 将文件注册为已知类型 70 | 如果是在FileTypeFactory的createFileTypes中使用FileTypeManager的getFileTypeByExtension会导致循环依 71 | 赖的异常,从而使得ide无法启动,需要在/User/Library/Application Support/xxxxx/中找到plugin并删除方可正 72 | 常启动ide。 73 | 74 | 目前将Wxml文件注册为Html文件,做法是在WxmlFileTypeFactory中直接使用HtmlFileType返回(因为HtmlFileType 75 | 的构造函数都不是public/protected的,故而无法继承),而wxss文件则通过将WxssFileType继承CssFileType来解决 76 | 77 | 以后可以抽时间继续完善wxml的更多的语法支持,比如 78 | > * * 2 | com.ytw88.weappsupport 3 | WeChat weapp Support 4 | 0.1.0 5 | kerlw 6 | 7 | 9 |
    10 |
  • Using predefined js library base on https://github.com/DefinitelyTyped/DefinitelyTyped
  • 11 |
  • Use HTMLFileType for wxml file type
  • 12 |
  • Use CSSFileType for wxss file type
  • 13 |
  • We-game support not included yet
  • 14 |
  • We-app framework not support yet
  • 15 |
16 | 17 | ]]>
18 | 19 | 0.1.0 21 | Just a simple ide for we-app
22 | ]]> 23 |
24 | 25 | 26 | 27 | 28 | 30 | 31 | com.intellij.modules.lang 32 | 33 | JavaScript 34 | com.intellij.css 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | 7 | 8 | 10 | 12 | WeApp 13 | -------------------------------------------------------------------------------- /resources/icons/weapp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kerlw/weapp-support-plugin/f6f5ad9627ce407823ae05caaa4021b5aa000834/resources/icons/weapp.png -------------------------------------------------------------------------------- /resources/icons/wxml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kerlw/weapp-support-plugin/f6f5ad9627ce407823ae05caaa4021b5aa000834/resources/icons/wxml.png -------------------------------------------------------------------------------- /resources/icons/wxss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kerlw/weapp-support-plugin/f6f5ad9627ce407823ae05caaa4021b5aa000834/resources/icons/wxss.png -------------------------------------------------------------------------------- /resources/lib/weixin-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "lib": ["es6", "dom"], 5 | "noImplicitAny": true, 6 | "noImplicitThis": true, 7 | "strictNullChecks": true, 8 | "strictFunctionTypes": true, 9 | "baseUrl": "../", 10 | "typeRoots": ["../"], 11 | "types": [], 12 | "noEmit": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "esModuleInterop": true 15 | }, 16 | "files": ["index.d.ts", "weixin-app-tests.ts"], 17 | "exclude": [".prettierrc"] 18 | } 19 | -------------------------------------------------------------------------------- /resources/lib/weixin-app/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "dtslint/dt.json", 3 | "rules": { 4 | "no-any-union": false, 5 | "no-unnecessary-generics": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /resources/lib/weixin-app/weixin-app-tests.ts: -------------------------------------------------------------------------------- 1 | getCurrentPages(); 2 | 3 | interface MyOwnEvent 4 | extends wx.CustomEvent< 5 | "my-own", 6 | { 7 | hello: string; 8 | } 9 | > {} 10 | 11 | let behavior = Behavior({ 12 | behaviors: [], 13 | properties: { 14 | myBehaviorProperty: { 15 | type: String 16 | } 17 | }, 18 | data: { 19 | myBehaviorData: "" 20 | }, 21 | attached() {}, 22 | methods: { 23 | myBehaviorMethod() { 24 | const s: string = this.data.myBehaviorData; 25 | } 26 | } 27 | }); 28 | 29 | Component({ 30 | behaviors: [behavior, "wx://form-field"], 31 | 32 | options: { 33 | multipleSlots: false, 34 | addGlobalClass: false 35 | }, 36 | 37 | properties: { 38 | myProperty: { 39 | // 属性名 40 | type: String, // 类型(必填),目前接受的类型包括:String, Number, Boolean, Object, Array, null(表示任意类型) 41 | value: "", // 属性初始值(可选),如果未指定则会根据类型选择一个 42 | observer(newVal: string, oldVal: string, changedPath: string) { 43 | const anotherKey = newVal + changedPath; 44 | this.setData({ 45 | anotherKey 46 | }); 47 | } // 属性被改变时执行的函数(可选),也可以写成在methods段中定义的方法名字符串, 如:'_propertyChange' 48 | }, 49 | myProperty2: String // 简化的定义方式 50 | }, 51 | data: { 52 | key: "value", 53 | anotherKey: "value" 54 | }, // 私有数据,可用于模版渲染 55 | 56 | lifetimes: { 57 | attached() { 58 | wx.setEnableDebug({ 59 | enableDebug: true, 60 | success(res) {} 61 | }); 62 | 63 | wx.reportMonitor("123", 123); 64 | 65 | wx.getLogManager().info("123"); 66 | wx.getLogManager().log("123"); 67 | wx.getLogManager().warn("123"); 68 | wx.getLogManager().debug("123"); 69 | 70 | this.createIntersectionObserver({}).observe("123", res => { 71 | res.id; 72 | }); 73 | }, 74 | 75 | detached() { 76 | this.setData( 77 | { 78 | key: null 79 | }, 80 | () => {} 81 | ); 82 | } 83 | }, 84 | 85 | pageLifetimes: { 86 | show() {}, 87 | hide() {} 88 | }, 89 | 90 | // 生命周期函数,可以为函数,或一个在methods段中定义的方法名 91 | attached() { 92 | this.setData( 93 | { 94 | key: "123" 95 | }, 96 | () => {} 97 | ); 98 | }, 99 | 100 | moved() {}, 101 | 102 | detached() {}, 103 | 104 | methods: { 105 | readMyDataAndMyProps() { 106 | const stringValue1: string = this.data.myProperty; 107 | const stringValue2: string = this.data.myProperty2; 108 | const stringValue3: string = this.data.key; 109 | this.data.anotherKey; 110 | this.properties.myProperty; 111 | this.properties.myProperty2; 112 | this.properties.key; 113 | this.properties.anotherKey; 114 | this.setData({ 115 | key: stringValue1 + stringValue2 + stringValue3 116 | }); 117 | }, 118 | onMyButtonTap() { 119 | // 更新属性和数据的方法与更新页面数据的方法类似 120 | this.setData({ 121 | key: 123 // note this is edge case where it cannot detect wrong types... 122 | }); 123 | }, 124 | _myPrivateMethod() { 125 | // 内部方法建议以下划线开头 126 | // this.replaceDataOnPath(['A', 0, 'B'], 'myPrivateData'); // 这里将 data.A[0].B 设为 'myPrivateData' 127 | // this.applyDataUpdates(); 128 | this.setData({ 129 | anotherKey: 123 130 | }); 131 | }, 132 | _propertyChange(newVal: string, oldVal: string) { 133 | // 134 | } 135 | }, 136 | relations: { 137 | "./custom-ul": { 138 | type: "parent", // 关联的目标节点应为父节点 139 | linked(target: wx.Component<{ key: string }, {}>) { 140 | // 每次被插入到custom-ul时执行,target是custom-ul节点实例对象,触发在attached生命周期之后 141 | target.data.key; 142 | }, 143 | linkChanged(target: wx.Component<{ key: string }, {}>) { 144 | // 每次被移动后执行,target是custom-ul节点实例对象,触发在moved生命周期之后 145 | target.data.key; 146 | }, 147 | unlinked(target: wx.Component<{ key: string }, {}>) { 148 | // 每次被移除时执行,target是custom-ul节点实例对象,触发在detached生命周期之后 149 | target.data.key; 150 | } 151 | } 152 | } 153 | }); 154 | 155 | // index.js 156 | Page({ 157 | data: { 158 | text: "This is page data." 159 | }, 160 | onLoad() { 161 | // Do some initialize when page load. 162 | this.setData({}, () => { 163 | // callback 164 | }); 165 | }, 166 | onReady: () => { 167 | // Do something when page ready. 168 | }, 169 | onShow: () => { 170 | // Do something when page show. 171 | }, 172 | onHide: () => { 173 | // Do something when page hide. 174 | }, 175 | onUnload: () => { 176 | // Do something when page close. 177 | }, 178 | onPullDownRefresh: () => { 179 | // Do something when pull down. 180 | }, 181 | onReachBottom: () => { 182 | // Do something when page reach bottom. 183 | }, 184 | onShareAppMessage: res => { 185 | if (res && res.from === "menu") { 186 | // 187 | } 188 | // return custom share data when user share. 189 | return { 190 | success(res) { 191 | console.log(res.shareTickets.length); 192 | } 193 | }; 194 | }, 195 | onPageScroll() { 196 | wx.createIntersectionObserver(this, {}) 197 | .relativeToViewport() 198 | .observe("div", res => { 199 | console.log(res.id); 200 | console.log(res.dataset); 201 | console.log(res.intersectionRatio); 202 | console.log(res.intersectionRect.left); 203 | console.log(res.intersectionRect.top); 204 | console.log(res.intersectionRect.width); 205 | console.log(res.intersectionRect.height); 206 | }) 207 | .disconnect(); 208 | }, 209 | onTabItemTap(item: any) { 210 | this.setData({ 211 | 1: null, 212 | _2: "undefined" 213 | }); 214 | console.log(item.index); 215 | console.log(item.pagePath); 216 | console.log(item.text); 217 | }, 218 | // Event handler. 219 | viewTap() { 220 | this.setData( 221 | { 222 | text: "Set some data for updating view." 223 | }, 224 | () => { 225 | // this is setData callback 226 | } 227 | ); 228 | }, 229 | customData: { 230 | hi: "MINA" 231 | }, 232 | onMyOwnEvent(e: MyOwnEvent) { 233 | e.detail.hello; 234 | }, 235 | onTouchStart(e: wx.TouchStartEvent) { 236 | e.touches; 237 | e.detail.x; 238 | e.detail.y; 239 | }, 240 | onTouchEnd(e: wx.TouchEndEvent) { 241 | e.touches; 242 | e.detail.x; 243 | e.detail.y; 244 | }, 245 | onTouchCancel(e: wx.TouchCancelEvent) { 246 | e.touches; 247 | e.detail.x; 248 | e.detail.y; 249 | }, 250 | onTouchMove(e: wx.TouchMoveEvent) { 251 | e.touches; 252 | e.detail.x; 253 | e.detail.y; 254 | } 255 | }); 256 | 257 | Page({ 258 | getScrollOffset: () => { 259 | wx.createSelectorQuery() 260 | .selectViewport() 261 | .scrollOffset(res => { 262 | res.id; // 节点的ID 263 | res.dataset; // 节点的dataset 264 | res.scrollLeft; // 节点的水平滚动位置 265 | res.scrollTop; // 节点的竖直滚动位置 266 | }) 267 | .exec(); 268 | } 269 | }); 270 | 271 | Page({ 272 | getFields: () => { 273 | wx.createSelectorQuery() 274 | .select("#the-id") 275 | .fields( 276 | { 277 | id: true, 278 | dataset: true, 279 | size: true, 280 | scrollOffset: true, 281 | properties: ["scrollX", "scrollY"] 282 | }, 283 | res => { 284 | // res. 285 | res.dataset; // 节点的dataset 286 | res.width; // 节点的宽度 287 | res.height; // 节点的高度 288 | res.scrollLeft; // 节点的水平滚动位置 289 | res.scrollTop; // 节点的竖直滚动位置 290 | res.scrollX; // 节点 scroll-x 属性的当前值 291 | res.scrollY; // 节点 scroll-x 属性的当前值 292 | } 293 | ) 294 | .exec(); 295 | } 296 | }); 297 | 298 | Page({ 299 | getRect: () => { 300 | wx.createSelectorQuery() 301 | .select("#the-id") 302 | .boundingClientRect((rect: wx.NodesRefRect) => { 303 | rect.id; // 节点的ID 304 | rect.dataset; // 节点的dataset 305 | rect.left; // 节点的左边界坐标 306 | rect.right; // 节点的右边界坐标 307 | rect.top; // 节点的上边界坐标 308 | rect.bottom; // 节点的下边界坐标 309 | rect.width; // 节点的宽度 310 | rect.height; // 节点的高度 311 | }) 312 | .exec(); 313 | }, 314 | getAllRects: () => { 315 | wx.createSelectorQuery() 316 | .selectAll(".a-class") 317 | .boundingClientRect((rects: wx.NodesRefRect[]) => { 318 | rects.forEach(rect => { 319 | rect.id; // 节点的ID 320 | rect.dataset; // 节点的dataset 321 | rect.left; // 节点的左边界坐标 322 | rect.right; // 节点的右边界坐标 323 | rect.top; // 节点的上边界坐标 324 | rect.bottom; // 节点的下边界坐标 325 | rect.width; // 节点的宽度 326 | rect.height; // 节点的高度 327 | }); 328 | }) 329 | .exec(); 330 | } 331 | }); 332 | 333 | const recorderManager = wx.getRecorderManager(); 334 | 335 | recorderManager.onStart(() => { 336 | console.log("recorder start"); 337 | }); 338 | recorderManager.onResume(() => { 339 | console.log("recorder resume"); 340 | }); 341 | recorderManager.onPause(() => { 342 | console.log("recorder pause"); 343 | }); 344 | recorderManager.onStop(res => { 345 | console.log("recorder stop", res); 346 | const { tempFilePath } = res; 347 | }); 348 | recorderManager.onFrameRecorded(res => { 349 | const { frameBuffer } = res; 350 | console.log("frameBuffer.byteLength", frameBuffer.byteLength); 351 | }); 352 | 353 | const options = { 354 | duration: 10000, 355 | sampleRate: 44100, 356 | numberOfChannels: 1, 357 | encodeBitRate: 192000, 358 | format: "aac", 359 | frameSize: 50 360 | }; 361 | 362 | recorderManager.start(options); 363 | 364 | wx.onGetWifiList(res => { 365 | if (res.wifiList.length) { 366 | wx.setWifiList({ 367 | wifiList: [ 368 | { 369 | SSID: res.wifiList[0].SSID, 370 | BSSID: res.wifiList[0].BSSID, 371 | password: "123456" 372 | } 373 | ] 374 | }); 375 | } else { 376 | wx.setWifiList({ 377 | wifiList: [] 378 | }); 379 | } 380 | }); 381 | wx.getWifiList(); 382 | 383 | wx.onWifiConnected(wifi => { 384 | // wifi.BSSID 385 | }); 386 | 387 | wx.getConnectedWifi({ 388 | success(result) { 389 | result.signalStrength; 390 | } 391 | }); 392 | 393 | wx.getWeRunData({ 394 | success(res) { 395 | const encryptedData = res.encryptedData; 396 | } 397 | }); 398 | 399 | const uploadTask = wx.uploadFile({ 400 | url: "http://example.weixin.qq.com/upload", // 仅为示例,非真实的接口地址 401 | filePath: "/local/folder/file.ext", 402 | name: "file", 403 | formData: { 404 | user: "test" 405 | }, 406 | success: res => { 407 | const data = res.data; 408 | // do something 409 | } 410 | }); 411 | 412 | uploadTask.onProgressUpdate(res => { 413 | console.log("上传进度", res.progress); 414 | console.log("已经上传的数据长度", res.totalBytesSent); 415 | console.log("预期需要上传的数据总长度", res.totalBytesExpectedToSend); 416 | }); 417 | 418 | uploadTask.abort(); // 取消上传任务 419 | 420 | const downloadTask = wx.downloadFile({ 421 | url: "http://example.com/audio/123", // 仅为示例,并非真实的资源 422 | success: res => { 423 | wx.playVoice({ 424 | filePath: res.tempFilePath 425 | }); 426 | } 427 | }); 428 | 429 | downloadTask.onProgressUpdate(res => { 430 | console.log("下载进度", res.progress); 431 | console.log("已经下载的数据长度", res.totalBytesWritten); 432 | console.log("预期需要下载的数据总长度", res.totalBytesExpectedToWrite); 433 | }); 434 | 435 | downloadTask.abort(); // 取消下载任务 436 | 437 | wx.request({ 438 | url: "https://www.baidu.com", 439 | method: "GET", 440 | success(res) { 441 | if (res.statusCode < 300) { 442 | console.log(res.data); 443 | } else { 444 | console.warn(res.statusCode, res.header); 445 | } 446 | }, 447 | fail(e) { 448 | console.error(e); 449 | } 450 | }).abort(); 451 | 452 | wx.getSystemInfo({ 453 | success(res) { 454 | const { 455 | brand, 456 | pixelRatio, 457 | platform, 458 | windowHeight, 459 | windowWidth, 460 | screenHeight, 461 | screenWidth, 462 | statusBarHeight, 463 | SDKVersion, 464 | language, 465 | model, 466 | version, 467 | fontSizeSetting, 468 | system 469 | } = res; 470 | } 471 | }); 472 | 473 | function testAccountInfo(): string { 474 | const accountInfo: wx.AccountInfo = wx.getAccountInfoSync(); 475 | return accountInfo.miniProgram.appId; 476 | } 477 | 478 | wx.reportAnalytics("test-event", { a: 1, b: "2" }); 479 | -------------------------------------------------------------------------------- /src/com/ytw88/weappsupport/IconsLoader.java: -------------------------------------------------------------------------------- 1 | package com.ytw88.weappsupport; 2 | 3 | import com.intellij.openapi.util.IconLoader; 4 | 5 | import javax.swing.*; 6 | 7 | public class IconsLoader { 8 | public final static Icon WEAPP_ICON = IconLoader.findIcon("/icons/weapp.png"); 9 | 10 | public final static Icon WXML_ICON = IconLoader.findIcon("/icons/wxml.png"); 11 | 12 | public final static Icon WXSS_ICON = IconLoader.findIcon("/icons/wxss.png"); 13 | } 14 | -------------------------------------------------------------------------------- /src/com/ytw88/weappsupport/WeappFramework.java: -------------------------------------------------------------------------------- 1 | package com.ytw88.weappsupport; 2 | 3 | import com.intellij.framework.FrameworkTypeEx; 4 | import com.intellij.framework.addSupport.FrameworkSupportInModuleConfigurable; 5 | import com.intellij.framework.addSupport.FrameworkSupportInModuleProvider; 6 | import com.intellij.ide.util.frameworkSupport.FrameworkSupportModel; 7 | import com.intellij.openapi.module.Module; 8 | import com.intellij.openapi.module.ModuleType; 9 | import com.intellij.openapi.roots.ModifiableModelsProvider; 10 | import com.intellij.openapi.roots.ModifiableRootModel; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import javax.swing.*; 15 | 16 | public class WeappFramework extends FrameworkTypeEx { 17 | public static final String FRAMEWORK_ID = "Weapp"; 18 | 19 | protected WeappFramework() { 20 | super(FRAMEWORK_ID); 21 | } 22 | 23 | @NotNull 24 | @Override 25 | public FrameworkSupportInModuleProvider createProvider() { 26 | return new FrameworkSupportInModuleProvider() { 27 | @NotNull 28 | @Override 29 | public FrameworkTypeEx getFrameworkType() { 30 | return WeappFramework.this; 31 | } 32 | 33 | @NotNull 34 | @Override 35 | public FrameworkSupportInModuleConfigurable createConfigurable(@NotNull FrameworkSupportModel frameworkSupportModel) { 36 | return new FrameworkSupportInModuleConfigurable() { 37 | @Nullable 38 | @Override 39 | public JComponent createComponent() { 40 | return new JCheckBox("Extra Option"); 41 | } 42 | 43 | @Override 44 | public void addSupport(@NotNull Module module, @NotNull ModifiableRootModel modifiableRootModel, @NotNull ModifiableModelsProvider modifiableModelsProvider) { 45 | //do what you want here: setup a library, generate a specific file, etc 46 | //TODO 这里也许可以将weapp的library加入到js library中 47 | } 48 | }; 49 | } 50 | 51 | @Override 52 | public boolean isEnabledForModuleType(@NotNull ModuleType moduleType) { 53 | return (moduleType instanceof WeappModuleType); 54 | } 55 | }; 56 | } 57 | 58 | @NotNull 59 | @Override 60 | public String getPresentableName() { 61 | return "WeApp Framework"; 62 | } 63 | 64 | @NotNull 65 | @Override 66 | public Icon getIcon() { 67 | return IconsLoader.WEAPP_ICON; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/com/ytw88/weappsupport/WeappJSPredefinedLibraryProvider.java: -------------------------------------------------------------------------------- 1 | package com.ytw88.weappsupport; 2 | 3 | import com.intellij.lang.javascript.library.JSPredefinedLibraryProvider; 4 | import com.intellij.openapi.diagnostic.Logger; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.openapi.vfs.VfsUtil; 7 | import com.intellij.openapi.vfs.VirtualFile; 8 | import com.intellij.webcore.libraries.ScriptingLibraryModel; 9 | import org.jetbrains.annotations.NonNls; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.net.URL; 13 | import java.util.ArrayList; 14 | import java.util.HashSet; 15 | import java.util.List; 16 | import java.util.Set; 17 | 18 | public class WeappJSPredefinedLibraryProvider extends JSPredefinedLibraryProvider { 19 | @NonNls 20 | private static final String LIBRARY_NAME = "weapp-api"; 21 | 22 | @NonNls 23 | private String[] jsFiles = { 24 | "/lib/weixin-app/index.d.ts", 25 | "/lib/weixin-app/tsconfig.json", 26 | "/lib/weixin-app/tslint.json", 27 | "/lib/weixin-app/weixin-app-tests.ts" 28 | }; 29 | 30 | @NotNull 31 | @Override 32 | public ScriptingLibraryModel[] getPredefinedLibraries(@NotNull Project project) { 33 | Set virtualFiles = getVirtualFiles(); 34 | 35 | ScriptingLibraryModel scriptingLibraryModel = ScriptingLibraryModel.createPredefinedLibrary(LIBRARY_NAME, 36 | virtualFiles.toArray(new VirtualFile[virtualFiles.size()]), true); 37 | 38 | return new ScriptingLibraryModel[]{scriptingLibraryModel}; 39 | } 40 | 41 | @NotNull 42 | @Override 43 | public Set getRequiredLibraryFilesToIndex() { 44 | return getVirtualFiles(); 45 | } 46 | 47 | @NotNull 48 | @Override 49 | public Set getRequiredLibraryFilesForResolve(@NotNull Project project) { 50 | return getVirtualFiles(); 51 | } 52 | 53 | private Set getVirtualFiles() { 54 | Set virtualFiles = new HashSet<>(); 55 | 56 | final List names = new ArrayList(); 57 | for (String libFileName : this.jsFiles) { 58 | URL fileUrl = WeappJSPredefinedLibraryProvider.class.getResource(libFileName); 59 | names.add(fileUrl.toString()); 60 | virtualFiles.add(VfsUtil.findFileByURL(fileUrl)); 61 | } 62 | return virtualFiles; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/com/ytw88/weappsupport/WeappModuleBuilder.java: -------------------------------------------------------------------------------- 1 | package com.ytw88.weappsupport; 2 | 3 | import com.intellij.ide.util.projectWizard.ModuleBuilder; 4 | import com.intellij.ide.util.projectWizard.ModuleWizardStep; 5 | import com.intellij.ide.util.projectWizard.WizardContext; 6 | import com.intellij.openapi.Disposable; 7 | import com.intellij.openapi.module.ModuleType; 8 | import com.intellij.openapi.options.ConfigurationException; 9 | import com.intellij.openapi.roots.ModifiableRootModel; 10 | import com.intellij.openapi.roots.libraries.LibraryTable; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | public class WeappModuleBuilder extends ModuleBuilder { 14 | @Override 15 | public void setupRootModel(ModifiableRootModel modifiableRootModel) throws ConfigurationException { 16 | //TODO .... 17 | LibraryTable libraryTable = modifiableRootModel.getModuleLibraryTable(); 18 | libraryTable.createLibrary(); 19 | } 20 | 21 | @Override 22 | public ModuleType getModuleType() { 23 | return WeappModuleType.getInstance(); 24 | } 25 | 26 | @Nullable 27 | @Override 28 | public ModuleWizardStep getCustomOptionsStep(WizardContext context, Disposable parentDisposable) { 29 | return new WeappModuleWizardStep(); 30 | } 31 | 32 | @Nullable 33 | @Override 34 | public String getBuilderId() { 35 | return "weapp"; 36 | } 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/com/ytw88/weappsupport/WeappModuleType.java: -------------------------------------------------------------------------------- 1 | package com.ytw88.weappsupport; 2 | 3 | import com.intellij.ide.util.projectWizard.ModuleWizardStep; 4 | import com.intellij.ide.util.projectWizard.WizardContext; 5 | import com.intellij.openapi.module.ModuleType; 6 | import com.intellij.openapi.module.ModuleTypeManager; 7 | import com.intellij.openapi.roots.ui.configuration.ModulesProvider; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import javax.swing.*; 11 | 12 | public class WeappModuleType extends ModuleType { 13 | public static final String MODULE_ID = "WEAPP_MODULE"; 14 | 15 | public static WeappModuleType getInstance() { 16 | return (WeappModuleType) ModuleTypeManager.getInstance().findByID(MODULE_ID); 17 | } 18 | 19 | public WeappModuleType() { 20 | super(MODULE_ID); 21 | } 22 | 23 | @NotNull 24 | @Override 25 | public WeappModuleBuilder createModuleBuilder() { 26 | return new WeappModuleBuilder(); 27 | } 28 | 29 | @NotNull 30 | @Override 31 | public String getName() { 32 | return "WeChat App"; 33 | } 34 | 35 | @NotNull 36 | @Override 37 | public String getDescription() { 38 | return "Weapp module"; 39 | } 40 | 41 | @Override 42 | public Icon getNodeIcon(boolean b) { 43 | return IconsLoader.WEAPP_ICON; 44 | } 45 | 46 | @NotNull 47 | @Override 48 | public ModuleWizardStep[] createWizardSteps(@NotNull WizardContext wizardContext, @NotNull WeappModuleBuilder moduleBuilder, @NotNull ModulesProvider modulesProvider) { 49 | return super.createWizardSteps(wizardContext, moduleBuilder, modulesProvider); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/com/ytw88/weappsupport/WeappModuleWizardStep.java: -------------------------------------------------------------------------------- 1 | package com.ytw88.weappsupport; 2 | 3 | import com.intellij.ide.util.projectWizard.ModuleWizardStep; 4 | 5 | import javax.swing.*; 6 | 7 | public class WeappModuleWizardStep extends ModuleWizardStep { 8 | @Override 9 | public JComponent getComponent() { 10 | return new JLabel("hahahaha"); 11 | } 12 | 13 | @Override 14 | public void updateDataModel() { 15 | //todo update model according to UI 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/com/ytw88/weappsupport/WeappProjectStructureDetector.java: -------------------------------------------------------------------------------- 1 | package com.ytw88.weappsupport; 2 | 3 | import com.intellij.ide.util.importProject.ProjectDescriptor; 4 | import com.intellij.ide.util.projectWizard.importSources.DetectedProjectRoot; 5 | import com.intellij.ide.util.projectWizard.importSources.ProjectFromSourcesBuilder; 6 | import com.intellij.ide.util.projectWizard.importSources.ProjectStructureDetector; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.io.File; 10 | import java.util.Collection; 11 | import java.util.List; 12 | 13 | public class WeappProjectStructureDetector extends ProjectStructureDetector { 14 | @NotNull 15 | @Override 16 | public DirectoryProcessingResult detectRoots(@NotNull File file, @NotNull File[] files, @NotNull File file1, @NotNull List list) { 17 | return null; 18 | } 19 | 20 | @Override 21 | public String getDetectorId() { 22 | return "Weapp"; 23 | } 24 | 25 | @Override 26 | public void setupProjectStructure(@NotNull Collection roots, @NotNull ProjectDescriptor projectDescriptor, @NotNull ProjectFromSourcesBuilder builder) { 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/com/ytw88/weappsupport/WeappStartupActivity.java: -------------------------------------------------------------------------------- 1 | package com.ytw88.weappsupport; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.startup.StartupActivity; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | public class WeappStartupActivity implements StartupActivity { 8 | @Override 9 | public void runActivity(@NotNull Project project) { 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/com/ytw88/weappsupport/wxml/WxmlColorSettingsPage.java: -------------------------------------------------------------------------------- 1 | package com.ytw88.weappsupport.wxml; 2 | 3 | import com.intellij.openapi.editor.colors.TextAttributesKey; 4 | import com.intellij.openapi.fileTypes.SyntaxHighlighter; 5 | import com.intellij.openapi.options.colors.AttributesDescriptor; 6 | import com.intellij.openapi.options.colors.ColorDescriptor; 7 | import com.intellij.openapi.options.colors.ColorSettingsPage; 8 | import com.ytw88.weappsupport.IconsLoader; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | import javax.swing.*; 13 | import java.util.Map; 14 | 15 | public class WxmlColorSettingsPage implements ColorSettingsPage { 16 | private static final AttributesDescriptor[] DESCRIPTORS = new AttributesDescriptor[]{ 17 | new AttributesDescriptor("Comment", WxmlSyntaxHighlighter.COMMENT), 18 | }; 19 | 20 | @Nullable 21 | @Override 22 | public Icon getIcon() { 23 | return IconsLoader.WXML_ICON; 24 | } 25 | 26 | @NotNull 27 | @Override 28 | public SyntaxHighlighter getHighlighter() { 29 | return new WxmlSyntaxHighlighter(); 30 | } 31 | 32 | @NotNull 33 | @Override 34 | public String getDemoText() { 35 | return "\n" + 36 | "\n" + 37 | " > (attrs | rule) { 60 | pin=1 recoverWhile=grammar_element_recover 61 | } 62 | private grammar_element_recover::=!('{'|rule_start) 63 | 64 | rule ::= rule_start expression attrs? ';'? {pin=2} 65 | private rule_start ::= modifier* id '::=' 66 | modifier ::= 'private' | 'external' | 'meta' 67 | | 'inner' | 'left' | 'upper' | 'fake' 68 | 69 | attrs ::= '{' attr * '}' {pin=1} 70 | attr ::= attr_start attr_value ';'? { 71 | pin=1 recoverWhile=attr_recover 72 | } 73 | private attr_start ::= id (attr_pattern '=' | '=') { 74 | pin(".*")="attr_pattern" 75 | } 76 | private attr_start_simple ::= id attr_pattern? '=' 77 | private attr_recover ::= !('}' | attr_start) 78 | private attr_value ::= attr_value_inner !'=' 79 | private attr_value_inner ::= reference_or_token 80 | | literal_expression 81 | | value_list 82 | attr_pattern ::= '(' string_literal_expression ')' { 83 | pin=1 methods=[literalExpression="string_literal_expression"] 84 | } 85 | 86 | value_list ::= '[' list_entry * ']' {pin=1 extends=expression} 87 | list_entry ::= (id list_entry_tail? | string_literal_expression) ';'? { 88 | recoverWhile=list_entry_recover 89 | methods=[getReferences literalExpression="string_literal_expression"] 90 | } 91 | private list_entry_tail ::= '=' string_literal_expression {pin=1} 92 | private list_entry_recover ::= !(']' | '}' | id | string) 93 | 94 | expression ::= sequence choice? 95 | sequence ::= option * { 96 | extends=expression recoverWhile=sequence_recover 97 | } 98 | private sequence_recover ::= 99 | !(';'|'|'|'('|')'|'['|']'|'{'|'}') grammar_element_recover 100 | private option ::= predicate | paren_opt_expression | simple quantified? 101 | 102 | left choice ::= ( '|' sequence ) + {pin(".*")=1 extends=expression} 103 | left quantified ::= quantifier {extends=expression } 104 | quantifier ::= '?' | '+' | '*' 105 | 106 | predicate ::= predicate_sign simple {extends=expression} 107 | predicate_sign ::= '&' | '!' 108 | 109 | fake parenthesized ::= '(' expression ')' {extends=expression} 110 | private simple ::= !(modifier* id '::=' ) reference_or_token 111 | | literal_expression 112 | | external_expression 113 | | paren_expression 114 | external_expression ::= '<<' reference_or_token option * '>>' { 115 | pin=2 extends=expression methods=[refElement='/expression[0]' getArguments] 116 | } 117 | reference_or_token ::= id {extends=expression methods=[resolveRule]} 118 | literal_expression ::= string_literal_expression | number {extends=expression } 119 | string_literal_expression ::= string {extends=literal_expression} 120 | paren_expression ::= '(' expression ')' | '{' alt_choice_element '}' {pin(".*")=2} 121 | paren_opt_expression ::= '[' expression ']' {pin=2} 122 | private alt_choice_element ::= !attr_start_simple expression -------------------------------------------------------------------------------- /src/com/ytw88/weappsupport/wxml/grammer/tutorial2.bnf: -------------------------------------------------------------------------------- 1 | { 2 | tokens=[ 3 | SEMI=';' 4 | EQ='=' 5 | LP='(' 6 | RP=')' 7 | 8 | space='regexp:\s+' 9 | comment='regexp://.*' 10 | number='regexp:\d+(\.\d*)?' 11 | id='regexp:\p{Alpha}\w*' 12 | string="regexp:('([^'\\]|\\.)*'|\"([^\"\\]|\\.)*\")" 13 | 14 | op_1='+' 15 | op_2='-' 16 | op_3='*' 17 | op_4='/' 18 | op_5='!' 19 | ] 20 | 21 | name(".*expr")='expression' 22 | extends(".*expr")=expr 23 | } 24 | 25 | root ::= root_item * 26 | private root_item ::= !<> property ';' {pin=1 recoverWhile=property_recover} 27 | 28 | property ::= id '=' expr {pin=2} 29 | private property_recover ::= !(';' | id '=') 30 | 31 | expr ::= factor plus_expr * 32 | left plus_expr ::= plus_op factor 33 | private plus_op ::= '+'|'-' 34 | private factor ::= primary mul_expr * 35 | left mul_expr ::= mul_op primary 36 | private mul_op ::= '*'|'/' 37 | private primary ::= primary_inner factorial_expr ? 38 | left factorial_expr ::= '!' 39 | private primary_inner ::= literal_expr | ref_expr | paren_expr 40 | paren_expr ::= '(' expr ')' {pin=1} 41 | ref_expr ::= id 42 | literal_expr ::= number | string | float -------------------------------------------------------------------------------- /src/com/ytw88/weappsupport/wxml/grammer/wxml.bnf: -------------------------------------------------------------------------------- 1 | { 2 | // Name and the location of the parser which will be generated. 3 | parserClass="com.ytw88.weappsupport.wxml.parser.WxmlParser" 4 | parserUtilClass="com.ytw88.weappsupport.wxml.parser.WxmlParserUtil" 5 | 6 | // All nodes will extend this class. This wraps AST node to a PSI node. 7 | extends="com.intellij.extapi.psi.ASTWrapperPsiElement" 8 | 9 | // Prefix for all generated classes. 10 | psiClassPrefix="Wxml" 11 | // Suffix for implementation classes. 12 | psiImplClassSuffix="Impl" 13 | 14 | // Location to be used when generating PSI classes. 15 | psiPackage="com.ytw88.weappsupport.wxml.psi" 16 | // Location to be used when generating PSI implementation classes. 17 | psiImplPackage="com.ytw88.weappsupport.wxml.psi.impl" 18 | 19 | // Element type holder class name. 20 | elementTypeHolderClass="com.ytw88.weappsupport.wxml.psi.WxmlTypes" 21 | 22 | // Class which will be used to create internal nodes. 23 | elementTypeClass="com.ytw88.weappsupport.wxml.psi.WxmlElementType" 24 | // Class which will be used to create leaf nodes. 25 | tokenTypeClass="com.ytw88.weappsupport.wxml.psi.WxmlTokenType" 26 | 27 | tokens = [ 28 | Eq = '=' 29 | S = 'regexp:\s+' 30 | 31 | Comment = 'regexp:\