Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 精品国产3p一区二区三区,国产成人一区二区精品非洲,亚洲国产精品大秀在线播放

          整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          如何編寫屬于自己的 PostCSS 8 插件?

          者近期在將前端架構 webpack 升級到 5 時,一些配套模塊也需要進行升級,其中包括了 css 處理模塊 PostCSS。舊版本使用的是 PostCSS 7,在升級至 PostCSS 8 的過程中,筆者發現部分插件前置依賴還是停留在 7 版本,且年久失修,在 PostCSS 8 中出現各種各樣的問題,無奈只能研究源碼,將目前部分舊版本插件升級至新版本。這里,筆者將升級插件的過程進行簡化和提煉,讓讀者自己也可以編寫一個 PostCSS 8 插件。

          插件工作原理

          PostCSS 是一個允許使用 JS 插件轉換樣式的工具。開發者可以根據自己的實際需求,在編譯過程將指定 css 樣式進行轉換和處理。目前 PostCSS 官方收錄插件有 200 多款,其中包括使用最廣泛的Autoprefixer自動補全 css 前綴插件。

          PostCSS 和插件的工作原理其實很簡單,就是先將 css 源碼轉換為 AST,插件基于轉換后 AST 的信息進行個性化處理,最后 PostCSS 再將處理后的 AST 信息轉換為 css 源碼,完成 css 樣式轉換,其流程可以歸結為下圖:

          下面我們通過實際例子看看 PostCSS 會將 css 源碼轉換成的 AST 格式:

          const postcss = require('postcss')
          postcss().process(`
          .demo {
           font-size: 14px; /*this is a comment*/
          }
          `).then(result => {
           console.log(result)
          })

          復制代碼

          代碼中直接引用 PostCSS,在不經過任何插件的情況下將 css 源碼進行轉換,AST 轉換結果如下:

          {
           "processor": {
           "version": "8.3.6",
           "plugins": []
           },
           "messages": [],
           "root": {
           "raws": {
           "semicolon": false,
           "after": "\n"
           },
           "type": "root",
           // ↓ nodes字段內容重點關注
           "nodes": [
           {
           "raws": {
           "before": "\n",
           "between": " ",
           "semicolon": true,
           "after": "\n"
           },
           "type": "rule",
           "nodes": [
           {
           "raws": {
           "before": "\n ",
           "between": ": "
           },
           "type": "decl",
           "source": {
           "inputId": 0,
           "start": {
           "offset": 11,
           "line": 3,
           "column": 3
           },
           "end": {
           "offset": 26,
           "line": 3,
           "column": 18
           }
           },
           "prop": "font-size", // css屬性和值
           "value": "14px"
           },
           {
           "raws": {
           "before": " ",
           "left": "",
           "right": ""
           },
           "type": "comment", // 注釋類
           "source": {
           "inputId": 0,
           "start": {
           "offset": 28,
           "line": 3,
           "column": 20
           },
           "end": {
           "offset": 48,
           "line": 3,
           "column": 40
           }
           },
           "text": "this is a comment"
           }
           ],
           "source": {
           "inputId": 0,
           "start": {
           "offset": 1,
           "line": 2,
           "column": 1
           },
           "end": {
           "offset": 28,
           "line": 4,
           "column": 1
           }
           },
           "selector": ".demo", // 類名
           "lastEach": 1,
           "indexes": {}
           }
           ],
           "source": {
           "inputId": 0,
           "start": {
           "offset": 0,
           "line": 1,
           "column": 1
           }
           },
           "lastEach": 1,
           "indexes": {},
           "inputs": [
           {
           "hasBOM": false,
           "css": "\n.demo {\n font-size: 14px;\n}\n",
           "id": "<input css vi1Oew>"
           }
           ]
           },
           "opts": {},
           "css": "\n.demo {\n font-size: 14px;\n}\n"
          }

          復制代碼

          AST 對象中 nodes 字段里的內容尤為重要,其中存儲了 css 源碼的關鍵字、注釋、源碼的起始、結束位置以及 css 的屬性和屬性值,類名使用selector存儲,每個類下又存儲一個 nodes 數組,該數組下存放的就是該類的屬性(prop)和屬性值(value)。那么插件就可以基于 AST 字段對 css 屬性進行修改,從而實現 css 的轉換。

          PostCSS 插件格式規范及 API

          PostCSS 插件其實就是一個 JS 對象,其基本形式和解析如下:

          module.exports = (opts = { }) => {
           // 此處可對插件配置opts進行處理
           return {
           postcssPlugin: 'postcss-test', // 插件名字,以postcss-開頭
           
          Once (root, postcss) {
           // 此處root即為轉換后的AST,此方法轉換一次css將調用一次
           },
           
          Declaration (decl, postcss) {
           // postcss遍歷css樣式時調用,在這里可以快速獲得type為decl的節點(請參考第二節的AST對象)
           },
           
          Declaration: {
           color(decl, postcss) {
           // 可以進一步獲得decl節點指定的屬性值,這里是獲得屬性為color的值
           }
           },
           
          Comment (comment, postcss) {
           // 可以快速訪問AST注釋節點(type為comment)
           },
           
          AtRule(atRule, postcss) {
           // 可以快速訪問css如@media,@import等@定義的節點(type為atRule)
           }
           
          }
          }
          module.exports.postcss = true

          復制代碼

          更多的 PostCSS 插件 API 可以詳細參考官方postcss8文檔,基本原理就是 PostCSS 會遍歷每一個 css 樣式屬性值、注釋等節點,之后開發者就可以針對個性需求對節點進行處理即可。

          實際開發一個 PostCSS 8 插件

          了解了 PostCSS 插件的格式和 API,我們將根據實際需求來開發一個簡易的插件,有如下 css:

          .demo {
           font-size: 14px; /*this is a comment*/
           color: #ffffff;
          }

          復制代碼

          需求如下:

          1. 刪除 css 內注釋
          2. 將所有顏色為十六進制的#ffffff轉為 css 內置的顏色變量white

          根據第三節的插件格式,本次開發只需使用Comment和Declaration接口即可:

          // plugin.js
          module.exports = (opts = { }) => {
           return {
           postcssPlugin: 'postcss-test',
           
          Declaration (decl, postcss) {
           if (decl.value === '#ffffff') {
           decl.value = 'white'
           }
           },
           
          Comment(comment) {
           comment.text = ''
           }
           
          }
          }
          module.exports.postcss = true

          復制代碼

          在 PostCSS 中使用該插件:

          // index.js
          const plugin = require('./plugin.js')
          postcss([plugin]).process(`
          .demo {
           font-size: 14px; /*this is a comment*/
           color: #ffffff;
          }
          `).then(result => {
           console.log(result.css)
          })

          復制代碼

          運行結果如下:

          .demo {
           font-size: 14px; /**/
           color: white;
          }

          復制代碼

          可以看到,字體顏色值已經成功做了轉換,注釋內容已經刪掉,但注釋標識符還依舊存在,這是因為注釋節點是包含/**/內容存在的,只要 AST 里注釋節點還存在,最后 PostCSS 還原 AST 時還是會把這段內容還原,要做到徹底刪掉注釋,需要對 AST 的 nodes 字段進行遍歷,將 type 為 comment 的節點進行刪除,插件源碼修改如下:

          // plugin.js
          module.exports = (opts = { }) => {
           // Work with options here
           // https://postcss.org/api/#plugin
           return {
           postcssPlugin: 'postcss-test',
           
          Once (root, postcss) {
           // Transform CSS AST here
           root.nodes.forEach(node => {
           if (node.type === 'rule') {
           node.nodes.forEach((n, i) => {
           if (n.type === 'comment') {
           node.nodes.splice(i, 1)
           }
           })
           }
           })
           },
           
          
          Declaration (decl, postcss) {
           // The faster way to find Declaration node
           if (decl.value === '#ffffff') {
           decl.value = 'white'
           }
           }
           
          }
          }
          module.exports.postcss = true

          復制代碼

          重新執行 PostCSS,結果如下,符合預期。

          .demo {
           font-size: 14px;
           color: white;
          }

          復制代碼

          插件開發注意事項

          通過實操開發可以看到,開發一個 PostCSS 插件其實很簡單,但在實際的插件開發中,開發者需要注意以下事項:

          1.盡量使插件簡單,使用者可以到手即用

          Build code that is short, simple, clear, and modular.

          盡量使你的插件和使用者代碼解耦,開放有限的 API,同時開發者在使用你的插件時從名字就可以知道插件的功能。這里推薦一個簡單而優雅的 PostCSS 插件postcss-focus,讀者可以從這個插件的源碼中體會這個設計理念。

          2.開發插件前確認是否有現成的輪子

          如果你對自己的項目有個新點子,想自己開發一個插件去實現,在開始寫代碼前,可以先到 PostCSS 官方注冊的插件列表中查看是否有符合自己需求的插件,避免重復造輪子。不過截止目前(2021.8),大部分插件依舊停留在 PostCSS 8 以下,雖然 PostCSS 8 已經對舊版本插件做了處理,但在 AST 的解析處理上還是有差異,從實際使用過程中我就發現 PostCss8 使用低版本插件會導致 AST 內的source map丟失,因此目前而言完全兼容 PostCSS 8 的插件還需各位開發者去升級。

          從低版本 PostCSS 遷移

          升級你的 PostCSS 插件具體可以參考官方給出的升級指引。這里只對部分關鍵部分做下解釋:

          1.升級 API

          • 將舊版module.exports = postcss.plugin(name, creator)替換為module.exports = creator;
          • 新版插件將直接返回一個對象,對象內包含Once方法回調;
          • 將原插件邏輯代碼轉移至Once方法內;
          • 插件源碼最后加上module.exports.postcss = true;

          具體示例如下。

          舊版插件:

          - module.exports = postcss.plugin('postcss-dark-theme-class', (opts = {}) => {
          - checkOpts(opts)
          - return (root, result) => {
           root.walkAtRules(atrule => { … })
          - }
          - })

          復制代碼

          升級后插件:

          + module.exports = (opts = {}) => {
          + checkOpts(opts)
          + return {
          + postcssPlugin: 'postcss-dark-theme-class',
          + Once (root, { result }) {
           root.walkAtRules(atrule => { … })
          + }
          + }
          + }
          + module.exports.postcss = true

          復制代碼

          2.提取邏輯代碼至新版 API

          把邏輯代碼都放在Once回調內還不夠優雅,PostCSS 8 已經實現了單個 css 的代碼掃描,提供了Declaration(), Rule(), AtRule(), Comment() 等方法,舊版插件類似root.walkAtRules的方法就可以分別進行重構,插件效率也會得到提升:

          module.exports = {
           postcssPlugin: 'postcss-dark-theme-class',
          - Once (root) {
          - root.walkAtRules(atRule => {
          - // Slow
          - })
          - }
          + AtRule (atRule) {
          + // Faster
          + }
           }
           module.exports.postcss = true

          復制代碼

          總結

          通過本文的介紹,讀者可以了解 PostCSS 8 工作的基本原理,根據具體需求快速開發一個 PostCSS 8 插件,并在最后引用官方示例中介紹了如何快速升級舊版 PostCSS 插件。目前 PostCSS 8 還有大量還沒進行升級兼容的 PostCSS 插件,希望讀者可以在閱讀本文后可以獲得啟發,對 PostCSS 8 的插件生態做出貢獻。

          信大家平時在電腦上逛掘金、知乎網站時,肯定有看到過下面超級煩人的跳轉攔截確認頁面

          雖然這種攔截的初衷是好的,但是我相信大家平時肯定不會因為有了這個攔截提醒頁面,就會對即將打開的網站安全性提高自己的警惕性,而是把它當做用戶協議一樣無視并點擊“繼續訪問”。這種體驗給人的感覺是十分難受的,特別是有時候看一些技術文章,文章里面會貼一些參考資料鏈接,有時我會習慣先右鍵新tab中打開,并且繼續往下閱讀,等看到剛打開的tab欄沒有加載圈圈時(說明頁面已經加載完畢),再切過去看,結果被攔截了???

          上面的痛點,其實很容易解決,就是通過開發一個瀏覽器插件實現。

          實現思路

          我們先打開控制臺看下這些網站跳轉鏈接長啥樣:

          掘金:

          知乎:

          可以看到,a標簽的鏈接里面并不是直接放置我們要跳轉網站鏈接,而是把它放在了target參數里面。我們要做的就是通過插件,給頁面添加點擊監聽事件,先攔截a標簽的默認跳轉行為,然后通過js提取到我們要跳轉的鏈接,通過window.open或者window.localtion打開即可。

          開始動手開發插件

          新建manifest.json配置文件

          首先我們新建個項目文件夾,命名direct-link,在里面新建manifest.json配置文件,里面存放我們插件的配置信息。內容如下:

          {
            "name": "direct link", // 插件名字
            "description": "跳過網站點擊跳轉詢問頁面!", // 插件描述
            "version": "0.0.1", // 版本號
            "manifest_version": 3, // 插件版本,目前大多插件還是2, 3是目前最新規范標準
            "permissions": ["storage", "tabs", "scripting"], // 插件需要用到的權限
            "background": {
              "service_worker": "./background.js" // 對應background.js文件,相當于程序運行入口
            },
            "action": {
              "default_popup": "popup.html",
              "default_icon": {
                "16": "/images/logo16.png",
                "32": "/images/logo32.png",
                "48": "/images/logo48.png",
                "128": "/images/logo128.png"
              }
            },
            "icons": {
              "16": "/images/logo16.png",
              "32": "/images/logo32.png",
              "48": "/images/logo48.png",
              "128": "/images/logo128.png"
            }
          }

          新建images文件夾

          新建images文件夾,里面存放插件的圖標。我們可以去iconfont網站查找下載圖片即可,尺寸需要下載多個,從上面配置文件可以看到一共放了16,32,48及128四個分辨率的圖片。

          新建background.js

          在根目錄下新建background.js,該文件相當于程序運行入口。創建background.js文件之后,此時準備的文件已經可以在瀏覽器中運行了。我們按如下圖方式打開瀏覽器插件頁面

          然后將右上角的開發者模式打開

          接著將direct link文件夾直接拖到當前頁面即可看到插件成功安裝

          這里值得一提的是,上面manifest.json文件中在兩處地方配置了logo信息,上圖看到的插件圖標對應的是icon屬性,而action ->default_icon 對應的是下圖中的圖標顯示位置:

          編輯background.js

          上面有提到,background.js相當于程序主入口,內容如下:

          // 用戶首次安裝插件時執行一次,后面不會再重新執行。(除非用戶重新安裝插件)
          chrome.runtime.onInstalled.addListener(() => {
            // 插件功能安裝默認啟用  
            chrome.storage.sync.set({
              linkOpen: true,
            });
          });
          
          // 監聽tab頁面加載狀態,添加處理事件
          chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
            // 設置判斷條件,頁面加載完成才添加事件,否則會導致事件重復添加觸發多次
            if (changeInfo.status === "complete" && /^http/.test(tab.url)) {
              chrome.scripting
                .executeScript({
                  target: { tabId: tabId },
                  files: ["./content-script.js"],
                })
                .then(() => {
                  console.log("INJECTED SCRIPT SUCC.");
                })
                .catch((err) => console.log(err));
            }
          });

          上面的代碼邏輯比較簡單,插件安裝初始化時,在本地存儲一個變量linkOpen設為true,后面我們會新增一個選項切換是否啟用插件,需要用到這個變量判斷。

          接著在頁面初始化時,添加執行腳本代碼,這個腳本代碼叫content-script,里面執行我們功能代碼邏輯。

          編輯content-script.js

          在根目錄新建content-script.js,編輯內容如下:

          chrome.storage.sync.get("linkOpen", ({ linkOpen }) => {
            if (linkOpen) {
              document.body.addEventListener("click", function (event) {
                const target = event.target;
                // 判斷點擊的是否a標簽
                if (target.nodeName.toLocaleLowerCase() === "a") {
                  const href = target.getAttribute("href");
                  if (href.indexOf("://link") > -1) {
                    // 禁止默認的跳轉行為
                    event.preventDefault();
                    const link = href.split("target=")[1];
                    const url = decodeURIComponent(link);
                    // 處理完 a 標簽的內容,重新觸發跳轉,根據原來 a 標簽頁 target 來判斷是否需要新窗口打開
                    if (target.getAttribute("target") === "_blank") {
                      // 新窗口打開  
                      window.open(url);
                    } else {
                      // 當前窗口打開  
                      window.location.href = url;
                    }
                  }
                }
              });
            }
          });

          插件主邏輯如上,對應文章開頭提到的實現思路。

          添加是否啟用插件的功能開關

          在瀏覽器右上角插件點擊時,通常會顯示一個功能菜單,如下圖

          下面我們也添加一個類似的功能,用來是否啟用插件。

          新建popup.js和popup.html

          popup.html對應點擊時顯示的內容,popup.js則是相關執行邏輯。

          popup.html:

          <!DOCTYPE html>
          <html lang="en">
          <html>
          <meta charset="UTF-8">
          
          <head>
              <style>
                  .option{padding:30px 0;display:flex;align-items:center;justify-content:center;min-width:160px}.option .name{color:#333;font-size:18px;font-weight:bold}.switch{position:relative;display:inline-block;width:60px;height:34px}.switch input{opacity:0;width:0;height:0}.slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background-color:#ccc;-webkit-transition:.4s;transition:.4s}.slider:before{position:absolute;content:"";height:26px;width:26px;left:4px;bottom:4px;background-color:white;-webkit-transition:.4s;transition:.4s}input:checked+.slider{background-color:#45c7d8}input:focus+.slider{box-shadow:0 0 1px #45c7d8}input:checked+.slider:before{-webkit-transform:translateX(26px);-ms-transform:translateX(26px);transform:translateX(26px)}.slider.round{border-radius:34px}.slider.round:before{border-radius:50%}
              </style>
          </head>
          
          <body>
              <div class="option">
                  <span class="name">開啟:</span>
                  <label class="switch">
                      <input type="checkbox" id="switch">
                      <span class="slider round"></span>
                  </label>
              </div>
          
              <script src="popup.js"></script>
          </body>
          
          </html>

          顯示效果如下:

          popup.js

          const btn = document.querySelector("#switch");
          
          chrome.storage.sync.get("linkOpen", ({ linkOpen }) => {
            btn.checked = linkOpen;
          });
          
          btn.addEventListener("change", () => {
            if (btn.checked) {
              chrome.storage.sync.set({ linkOpen: true });
            } else {
              chrome.storage.sync.set({ linkOpen: false });
            }
            // 獲取當前tab窗口
            chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
              chrome.scripting.executeScript({
                target: { tabId: tabs[0].id },
                func: refreshPage,
              });
            });
          });
          
          // 刷新頁面
          function refreshPage() {
            window.location.reload();
          }

          上面的邏輯也很簡單,就是監聽swich按鈕,更新本地存儲變量,并且每次修改時刷新下頁面觸發content-script里面的邏輯

          至此,我們的一個插件就開發完成了。

          值得注意的事

          開發調試踩坑

          1. 每次修改代碼后,我們需要點擊如下圖的刷新按鈕,并且重新刷新頁面,否則可能出現代碼沒更新觸發的情況

          1. 如果出現報錯信息,更新代碼后重新點擊刷新按鈕,錯誤可能依然還在

          點擊“錯誤”按鈕

          然后點擊右上角的全部清除,再重新刷新即可

          1. background.js文件代碼中的console.log不會在控制臺顯示

          我們在background.js文件中添加的打印代碼是不會在瀏覽器的控制臺打印出來的,因為它有個單獨的控制臺顯示。入口如下圖:

          點擊service worker會出現一個單獨的調試窗口,background.js里面添加打印代碼會在這個窗口的控制臺中顯示打印信息。

          插件訪問頁面權限問題

          如果你有按照上面內容一步步實現的話,將鼠標移動到瀏覽器右上角插件圖標,你會發現如下圖所示:

          也就是說目前其實你的插件沒有訪問網站內容的權限,此時你需要手動點擊該插件圖標才能成功獲得訪問網站的權限。那要如何配置默認獲得訪問所有網站的權限呢?經過漫長的查找,發現是需要在manifest.json文件中添加這么一個屬性

          "host_permissions": ["https://*/*"]

          添加該屬性之后,右鍵點擊圖標,可以看到默認可讀取更改屬性是所有網站上

          發布到chrome應用商店

          發布插件到應用商店需要注冊開發者身份,如下圖所示

          額,需要5美元注冊費,本文結束。(感興趣的可以自己花錢注冊提交試試,哈哈)

          ntelliJ IDEA 是目前最好用的 JAVA 開發 IDE,它本身的功能已經非常強大了,但是每個人的需求不一樣,有些需求 IDEA 本身無法滿足,于是我們就需要自己開發插件來解決。工欲善其事,必先利其器,想要提高開發效率,我們可以借助 IDEA 提供的插件功能來滿足我們的需求。如果沒有我需要的功能怎么辦?很簡單,我們自己造一個!

          插件能做什么?

          IDEA 的插件幾乎可以做任何事情,因為它把 IDE 本身的能力都封裝好開放出來了。主要的插件功能包含以下四種:

          • 自定義語言支持:如果有 IDEA 暫時不支持的語言,你可以自己寫一個插件來支持,例如 Go 語言原來的支持就是通過插件做的,后來單獨做了一個 Goland。官方有自定義語言插件支持的教程。
          • 框架支持:例如Struts 2 的框架支持
          • 工具集成:可以給 IDEA 的自帶功能進行增強,例如對 Git 的操作增加 CodeReview 的功能。參考Gerrit
          • 用戶界面:自定義的插件改變用戶界面。參考BackgroundImage

          我為了減少重復代碼的編寫,寫了一個代碼生成的插件IDEA代碼生成插件CodeMaker,支持自定義代碼生成的模板。

          Hello world 插件

          依照慣例,我們從 Hello world 開始。

          新建一個 Gradle 的插件工程

          有些教程推薦用 IDEA 默認的插件工程來開始,但是我比較推薦用 Gradle 來管理整個插件工程,后面的依賴管理會很方便,否則都得靠手動管理。

          點擊新建工程,選擇 Gradle

          接下來填寫項目屬性

          配置 Gradle,用默認配置就行

          新建完工程之后,IDEA 會自動開始解析項目依賴,因為它要下載一個幾百兆的 SDK 依賴包,所以會比較久,打開科學上網能快一點。

          Gradle 依賴解析完成之后,項目結構如下圖,其中 plugin.xml 是插件的配置,build.gradle 是項目依賴的配置(類比 pom.xml)。

          下面就是默認生成的 plugin.xml

          <idea-plugin>
           <!--插件id-->
           <id>com.xiaokai.test.demo</id>
           <!--插件名稱-->
           <name>Demo</name>
           <!--開發者信息-->
           <vendor email="support@yourcompany.com" url="http://www.yourcompany.com">YourCompany</vendor>
           <!--插件說明-->
           <description><![CDATA[
           Enter short description for your plugin here.<br>
           <em>most HTML tags may be used</em>
           ]]></description>
           <!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html
           on how to target different products -->
           <!-- uncomment to enable plugin in all products
           <depends>com.intellij.modules.lang</depends>
           -->
           <!--依賴的其他插件能力-->
           <extensions defaultExtensionNs="com.intellij">
           <!-- Add your extensions here -->
           </extensions>
           <!--插件動作-->
           <actions>
           <!-- Add your actions here -->
           </actions>
          </idea-plugin>
          

          創建一個 Action

          Action 是 IDEA 中對事件響應的處理器,它的 actionPerformed 就像是 JS 中的 onClick 方法。可以看出來,插件的開發本質上跟 web、Android 的開發沒有什么不同,因為都是事件驅動的編程。

          我們可以直接使用 IDEA 提供的 Action 生成器

          點擊 OK 之后會在 src 生成類文件:

          package com.xiaokai.test;
          import com.intellij.openapi.actionSystem.AnAction;
          import com.intellij.openapi.actionSystem.AnActionEvent;
          public class HelloWorldAction extends AnAction {
           @Override
           public void actionPerformed(AnActionEvent e) {
           // TODO: insert action logic here
           }
          }
          

          同時,動作的信息也會注冊到 plugin.xml 中

           <!--插件動作-->
           <actions>
           <!-- Add your actions here -->
           <action id="demo.hello.world" class="com.xiaokai.test.HelloWorldAction" text="HelloWorld"
           description="Say Hello World">
           <add-to-group group-id="GenerateGroup" anchor="last"/>
           </action>
           </actions>
          

          彈出對話框

          創建完 Action 之后我們就要開始往里面寫邏輯了,既然是 Hello World 教學,那我們就來試一下最簡單的彈出對話框。

           @Override
           public void actionPerformed(AnActionEvent e) {
           //獲取當前在操作的工程上下文
           Project project = e.getData(PlatformDataKeys.PROJECT);
           //獲取當前操作的類文件
           PsiFile psiFile = e.getData(CommonDataKeys.PSI_FILE);
           //獲取當前類文件的路徑
           String classPath = psiFile.getVirtualFile().getPath();
           String title = "Hello World!";
           //顯示對話框
           Messages.showMessageDialog(project, classPath, title, Messages.getInformationIcon());
           }
          

          代碼寫完之后,打開 Gradle 的界面,點擊 runIde 就會啟動一個安裝了插件的 IDEA,然后就可以進行測試。你還可以右鍵啟動 Debug 模式,這樣還能進行斷點。

          運行的效果如下圖:

          可以看到,我們右鍵打開 Generate 菜單之后,里面最后一項就是我們添加的 Action,

          進階的教程

          如果想學習更多的原理和設計理念可以看IntelliJ Platform SDK的官方文檔。不過老實說,它的文檔寫的挺差的,基本上就是簡單講了一下概念和原理,沒有深入的分析。所以如果要深入研究還得靠自己。最靠譜的學習方式就是看別人寫的插件,舉個例子,你想知道怎么樣實現自動生成代碼,你就去找支持這個功能的插件,看他的源碼是怎么寫的。

          我當時寫CodeMaker的時候也是靠自己啃源碼之后寫出來的。下面我簡單介紹一下我用過的一些 API,這些 API 基本都沒有文檔說明,全靠代碼相傳。

          判斷當前光標選擇的元素是什么

           //獲取當前事件觸發時,光標所在的元素
           PsiElement psiElement = anActionEvent.getData(LangDataKeys.PSI_ELEMENT);
           //如果光標選擇的不是類,彈出對話框提醒
           if (psiElement == null || !(psiElement instanceof PsiClass)) {
           Messages.showMessageDialog(project, "Please focus on a class", "Generate Failed", null);
           return;
           }
          

          獲取當前類文件的所有類對象

          一個類文件中可能會有內部類,所以讀取的時候返回的是一個列表

           public static List<PsiClass> getClasses(PsiElement element) {
           List<PsiClass> elements = Lists.newArrayList();
           List<PsiClass> classElements = PsiTreeUtil.getChildrenOfTypeAsList(element, PsiClass.class);
           elements.addAll(classElements);
           for (PsiClass classElement : classElements) {
           //這里用了遞歸的方式獲取內部類
           elements.addAll(getClasses(classElement));
           }
           return elements;
           }
          

          格式化代碼

           public static void reformatJavaFile(PsiElement theElement) {
           CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(theElement.getProject());
           try {
           codeStyleManager.reformat(theElement);
           } catch (Exception e) {
           LOGGER.error("reformat code failed", e);
           }
           }
          

          使用粘貼板

           CopyPasteManager.getInstance()
           .setContents(new SimpleTransferable(table.toString(), DataFlavor.allHtmlFlavor));
          

          更多

          更多的技巧可以參考我的項目CodeMaker,以及其他的開源插件。

          作者:風馬蕭蕭


          主站蜘蛛池模板: 日韩欧美一区二区三区免费观看 | 无码国产精品一区二区免费式影视 | 国产乱码精品一区二区三区香蕉 | 中字幕一区二区三区乱码| 无码喷水一区二区浪潮AV| 久久se精品一区二区| 国产免费一区二区三区免费视频 | 无码8090精品久久一区| 日韩精品免费一区二区三区| 日韩高清国产一区在线| 国产一区二区不卡在线播放| 国产一区二区三区手机在线观看| 国产精品揄拍一区二区| 精品亚洲A∨无码一区二区三区| 免费日本一区二区| 久久国产一区二区| 亚洲一区二区三区高清| 午夜精品一区二区三区免费视频| 国产成人精品久久一区二区三区av | 一区二区三区高清视频在线观看| 色婷婷av一区二区三区仙踪林| 亚洲性色精品一区二区在线| 亚洲一区免费视频| 一区二区三区在线观看| 亚洲色精品三区二区一区| 亚洲av无码一区二区三区在线播放| 91精品国产一区| 亚洲乱码一区二区三区国产精品 | 波多野结衣一区视频在线| 久久久久人妻一区精品色| 97精品一区二区视频在线观看| 亚洲日本久久一区二区va| 无码人妻精品一区二区蜜桃 | 亚洲一区无码精品色| 亚洲片一区二区三区| 射精专区一区二区朝鲜| 亚洲欧美日韩中文字幕一区二区三区| 精品无码国产一区二区三区麻豆| 另类ts人妖一区二区三区| 一区二区三区视频免费观看| 夜夜添无码一区二区三区|