在IDEA里刷个掘金不过分吧!

24,745 阅读5分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

文章首发于我的个人博客:xeblog.cn/articles/10…

系列文章:

开源地址:github.com/anlingyi/xe…

前言

嘿嘿~ 如果你是第一次发现这个插件或是对这个插件感兴趣,可以去看看这篇文章了解下这个插件的经历😊:是的,我写了一个摸鱼插件!

前不久,插件新增了一个 浏览器 功能,现在可以直接在 IDEA 中浏览网页了。

image.png

这时有人就要说了:“是Chrome不好用,还是你真的有啥‘大毛病’?”

可能就是有啥毛病吧。

开始!

以防 API 不兼容,请使用 2021.2 及以上版本的 IDEA !

插件安装

添加插件库 Plugins > 设置按钮 > Manage Plugin Repositories...

http://plugins.xeblog.cn

搜索 “xechat” 安装

插件主界面

image.png

启动浏览器

使用命令 #open 1 开启浏览器

image.png

咱们来访问一下掘金社区:https://juejin.cn

右上边地址栏输入一下网站地址后回车

image.png

看看文章,学习一下“新技术”

image.png

逛逛沸点,提升一下“软技能”

image.png

使用说明

按键说明

:关闭浏览器

:跳到主页

:后退一页

:前进一页

:刷新当前页

调整窗口大小

S:小

M:中

UA设置

缩放

输入数值后回车。

负值缩小,正值放大。

浏览器实现原理

该浏览器是基于 IntelliJ SDK 内置的 JBCefBrowser 实现的,其核心是 JCEF,参考:plugins.jetbrains.com/docs/intell…

抽象

浏览器功能接口

浏览器基本功能的定义。

package cn.xeblog.plugin.tools.browser.core;

import java.awt.*;

/**
 * @author anlingyi
 * @date 2022/8/15 2:08 PM
 */
public interface BrowserService {

    /**
     * 获取浏览器UI组件
     *
     * @return
     */
    Component getUI();

    /**
     * 加载URL
     *
     * @param url
     */
    void loadURL(String url);

    /**
     * 后退
     */
    void goBack();

    /**
     * 前进
     */
    void goForward();

    /**
     * 重新加载当前页面
     */
    void reload();

    /**
     * 浏览器关闭
     */
    void close();

    /**
     * 设置用户代理
     *
     * @param userAgent
     */
    void setUserAgent(UserAgent userAgent);

    /**
     * 添加浏览器事件监听
     *
     * @param listener
     */
    void addEventListener(BrowserEventListener listener);

}

浏览器事件监听接口

目前只用到了两个事件:

  • 浏览器地址变更事件:在浏览器地址变更之后,用于获取变更之后的地址。
  • 浏览器关闭之前事件:在浏览器关闭之前释放一些资源。
package cn.xeblog.plugin.tools.browser.core;

/**
 * @author anlingyi
 * @date 2022/8/15 2:24 PM
 */
public interface BrowserEventListener {

    /**
     * 浏览器地址变更
     *
     * @param url
     */
    default void onAddressChange(String url) {
    }

    /**
     * 浏览器关闭之前
     */
    default void onBeforeClose() {
    }

}

UA定义

通过枚举类定义目前可支持的 UserAgent 设置

package cn.xeblog.plugin.tools.browser.core;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @author anlingyi
 * @date 2022/8/15 5:15 PM
 */
@AllArgsConstructor
public enum UserAgent {

    IPHONE("iPhone") {
        @Override
        public String getValue() {
            return "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1";
        }
    },
    ANDROID("Android") {
        @Override
        public String getValue() {
            return "Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Mobile Safari/537.36";
        }
    },
    IPAD("iPad") {
        @Override
        public String getValue() {
            return "Mozilla/5.0 (iPad; CPU OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87.0.4280.77 Mobile/15E148 Safari/604.1";
        }
    },
    WINDOWS("Windows") {
        @Override
        public String getValue() {
            return "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36";
        }
    },
    MACOS("MacOS") {
        @Override
        public String getValue() {
            return "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36";
        }
    },
    NATIVE("本机") {
        @Override
        public String getValue() {
            return null;
        }
    };

