什么选择Atom?
开源–遵循MIT协议,代码托管在github上面;
多平台支持–支持MAC/WIN/LINUX;
丰富的插件库和主题库;
类sublime–风格和sublime text极其相似,不管是风格还是操作上,快捷键上一些是通用的;
采用包管理技术–采用了node.js来访问文件系统和包管理;
强大的生命力–背靠github社区,这对于atom来说,可以注入源源不断的生命力。
优秀插件推荐
1.emmet
这个插件如果用过sublime的都知道的。Emmet的前身是大名鼎鼎的Zen coding,如果你从事Web前端开发的话,对该插件一定不会陌生。它使用仿CSS选择器的语法来生成代码,大大提高了HTML/CSS代码编写的速度。
2. minimap
这个插件用过sublime的同学肯定不会陌生的。代码地图。
3. linter
这货默认可以识别多门语言的错误,但是不细致,属于主插件,可以针对性的安装更细致的检查插件,下面是可能用到的:linter-php,linter-jshint,linter-scss-lint,linter-less,linter-csslint
4. file-icons
这个插件可以对不同类型的文件显示不同的文件图标,包含在目录树里面和标签页里面都会显示。
5. docblockr
这个插件可以帮助你快速的生成注释,很多高级的编辑器都有这个功能,Atom怎么能少呢?
6. autoclose-html
自动闭合html标签,很实用的功能,可以节省很多的时间。
7. highlight-line
高亮当前行,快速找到光标在什么地方。
8. highlight-selected
高亮当前所选文字,双击后全文这个词或者变量都会高亮哦
9. autocomplete-plus
完善自带autocomplete,可以安装更细化的插件:autocomplete-php、autocomplete-python、autocomplete-paths、autocomplete-html、autocomplete-bibtex、autocomplete-css等等。
10. color-picker
颜色拾取插件,前端工作者的利器。
11. simplified-chinese-menu
Atom 的简体中文汉化语言包,目前最全的汉化包。包含菜单汉化、右键菜单汉化以及设置汉化。
都说工欲善其事必先利其器,Python 的学习过程少不了 IDE 或者代码编辑器,或者集成的开发编辑器(IDE)。
这些 Python 开发工具帮助开发者加快使用 Python 开发的速度,提高效率。高效的代码编辑器或者 IDE 应该会提供插件,工具等能帮助开发者高效开发的特性。
这篇文章收集了一些对开发者非常有帮助的,最好的 10 款 Python IDE。
PyCharm 是 JetBrains 开发的 Python IDE。PyCharm用于一般IDE具备的功能,比如, 调试、语法高亮、Project管理、代码跳转、智能提示、自动完成、单元测试、版本控制……另外,PyCharm还提供了一些很好的功能用于Django开发,同时支持Google App Engine,更酷的是,PyCharm支持IronPython!
曾有人开玩笑说,如果生成一段随机字符串,有人回答给他一个Vim 编辑器就可以了。Vim 虽然使用门槛高,但是一旦你学会了,写代码的效率杠杠的,Vim 是高级文本编辑器,旨在提供实际的 Unix 编辑器’Vi’功能,支持更多更完善的特性集。Vim 不需要花费太多的学习时间,一旦你需要一个无缝的编程体验,那么就会把 Vim 集成到你的工作流中。这是黑客最喜欢的编辑器之一。
Eclipse 曾今是非常流行的 IDE,而且已经有了很久的历史。虽然现在慢慢地被 jetbrains 家族替代了,但是,瘦死的骆驼比马大,Eclipse with Pydev 允许开发者创建有用和交互式的 Web 应用。PyDev 是 Eclipse 开发 Python 的 IDE,支持 Python,Jython和 IronPython 的开发。
Sublime Text 是开发者中最流行的编辑器之一,多功能,支持多种语言,而且在开发者社区非常受欢迎。Sublime 有自己的包管理器,开发者可以使用TA来安装组件,插件和额外的样式,所有这些都能提升你的编码体验。虽然作为收费软件,良心的是你不需要付费也能正常使用,只不过是不是给你弹出一个购买的窗口,有钱了还是要支持一下正版软件。
Visual Studio Code (VSCode)为MS所开发的code editing tool,免费且开源,并支持Windows,Mac OS,Linux。VSCode像是精简版的Visual Studio,升级版的Sublime。VSCode由于其非常的轻量,因此使用过程中非常的流畅,对于用户不同的需要,可以自行下载需要的扩展(Extensions)来安装。
对于配置Python开发环境来说,相比于Sublime,配置起来更加容易。VSCode配置完后的环境是可以直接进行可视化的Debug,再也不用打各种print或者用pdb调试命令了,回归到Visual Studio里F10和F11。
Atom是由 GitHub 的程序员们打造的称为“属于21世纪”的代码编辑器。它开源免费跨平台(支持 Windows、Mac、Linux 三大桌面平台),并且整合 GIT 并提供类似 SublimeText 的包管理功能,作为一个现代的代码编辑器,Atom 支持各种编程语言的代码高亮(HTML / CSS / Javascript / PHP / Python / C / C++ / Objective C / Java / JSON / Perl / CoffeeScript / Go / Sass / YAML / Markdown 等等)、 与大多数其他编辑器相比,Atom的语言支持已经算是覆盖非常全面了。另外,它的代码补全功能(也叫Snippets) 也非常好用,你只需输入几个字符即可展开成各种常用代码,可以极大提高编程效率。
GNU Emacs 是可扩展,自定义的文本编辑器,甚至是更多的功能。Emacs 的核心是 Emacs Lisp 解析器,但是支持文本编辑。如果你已经使用过 Vim,可以尝试一下 Emacs。
Spyder (前身是 Pydee) 是一个强大的交互式 Python 语言开发环境,提供高级的代码编辑、交互测试、调试等特性,支持包括 Windows、Linux 和 OS X 系统。Spyder是Anaconda科学计算工具中默认的开发工具,做数据分析、搞机器学习的人喜欢这个工具
Thonny是基于python内置图形库tkinter开发出来的支持多平台(windows,Mac,Linux)的python IDE,支持语法着色、代码自动补全、debug等功能,如果你正在寻找一种“轻量级”的python IDE,那么可以试一试Thonny。
Wingware 的 Python IDE 兼容 Python 2.x 和 3.x,可以结合 Django, matplotlib, Zope, Plone, App Engine, PyQt, PySide, wxPython, PyGTK, Tkinter, mod_wsgi, pygame, Maya, MotionBuilder, NUKE, Blender 和其他 Python 框架使用。Wing 支持测试驱动开发,集成了单元测试,nose 和 Django 框架的执行和调试功能。Wing IDE 启动和运行的速度都非常快,支持 Windows, Linux, OS X 和 Python versi。
tom是一个著名的开源编辑器,是由Chris Wanstrath在2008年作为其个人的编外项目发展而来。据说在今年(2022)年底,这款编辑器也将进入关停状态。而且目前大部分程序员都把VS Code作为其最主要的开发工作,但是Atom本身的设计和代码实现都是非常优秀的,通过阅读它的源码,我们还是可以学到很多相关的编程技巧。
atom的代码结构非常清晰,整个项目可以分为两个部分,一个是atom本身的代码,另一个是atom的插件。atom本身的代码又可以分为两部分,一个是atom的核心业务逻辑,另一个是atom的UI代码。核心业务逻辑主要
是用来设置环境变量,调度窗口、调度系统资源等等。UI代码则主要负责处理atom的界面,比如菜单栏,工具栏,状态栏等等。
作为使用electron框架编写的应用程序,整体都是使用js来写的(早期是使用coffee来编写的),可以从其目录中看到,整个项目的目录结构如下:
|-src // 核心业务逻辑
|-|-main-process
|-|-|-atom-application.js
|-|-|-atom-environment.js
|-|-|-atom-window.js
|-static // UI代码
|-packages // 其它扩展包
...
众所周知,用Electron框架写成的应用,都可以分为主线程和渲染进程。对应到atom中,主线程的代码都是在src/main-process目录下,而渲染线程的代码则是直接src目录下。静态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;
// 下面省略大量代码,主要用于处理命令行参数,用来专门处理使用命令行打开atom的情况
// 真正的入口
const start = require(path.join(resourcePath, 'src', 'main-process', 'start'));
start(resourcePath, devResourcePath, startTime);
可以从上面代码看出,其实真正的处理入口还是在start函数中(src/main-process/start.js):
module.exports = function start(resourcePath, devResourcePath, startTime) {
// 处理错误情况
process.on('uncaughtException', function(error = {}) {
});
process.on('unhandledRejection', function(error = {}) {
});
// 初始化各种参数
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);
// 当应用关闭的时候,需要上报一些数据
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);
// 构造一个atomApplication对象
const AtomApplication = require(path.join(
args.resourcePath,
'src',
'main-process',
'atom-application'
));
// 并将之前的参数传入
AtomApplication.open(args);
});
};
从上面代码可以看出,前置处理也是各种参数的初始化,以及为了便于测试,做的一些定制处理。在应用初始化结束后,就会动态加载应用模块,构造 AtomApplication 实例。可以注意到,这里使用按需加载的目的是希望能够在需要的时候才会去加载对应的模块,这样可以减少内存的占用。
接着,我们来看一下atom-application.js的代码,这块代码量比较大,是整个atom的核心代码,我们先来看一下整体的结构:
// 是一个单列模式, 继承自`EventEmitter`模块,主要因为内部会大量应用事件处理机制来分发逻辑。
class AtomApplication extends EventEmitter {
static open(options) {
// 初始化一些参数
// 创建一个atomApplication对象
// 并将之前的参数传入
return new AtomApplication(options);
}
exit(status) {
app.exit(status);
}
constructor(options){}
async initialize(options) {}
}
程序启动的入口只有AtomApplication.open这一个方法,这个方法会创建一个AtomApplication对象,然后调用它的initialize方法,层层递进,再调用创建窗口、加载配置等方法,最终完成程序的启动。
其中,比较值得注意的是使用了一个叫做event-kit的模块。它是一个事件处理器模块,提供了一个事件处理器的抽象,可以让我们更容易地处理事件。最重要的作用是它实现了CompositeDisposable类,可以在需要的时候,释放资源。虽然javascript是一个有垃圾回收机制的语言,但是如果没有手动释放一些资源的话,会造成大量的内存占用。
在使用过程中,也十分简单
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);
// 在销毁的时候统一释放
this.disposable.dispose();
}
// 注册事件处理函数
handleEvents() {
// 省略其它代码,
// 在注册事件回调的时候,直接将对象添加到disposable的依赖中去
this.disposable.add(
ipcHelpers.on(app, 'before-quit', async event => {...})
);
}
}
atom作为一个编辑器,它的扩展机制是非常重要的。和其他的IDE类似,扩展机制也是使用的微内核模式(或者插件模式)来实现。微内核架构是一种十分常见的软件架构,它将应用系统分为两个部分:一个微内核和一组外部的插件。微内核负责管理插件,提供插件之间的通信机制,以及提供一些基础的服务。插件则负责提供具体的功能。这样的架构可以让我们更容易地扩展软件的功能,而不需要修改软件的核心代码。
在atom中,插件主要是通过package类来实现的。package是atom扩展的基本单元,它可以包含一些功能,比如语法高亮、代码提示、代码格式化等等,也提供了让第三方开发者扩展的能力。那么这些扩展是如何加载的呢?我们先来看一下package-manager.js的代码:
// 可以加载、激活、停用、卸载包
// 加载包读取并解析包的元数据和资源,例如快捷键、菜单、样式表等
// 激活包注册加载的资源并调用包的主模块的`activate()`方法
// 停用包取消注册包的资源并调用包的主模块的`deactivate()`方法
// 卸载包从包管理器中完全移除
// 可以通过`core.disabledPackages`配置项和调用`enablePackage()/disablePackage()`方法来启用/禁用包
class PackageManager {
preloadPackage(packageName, pack) {
...
}
loadPackages() {
...
}
enablePackage(packageName) {
...
}
// 触发事件,用来注册回调
onDidActivatePackage(callback) {
}
}
这个包管理器类PackageManager,可以管理扩展包的整个生命周期,主要负责包的加载、卸载、更新等操作。而所有的包都绑定在主内核的atom.packages这个全局变量上,我们可以通过这个变量来访问应用上加载的所有扩展。
那么packageManager是如何负责管理包的安装和卸载呢?:
class PackageManager {
constructor(packages) {
this.packages = packages;
}
getPackages() {
return this.packages;
}
getPackage(name) {
return this.packages.find(pkg => pkg.name === name);
}
// 禁用包,从内存中将包去除,然后通知应用程序或者扩展来执行禁用操作
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中,就可以在监听到did-deactivate-package事件后,执行一些清理操作:
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();
// 此处省略大量的细节
}
preload() {
// do something
}
load() {
// do something
}
unload() {
// do something
}
activate() {
// do something
}
deactivate() {
// do something
}
finishLoading() {
// do something
}
}
Package(扩展)实例本身要和主应用进行通信,atom是直接通过全局对象的方式进行调用的,这样做的好处是不用考虑通信的问题,但是也有一些弊端,比如不方便重构等。
在应用入口处,会将PackageManager实例挂载在应用实例上。后续我们可以通过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内部,当窗口初始化完成后,会正式调用`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的扩展机制还是比较简单的,在各种扩展的生命周期中,都可以通过事件来进行通信,从而实现各种功能。这样一种实现,其实也可以在我们日常工作过程中加以借鉴。
*请认真填写需求信息,我们会在24小时内与您取得联系。