itHub剛剛升級了Atom文本編輯器,添加了原生C++緩沖區(qū)并重寫了DOM交互層。GitHub稱Atom下一版本的更新將會改善Git集成和PHP支持。
Atom 1.19版本中,原生C ++文本緩沖區(qū)提高了響應速度和內(nèi)存使用率。GitHub Ian Olsen表示:“現(xiàn)在保存文件現(xiàn)在是異步發(fā)生,且不會造成UI阻塞,可以順利的從一個文件移動到下一個文件。此外,大文件的內(nèi)存消耗也很小。
DOM交互層被重寫,以提高性能并簡化代碼。重寫層利用了新的瀏覽器功能和虛擬DOM功能。重寫也是為了適應包含用于限制瀏覽器的樣式和布局范圍的CSS限制邊界的API,當元素內(nèi)容發(fā)生變化時,及時通知它們改變大小。
Atom是用HTML,JavaScript和Node.js構(gòu)建的,可用于Windows,MacOS和Linux,運行在GitHub’s Electron cross-platform framework,用于構(gòu)建桌面應用程序。即將推出的Atom是1.20版,現(xiàn)在處于beta階段。為了改進版本1.20中的Git集成,GitHub對diff視圖進行了重新設計,以支持待處理的窗格和多個視圖。
Atom 1.20還具有PHP語法修復功能。為了提高查找和替換能力,1.20版本中的上下文行可選顯示“查找項目”結(jié)果。用戶可以在軟件包設置中設置匹配之前和之后的可用行數(shù),并可在查看結(jié)果時內(nèi)聯(lián)修改顯示。
tom是一個著名的開源編輯器,是由Chris Wanstrath在2008年作為其個人的編外項目發(fā)展而來。據(jù)說在今年(2022)年底,這款編輯器也將進入關(guān)停狀態(tài)。而且目前大部分程序員都把VS Code作為其最主要的開發(fā)工作,但是Atom本身的設計和代碼實現(xiàn)都是非常優(yōu)秀的,通過閱讀它的源碼,我們還是可以學到很多相關(guān)的編程技巧。
atom的代碼結(jié)構(gòu)非常清晰,整個項目可以分為兩個部分,一個是atom本身的代碼,另一個是atom的插件。atom本身的代碼又可以分為兩部分,一個是atom的核心業(yè)務邏輯,另一個是atom的UI代碼。核心業(yè)務邏輯主要
是用來設置環(huán)境變量,調(diào)度窗口、調(diào)度系統(tǒng)資源等等。UI代碼則主要負責處理atom的界面,比如菜單欄,工具欄,狀態(tài)欄等等。
作為使用electron框架編寫的應用程序,整體都是使用js來寫的(早期是使用coffee來編寫的),可以從其目錄中看到,整個項目的目錄結(jié)構(gòu)如下:
|-src // 核心業(yè)務邏輯
|-|-main-process
|-|-|-atom-application.js
|-|-|-atom-environment.js
|-|-|-atom-window.js
|-static // UI代碼
|-packages // 其它擴展包
...
眾所周知,用Electron框架寫成的應用,都可以分為主線程和渲染進程。對應到atom中,主線程的代碼都是在src/main-process目錄下,而渲染線程的代碼則是直接src目錄下。靜態(tài)UI資源則在static目錄下。
我們先從主線程的入口代碼開始看起,代碼位于src/main-process/main.js路徑下:
// 命令行工具入口,
const args = yargs(process.argv)
// Don't handle --help or --version here; they will be handled later.
.help(false)
.version(false)
.alias('d', 'dev')
.alias('t', 'test')
.alias('r', 'resource-path').argv;
// 下面省略大量代碼,主要用于處理命令行參數(shù),用來專門處理使用命令行打開atom的情況
// 真正的入口
const start = require(path.join(resourcePath, 'src', 'main-process', 'start'));
start(resourcePath, devResourcePath, startTime);
可以從上面代碼看出,其實真正的處理入口還是在start函數(shù)中(src/main-process/start.js):
module.exports = function start(resourcePath, devResourcePath, startTime) {
// 處理錯誤情況
process.on('uncaughtException', function(error = {}) {
});
process.on('unhandledRejection', function(error = {}) {
});
// 初始化各種參數(shù)
app.commandLine.appendSwitch('enable-experimental-web-platform-features');
const args = parseCommandLine(process.argv.slice(1));
const previousConsoleLog = console.log;
console.log = nslog;
args.resourcePath = normalizeDriveLetterName(resourcePath);
args.devResourcePath = normalizeDriveLetterName(devResourcePath);
atomPaths.setAtomHome(app.getPath('home'));
atomPaths.setUserData(app);
const config = getConfig();
const colorProfile = config.get('core.colorProfile');
if (colorProfile && colorProfile !== 'default') {
app.commandLine.appendSwitch('force-color-profile', colorProfile);
}
if (handleStartupEventWithSquirrel()) {
return;
} else if (args.test && args.mainProcess) {
// 處理測試情況
app.setPath(
'userData',
temp.mkdirSync('atom-user-data-dir-for-main-process-tests')
);
console.log = previousConsoleLog;
app.on('ready', function() {
const testRunner = require(path.join(
args.resourcePath,
'spec/main-process/mocha-test-runner'
));
testRunner(args.pathsToOpen);
});
return;
}
const releaseChannel = getReleaseChannel(app.getVersion());
let appUserModelId = 'com.squirrel.atom.' + process.arch;
if (releaseChannel !== 'stable') {
appUserModelId += `-${releaseChannel}`;
}
// 這個方法可以防止win10在任務欄中顯示重復的atom圖標
app.setAppUserModelId(appUserModelId);
app.on('open-file', addPathToOpen);
app.on('open-url', addUrlToOpen);
// 當應用關(guān)閉的時候,需要上報一些數(shù)據(jù)
app.on('will-finish-launching', () =>
startCrashReporter({
uploadToServer: config.get('core.telemetryConsent') === 'limited',
releaseChannel
})
);
if (args.userDataDir != null) {
app.setPath('userData', args.userDataDir);
} else if (args.test || args.benchmark || args.benchmarkTest) {
app.setPath('userData', temp.mkdirSync('atom-test-data'));
}
app.on('ready', function() {
app.removeListener('open-file', addPathToOpen);
app.removeListener('open-url', addUrlToOpen);
// 構(gòu)造一個atomApplication對象
const AtomApplication = require(path.join(
args.resourcePath,
'src',
'main-process',
'atom-application'
));
// 并將之前的參數(shù)傳入
AtomApplication.open(args);
});
};
從上面代碼可以看出,前置處理也是各種參數(shù)的初始化,以及為了便于測試,做的一些定制處理。在應用初始化結(jié)束后,就會動態(tài)加載應用模塊,構(gòu)造 AtomApplication 實例??梢宰⒁獾?,這里使用按需加載的目的是希望能夠在需要的時候才會去加載對應的模塊,這樣可以減少內(nèi)存的占用。
接著,我們來看一下atom-application.js的代碼,這塊代碼量比較大,是整個atom的核心代碼,我們先來看一下整體的結(jié)構(gòu):
// 是一個單列模式, 繼承自`EventEmitter`模塊,主要因為內(nèi)部會大量應用事件處理機制來分發(fā)邏輯。
class AtomApplication extends EventEmitter {
static open(options) {
// 初始化一些參數(shù)
// 創(chuàng)建一個atomApplication對象
// 并將之前的參數(shù)傳入
return new AtomApplication(options);
}
exit(status) {
app.exit(status);
}
constructor(options){}
async initialize(options) {}
}
程序啟動的入口只有AtomApplication.open這一個方法,這個方法會創(chuàng)建一個AtomApplication對象,然后調(diào)用它的initialize方法,層層遞進,再調(diào)用創(chuàng)建窗口、加載配置等方法,最終完成程序的啟動。
其中,比較值得注意的是使用了一個叫做event-kit的模塊。它是一個事件處理器模塊,提供了一個事件處理器的抽象,可以讓我們更容易地處理事件。最重要的作用是它實現(xiàn)了CompositeDisposable類,可以在需要的時候,釋放資源。雖然javascript是一個有垃圾回收機制的語言,但是如果沒有手動釋放一些資源的話,會造成大量的內(nèi)存占用。
在使用過程中,也十分簡單
class AtomApplication extends EventEmitter {
// 省略其它代碼
constructor(options) {
// 省略其它代碼
this.disposable = new CompositeDisposable();
}
async destroy() {
const windowsClosePromises = this.getAllWindows().map(window => {
window.close();
return window.closedPromise;
});
await Promise.all(windowsClosePromises);
// 在銷毀的時候統(tǒng)一釋放
this.disposable.dispose();
}
// 注冊事件處理函數(shù)
handleEvents() {
// 省略其它代碼,
// 在注冊事件回調(diào)的時候,直接將對象添加到disposable的依賴中去
this.disposable.add(
ipcHelpers.on(app, 'before-quit', async event => {...})
);
}
}
atom作為一個編輯器,它的擴展機制是非常重要的。和其他的IDE類似,擴展機制也是使用的微內(nèi)核模式(或者插件模式)來實現(xiàn)。微內(nèi)核架構(gòu)是一種十分常見的軟件架構(gòu),它將應用系統(tǒng)分為兩個部分:一個微內(nèi)核和一組外部的插件。微內(nèi)核負責管理插件,提供插件之間的通信機制,以及提供一些基礎的服務。插件則負責提供具體的功能。這樣的架構(gòu)可以讓我們更容易地擴展軟件的功能,而不需要修改軟件的核心代碼。
在atom中,插件主要是通過package類來實現(xiàn)的。package是atom擴展的基本單元,它可以包含一些功能,比如語法高亮、代碼提示、代碼格式化等等,也提供了讓第三方開發(fā)者擴展的能力。那么這些擴展是如何加載的呢?我們先來看一下package-manager.js的代碼:
// 可以加載、激活、停用、卸載包
// 加載包讀取并解析包的元數(shù)據(jù)和資源,例如快捷鍵、菜單、樣式表等
// 激活包注冊加載的資源并調(diào)用包的主模塊的`activate()`方法
// 停用包取消注冊包的資源并調(diào)用包的主模塊的`deactivate()`方法
// 卸載包從包管理器中完全移除
// 可以通過`core.disabledPackages`配置項和調(diào)用`enablePackage()/disablePackage()`方法來啟用/禁用包
class PackageManager {
preloadPackage(packageName, pack) {
...
}
loadPackages() {
...
}
enablePackage(packageName) {
...
}
// 觸發(fā)事件,用來注冊回調(diào)
onDidActivatePackage(callback) {
}
}
這個包管理器類PackageManager,可以管理擴展包的整個生命周期,主要負責包的加載、卸載、更新等操作。而所有的包都綁定在主內(nèi)核的atom.packages這個全局變量上,我們可以通過這個變量來訪問應用上加載的所有擴展。
那么packageManager是如何負責管理包的安裝和卸載呢?:
class PackageManager {
constructor(packages) {
this.packages = packages;
}
getPackages() {
return this.packages;
}
getPackage(name) {
return this.packages.find(pkg => pkg.name === name);
}
// 禁用包,從內(nèi)存中將包去除,然后通知應用程序或者擴展來執(zhí)行禁用操作
async deactivatePackage(name, suppressSerialization) {
const pack = this.getLoadedPackage(name);
if (pack == null) {
return;
}
if (!suppressSerialization && this.isPackageActive(pack.name)) {
this.serializePackage(pack);
}
const deactivationResult = pack.deactivate();
if (deactivationResult && typeof deactivationResult.then === 'function') {
await deactivationResult;
}
delete this.activePackages[pack.name];
delete this.activatingPackages[pack.name];
this.emitter.emit('did-deactivate-package', pack);
}
}
比如在擴展ui-watcher中,就可以在監(jiān)聽到did-deactivate-package事件后,執(zhí)行一些清理操作:
watchForPackageChanges() {
this.subscriptions.add(
atom.packages.onDidDeactivatePackage(pack => {
// This only handles packages - onDidChangeActiveThemes handles themes
const watcher = this.watchedPackages.get(pack.name);
if (watcher) watcher.destroy();
this.watchedPackages.delete(pack.name);
})
);
}
Package類,則包含了包的基礎信息,包括鍵位設置、配置、樣式等,并且有完整的生命周期。
class Package {
constructor(params) {
this.config = params.config;
this.packageManager = params.packageManager;
this.styleManager = params.styleManager;
this.commandRegistry = params.commandRegistry;
this.keymapManager = params.keymapManager;
this.notificationManager = params.notificationManager;
this.grammarRegistry = params.grammarRegistry;
this.themeManager = params.themeManager;
this.menuManager = params.menuManager;
this.contextMenuManager = params.contextMenuManager;
this.deserializerManager = params.deserializerManager;
this.viewRegistry = params.viewRegistry;
this.emitter = new Emitter();
// 此處省略大量的細節(jié)
}
preload() {
// do something
}
load() {
// do something
}
unload() {
// do something
}
activate() {
// do something
}
deactivate() {
// do something
}
finishLoading() {
// do something
}
}
Package(擴展)實例本身要和主應用進行通信,atom是直接通過全局對象的方式進行調(diào)用的,這樣做的好處是不用考慮通信的問題,但是也有一些弊端,比如不方便重構(gòu)等。
在應用入口處,會將PackageManager實例掛載在應用實例上。后續(xù)我們可以通過atom.packages來訪問包管理器實例,從而獲取包的信息。
// atom-application.js
this.packages = new PackageManager({
... // 一堆的配置
});
this.packages.initialize(...);
而在渲染進程中,可以通過在window上掛載atom對象來訪問包管理器實例,從而獲取所有擴展包的信息,進行預加載操作。
// initialize-application-window.js
/ 初始化 AtomEnvironment
global.atom = new AtomEnvironment({
clipboard,
applicationDelegate: new ApplicationDelegate(),
enablePersistence: true
});
TextEditor.setScheduler(global.atom.views);
// 初始化應用窗口
global.atom.preloadPackages();
// ... 省略大量代碼
module.exports = function({ blobStore }) {
// 省略大量代碼
// 在startEditorWindows內(nèi)部,當窗口初始化完成后,會正式調(diào)用`loadPackages`方法來加載所有的擴展包
return global.atom.startEditorWindow().then(function() {
// Workaround for focus getting cleared upon window creation
const windowFocused = function() {
window.removeEventListener('focus', windowFocused);
setTimeout(() => document.querySelector('atom-workspace').focus(), 0);
};
window.addEventListener('focus', windowFocused);
ipcRenderer.on('environment', (event, env) => updateProcessEnv(env));
});
}
總體而言,atom的擴展機制還是比較簡單的,在各種擴展的生命周期中,都可以通過事件來進行通信,從而實現(xiàn)各種功能。這樣一種實現(xiàn),其實也可以在我們?nèi)粘9ぷ鬟^程中加以借鑒。
篇教程將會教你怎么制作你的第一個 Atom 文本編輯器的插件。我們將會制作一個山寨版的 Sourcerer,這是一個從 StackOverflow 查詢并使用代碼片段的插件。到教程結(jié)束時,你將會制作好一個將編程問題(用英語描述的)轉(zhuǎn)換成獲取自 StackOverflow 的代碼片段的插件,像這樣: |
準備
教程須知
Atom 文本編輯器是用 web 技術(shù)創(chuàng)造出來的。我們將完全使用 JavaScript 的 EcmaScript 6 規(guī)范來制作插件。你需要熟悉以下內(nèi)容:
教程的倉庫
你可以跟著教程一步一步走,或者看看放在 GitHub 上的倉庫,這里有插件的源代碼。這個倉庫的歷史提交記錄包含了這里每一個標題。
開始
安裝 Atom
根據(jù) Atom 官網(wǎng) 的說明來下載 Atom。我們同時還要安裝上 apm(Atom 包管理器的命令行工具)。你可以打開 Atom 并在應用菜單中導航到 Atom > Install Shell Commands 來安裝。打開你的命令行終端,運行 apm -v 來檢查 apm 是否已經(jīng)正確安裝好,安裝成功的話打印出來的工具版本和相關(guān)環(huán)境信息應該是像這樣的:
apm -v
> apm 1.9.2
> npm 2.13.3
> node 0.10.40
> python 2.7.10
> git 2.7.4
生成骨架代碼
讓我們使用 Atom 提供的一個實用工具創(chuàng)建一個新的 package(軟件包)來開始這篇教程。
如果你在側(cè)邊欄沒有看到軟件包的文件,依次按下 Cmd+K Cmd+B(MacOS)或者 Ctrl+K Ctrl+B(Windows/Linux)。
命令面板可以讓你通過模糊搜索來找到并運行軟件包。這是一個執(zhí)行命令比較方便的途徑,你不用去找導航菜單,也不用刻意去記快捷鍵。我們將會在整篇教程中使用這個方法。
運行骨架代碼包
在開始編程前讓我們來試用一下這個骨架代碼包。我們首先需要重啟 Atom,這樣它才可以識別我們新增的軟件包。再次打開命令面板,執(zhí)行 Window: Reload 命令。
重新加載當前窗口以確保 Atom 執(zhí)行的是我們最新的源代碼。每當需要測試我們對軟件包的改動的時候,就需要運行這條命令。
通過導航到編輯器菜單的 Packages > sourcefetch > Toggle 或者在命令面板執(zhí)行 sourcefetch:toggle 來運行軟件包的 toggle 命令。你應該會看到屏幕的頂部出現(xiàn)了一個小黑窗。再次運行這條命令就可以隱藏它。
“toggle”命令
打開 lib/sourcefetch.js,這個文件包含有軟件包的邏輯和 toggle 命令的定義。
toggle() {
console.log('Sourcefetch was toggled!');
return (
this.modalPanel.isVisible() ?
this.modalPanel.hide() :
this.modalPanel.show()
);
}
toggle 是這個模塊導出的一個函數(shù)。根據(jù)模態(tài)面板的可見性,它通過一個三目運算符 來調(diào)用 show 和 hide 方法。modalPanel 是 Panel(一個由 Atom API 提供的 UI 元素) 的一個實例。我們需要在 export default 內(nèi)部聲明 modalPanel 才可以讓我們通過一個實例變量 this 來訪問它。
this.subscriptions.add(atom.commands.add('atom-workspace', {
'sourcefetch:toggle': () => this.toggle()
}));
上面的語句讓 Atom 在用戶運行 sourcefetch:toggle 的時候執(zhí)行 toggle 方法。我們指定了一個 匿名函數(shù) () => this.toggle(),每次執(zhí)行這條命令的時候都會執(zhí)行這個函數(shù)。這是事件驅(qū)動編程(一種常用的 JavaScript 模式)的一個范例。
Atom 命令
命令只是用戶觸發(fā)事件時使用的一些字符串標識符,它定義在軟件包的命名空間內(nèi)。我們已經(jīng)用過的命令有:
軟件包對應到命令,以執(zhí)行代碼來響應事件。
進行你的第一次代碼更改
讓我們來進行第一次代碼更改——我們將通過改變 toggle 函數(shù)來實現(xiàn)逆轉(zhuǎn)用戶選中文本的功能。
改變 “toggle” 函數(shù)
如下更改 toggle 函數(shù)。
toggle() {
let editor
if (editor = atom.workspace.getActiveTextEditor()) {
let selection = editor.getSelectedText()
let reversed = selection.split('').reverse().join('')
editor.insertText(reversed)
}
}
測試你的改動
更新后的命令將會改變選中文本的順序:
在 sourcefetch 教程倉庫 查看這一步的全部代碼更改。
原文來自:https://linux.cn:443/article-7865-1.html
本文地址:https://www.linuxprobe.com/first-atom-editor.html編輯員:蘇西云,審核員:逄增寶
本文原創(chuàng)地址:https://www.linuxprobe.com/first-atom-editor.html
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。