    @Getter
    private String name;

    public abstract String getValue();

    public static UserAgent getUserAgent(String name) {
        for (UserAgent userAgent : values()) {
            if (userAgent.getName().equals(name)) {
                return userAgent;
            }
        }
        return IPHONE;
    }

}

实现

浏览器功能实现

基本原理:先创建一个 JBCefBrowser 对象,通过这个对象可以获取到 CefBrowser 对象,浏览器大部分功能都是通过 CefBrowser 来完成的,再通过 CefBrowser 获取 CefClientCefClient 可用于监听一些事件,像是浏览器地址变更事件、浏览器关闭事件、浏览器请求事件(可设置UA)等。

package cn.xeblog.plugin.tools.browser.core;

import cn.hutool.core.util.StrUtil;
import com.intellij.ui.jcef.JBCefBrowser;
import org.cef.CefClient;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.handler.*;
import org.cef.misc.BoolRef;
import org.cef.network.CefRequest;

import java.awt.*;

/**
 * @author anlingyi
 * @date 2022/8/15 2:06 PM
 */
public class JcefBrowserService implements BrowserService {

    private final JBCefBrowser jbCefBrowser;

    private final CefBrowser cefBrowser;

    private final CefClient client;

    private UserAgent userAgent;

    private BrowserEventListener eventListener;

    public JcefBrowserService(String url) {
        this.jbCefBrowser = new JBCefBrowser(url);
        this.cefBrowser = this.jbCefBrowser.getCefBrowser();
        this.client = this.cefBrowser.getClient();
        this.userAgent = UserAgent.IPHONE;
        this.eventListener = new BrowserEventListener() {
        };
        initAddEvent();
    }

    private void initAddEvent() {
        this.client.removeRequestHandler();
        this.client.addRequestHandler(new CefRequestHandlerAdapter() {
            @Override
            public CefResourceRequestHandler getResourceRequestHandler(CefBrowser browser, CefFrame frame, CefRequest request, boolean isNavigation, boolean isDownload, String requestInitiator, BoolRef disableDefaultHandling) {
                return new CefResourceRequestHandlerAdapter() {
                    @Override
                    public boolean onBeforeResourceLoad(CefBrowser browser, CefFrame frame, CefRequest request) {
                        String ua = userAgent.getValue();
                        if (ua != null) {
                            request.setHeaderByName("User-Agent", ua, true);
                        }
                        return false;
                    }
                };
            }
        });

        this.client.addDisplayHandler(new CefDisplayHandlerAdapter() {
            @Override
            public void onAddressChange(CefBrowser browser, CefFrame frame, String url) {
                if (!StrUtil.startWith(url, "http")) {
                    return;
                }

                eventListener.onAddressChange(url);
            }
        });

        this.client.removeLifeSpanHandler();
        this.client.addLifeSpanHandler(new CefLifeSpanHandlerAdapter() {
            @Override
            public boolean onBeforePopup(CefBrowser browser, CefFrame frame, String target_url, String target_frame_name) {
                if (StrUtil.endWithAnyIgnoreCase(target_url, "jpg", "png", "gif", "svg", "pdf", "bmp", "webp")) {
                    return false;
                }

                loadURL(target_url);
                return true;
            }

            @Override
            public void onBeforeClose(CefBrowser browser) {
                close();
                eventListener.onBeforeClose();
            }
        });
    }

    @Override
    public Component getUI() {
        return this.jbCefBrowser.getComponent();
    }

    @Override
    public void loadURL(String url) {
        this.jbCefBrowser.loadURL(url);
    }

    @Override
    public void goBack() {
        if (this.cefBrowser.canGoBack()) {
            this.cefBrowser.goBack();
        }
    }

    @Override
    public void goForward() {
        if (this.cefBrowser.canGoForward()) {
            this.cefBrowser.goForward();
        }
    }

    @Override
    public void reload() {
        this.cefBrowser.reload();
    }

    @Override
    public void close() {
        this.client.dispose();
        this.jbCefBrowser.dispose();
    }

    @Override
    public void setUserAgent(UserAgent userAgent) {
        if (userAgent == null) {
            return;
        }

        this.userAgent = userAgent;
    }

    @Override
    public void addEventListener(BrowserEventListener listener) {
        if (listener == null) {
            return;
        }

        this.eventListener = listener;
    }

}

浏览器UI实现

界面这一块是通过 Swing 实现的,主要是浏览器的一些基本控制按钮。

package cn.xeblog.plugin.tools.browser.ui;

import cn.hutool.core.util.StrUtil;
import cn.xeblog.plugin.enums.Command;
import cn.xeblog.plugin.tools.browser.core.BrowserEventListener;
import cn.xeblog.plugin.tools.browser.core.BrowserService;
import cn.xeblog.plugin.tools.browser.core.JcefBrowserService;
import cn.xeblog.plugin.tools.browser.core.UserAgent;
import com.intellij.openapi.ui.ComboBox;
import lombok.Getter;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

/**
 * @author anlingyi
 * @date 2022/8/14 11:48 AM
 */
public class BrowserUI extends JPanel {

    private final static String HOME_PAGE = "https://cn.bing.com";

    private BrowserService browserService;

    private String lastUrl;

    private WindowMode windowMode;

    private UserAgent userAgent;

    private Component browserUI;

    private JTextField urlField;

    private enum WindowMode {
        SMALL("S", 200, 250),
        MEDIUM("M", 400, 300);

        @Getter
        String name;

        @Getter
        int width;

        @Getter
        int height;

        WindowMode(String name, int width, int height) {
            this.name = name;
            this.width = width;
            this.height = height;
        }

        public static WindowMode getMode(String name) {
            for (WindowMode mode : values()) {
                if (mode.getName().equals(name)) {
                    return mode;
                }
            }
            return WindowMode.SMALL;
        }
    }

    public BrowserUI() {
        this.windowMode = WindowMode.SMALL;
        this.userAgent = UserAgent.IPHONE;
        initPanel();
    }

    private void initPanel() {
        removeAll();

        String url = HOME_PAGE;
        if (lastUrl != null) {
            url = lastUrl;
        }

        if (this.browserService != null) {
            this.browserService.close();
        }
        this.browserService = new JcefBrowserService(url);
        this.browserService.setUserAgent(userAgent);

        browserUI = browserService.getUI();
        urlField = new JTextField(url);
        resize();

        Dimension buttonDimension = new Dimension(50, 25);
        Box hbox = Box.createHorizontalBox();

        JButton exitButton = new JButton("✕");
        exitButton.setToolTipText("退出");
        exitButton.setPreferredSize(buttonDimension);
        exitButton.addActionListener(l -> Command.OVER.exec());
        hbox.add(exitButton);

        JButton homeButton = new JButton("♨");
        homeButton.setToolTipText("主页");
        homeButton.setPreferredSize(buttonDimension);
        homeButton.addActionListener(l -> browserService.loadURL(HOME_PAGE));
        hbox.add(homeButton);

        JButton backButton = new JButton("←");
        backButton.setToolTipText("后退");
        backButton.setPreferredSize(buttonDimension);
        backButton.addActionListener(l -> browserService.goBack());
        hbox.add(backButton);

        JButton forwardButton = new JButton("→");
        forwardButton.setToolTipText("前进");
        forwardButton.setPreferredSize(buttonDimension);
        forwardButton.addActionListener(l -> browserService.goForward());
        hbox.add(forwardButton);

        JButton refreshButton = new JButton("⟳");
        refreshButton.setToolTipText("刷新");
        refreshButton.setPreferredSize(buttonDimension);
        refreshButton.addActionListener(l -> browserService.reload());
        hbox.add(refreshButton);
        hbox.add(urlField);

        JPanel urlPanel = new JPanel();
        urlPanel.add(hbox);

        Box h2Box = Box.createHorizontalBox();
        h2Box.add(new JLabel("Window:"));
        ComboBox windowModeBox = new ComboBox();
        windowModeBox.setPreferredSize(buttonDimension);
        for (WindowMode mode : WindowMode.values()) {
            windowModeBox.addItem(mode.getName());
        }
        windowModeBox.setSelectedItem(windowMode.getName());
        windowModeBox.addItemListener(l -> {
            windowMode = WindowMode.getMode(l.getItem().toString());
            resize();
            updateUI();
        });
        h2Box.add(windowModeBox);

        h2Box.add(Box.createHorizontalStrut(5));
        h2Box.add(new JLabel("UA:"));
        ComboBox uaBox = new ComboBox();
        uaBox.setPreferredSize(new Dimension(100, 30));
        for (UserAgent userAgent : UserAgent.values()) {
            uaBox.addItem(userAgent.getName());
        }
        uaBox.setSelectedItem(userAgent.getName());
        uaBox.addItemListener(l -> {
            userAgent = UserAgent.getUserAgent(l.getItem().toString());
            browserService.setUserAgent(userAgent);
            browserService.reload();
        });
        h2Box.add(uaBox);

        JPanel bottomPanel = new JPanel();
        bottomPanel.add(h2Box);

        setLayout(new BorderLayout());
        add(urlPanel, BorderLayout.NORTH);
        add(browserUI, BorderLayout.CENTER);
        add(bottomPanel, BorderLayout.SOUTH);
        add(Box.createHorizontalStrut(10), BorderLayout.EAST);

        updateUI();

        urlField.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                    String url = urlField.getText();
                    if (!StrUtil.startWithAny(url, "https://", "http://")) {
                        url = "https://" + url;
                    }
                    browserService.loadURL(url);
                }
            }
        });

        this.browserService.addEventListener(new BrowserEventListener() {
            @Override
            public void onAddressChange(String url) {
                lastUrl = url;
                urlField.setText(url);
            }

            @Override
            public void onBeforeClose() {
                SwingUtilities.invokeLater(() -> initPanel());
            }
        });
    }

    private void resize() {
        int width = this.windowMode.getWidth();
        int height = this.windowMode.getHeight();

        urlField.setPreferredSize(new Dimension(width * 2 / 3, 30));
        urlField.updateUI();

        Dimension browserDimension = new Dimension(width, height);
        browserUI.setMinimumSize(null);
        browserUI.setPreferredSize(null);
        if (windowMode == WindowMode.SMALL) {
            browserUI.setPreferredSize(browserDimension);
        } else {
            browserUI.setMinimumSize(browserDimension);
        }
    }

    public void close() {
        this.browserService.close();
    }

}

浏览器入口

package cn.xeblog.plugin.tools.browser;

import cn.xeblog.plugin.annotation.DoTool;
import cn.xeblog.plugin.tools.AbstractTool;
import cn.xeblog.plugin.tools.Tools;
import cn.xeblog.plugin.tools.browser.ui.BrowserUI;

import java.awt.*;

/**
 * @author anlingyi
 * @date 2022/8/14 11:12 AM
 */
@DoTool(Tools.BROWSER)
public class Browser extends AbstractTool {

    private BrowserUI browserUI;

    @Override
    protected void init() {
        this.browserUI = new BrowserUI();
        mainPanel.setLayout(new BorderLayout());
        mainPanel.add(this.browserUI, BorderLayout.CENTER);
    }

    @Override
    public void over() {
        super.over();
        if (browserUI != null) {
            this.browserUI.close();
        }
    }

}

完整代码:github.com/anlingyi/xe…