https://ohdoylerules.com/workflows/why-i-still-like-sublime-text-in-2025/
Why I still like Sublime Text in 2025
Go home
January 27, 2025
I still get people asking me why I use Sublime Text in 2025 given
there are soooo many other great editors out there.
My response: there is? Because I still think Sublime Text holds up as
a great editor.
Table Of Contents
* It fast
* LSP
* Snippets
* Project workspaces
* Build systems
* Multiple cursors
* Key/mouse bindings
* Included niceness
* Wish List
I started with Sublime Text 2 back in 2010/2011 while I was in
college. I mainly started using it because it was free,
cross-platform, and came as a "portable app" that I could put on a
USB and just use.
Back then, I had a really basic Toshiba laptop that dual booted
Windows XP and Ubuntu (or maybe it was Mint?) so it was nice that it
worked on both. I really liked how snappy it was compared to the
tools our teacher suggested using. At that time it was Dreamweaver
and maybe Notepad++.
Sublime, at that time, was pretty novel. It clearly took a lot of
inspirations from TextMate, another classic editor, considering that
one is 4 years older than Sublime. It had multiple cursors, plugins,
a build system. But the biggest claim to fame for Sublime, was the
"command palette". I'm sure there is some other older app that had a
precursor or similar feature to it, but generally speaking, it seems
like that user experience pattern really kicked off with Sublime.
I built my web-dev chops on Sublime. The shortcuts are ingrained in
my bones at this point. I'm not some key-combo-king, but I know a lot
of the shortcuts that can help me get the UI and commands I need
without thinking much at all.
I have been, and continue to be, a Sublime user of about 15
years.
So take all this with that in mind. I have been, and continue to be,
a Sublime user of about 15 years.
So why do I keep using Sublime?
If you thought Sublime was dead, well you couldn't be more wrong! The
latest build of Sublime as of this post is "4192" and was released
20th January 2025. So basically a week ago from this post. Not too
bad.
It has regularly been updated with minor tweaks and fixes about a
dozen times a year. I think the last major upgrade would be when
Package Control (the plugin installer/manager) bumped to the next
version which allowed plugins to install external dependencies.
You can nitpick here and say that Package Control is not part of
Sublime. But most people won't use Sublime without it. So I am going
to take some liberties and say it is part of it.
I think the thing to consider is how Sublime is basically "done"
software. It has been around a long time. It was first released
around 2008. It just passed it's 17-year anniversary actually.
Congrats to them!
Before I dive into the details of why I still use it, consider this:
if you are using a modern GUI-driven editor, it probably has taken
inspiration from Sublime. So why not check out one of the OGs? You
might find something you like.
Without further adieu, my reasons for still using Sublime in 2025:
It fast
Sublime is fast. It starts instantly. Uses very few resources.
Handles large files gracefully. Rarely crashes.
Nothing else to add here. A+ performance.
LSP
Sublime LSP is really doing a lot for Sublime to keep it feeling
modern and keeping up with other tools in the same class.
If you aren't aware of what an LSP is, this isn't the post to learn
about it. But the gist is, it handles all that fancy code-aware
completion and hover info you like from VS Code. If you want to learn
more give TJ 5 minutes to learn you.
Some of the cool things about the Sublime LSP:
Multiple servers per file
You can enable as many LSP servers per file that you want. Restart
them individually and configure them on a per-project basis (more on
that later) which really helps bolster the capabilities of this
already great editor.
Detection on a scope level
When configuring an LSP, outside of one installed with a plugin, you
tell the LSP plugin which "scope" (think of this as an id for a
syntax) to enable an LSP on.
Want your LSP to only turn on if you only open a file with a specific
syntax? No problem. Want it to turn on only if a type of syntax is
detected? Like a specific flavour of CSS? Sure. It is very
configurable.
Extensible configuration
I know VS Code is the LSP king, which the tech originating with that
editor, but I haven't seen the ability to just add an LSP installed
as a binary in on your usr/local/bin.
There are a few "cutting-edge" LSPs that are installed via Cargo that
are usually only targeting Neovim, but can easily be configured in
Sublime with a simple JSON object.
Here is an example of configuring md-lsp (Markdown language server
with support for GitHub flavored Markdown) in a few lines:
// in the "LSP Settings" file, under "clients[]"
"md-lsp": {
// default enabled?
"enabled": true,
"command": [
// as long as the system path is setup right, we can find this binary
"md-lsp",
],
// when we see this scope, the LSP will start this server
"selector": "text.html.markdown",
},
It should be noted that md-lsp does not have a Sublime LSP plugin nor
any mention of Sublime in their README. They only mention support for
Helix and Neovim. Well, guess what? You support Sublime too!
Snippets
I write a lot of snippets. Right now, my snippets folder in Sublime
has 123 snippets. The latest one was added today. It was a "TODO"
snippet for Blade.
Sublime lets you create snippets from the Tools > Developer > New
Snippet dropdown. They get sent to your "User" folder in the Sublime
directory and are sourced on startup.
Scope based
Snippets are also scope-based. VS Code has scopes too, so there is
nothing new here. I wonder where they got that from?
A quick note on scopes: they can be very vague (like source.txt,
targeting a whole .txt file) or super specific (like
text.html.basic.liquid text.html.basic meta.object.liquid which
targets a nested object in a liquid template) based on what you need.
I have found the Sublime scope integration to be straightforward to
understand. A syntax defines scopes, and you can target those scopes
in snippets, keybindings, and macros. More on those other two later.
I'm sure I didn't get the details perfect. But it doesn't really
matter given the point I'm going to make: not all snippet systems
work this way.
Specifically, I have found Helix, Neovim, and Zed snippets to be more
based around "filetype" and not the scope of the where you are in the
"syntax".
I'm sure this will change. Or perhaps I've missed something. From
what I can see on the surface, snippets based on syntax-specific
scopes seem to be the default in VS Code and Sublime.
Tab stops with nesting, placeholders, and references
Here is the snippet I made today:
$0
]]>
Blade todo comment
todo
text.html.blade
Pretty simple. People who have written snippets before will recognize
the syntax. The $1 is where your caret is sent first, you can then
type, then hit tab, and you get sent to $0.
Here is a TODO snippet I have for "javascript" files:
todo
Insert a TODO JS comment
source.js, source.ts, source.jsx meta.function.js meta.block.js meta.group.js meta.jsx.js meta.interpolation.js, source.tsx meta.function.js meta.block.js meta.group.js meta.jsx.js meta.interpolation.js
Here you can see that the first tab stop has default content of "this
is my todo". Here you can see a more complex scope setup that only
expands this snippet under those conditions. Nothing really
spectacular here.
But snippets in Sublime also support some transformations...
Transformations (Vue component)
Here is a much more complicated snippet:
${1/([a-zA-Z]+)(?:(\s+?)|\b)/\L\1(?2:\-)\E/g}>
${2:Here is a description of my web component.}
@element ${3:div}
@fires change - This jsdoc tag makes it possible to document events.
@fires submit
@prop {String} title - You can use this jsdoc tag to document properties.
@slot - This is an unnamed slot (the default slot)
@slot start - This is a slot named "start".
@slot end
-->
<script setup lang="ts">
import type { HTMLAttributes } from 'vue';
export interface Props extends /* @vue-ignore */ HTMLAttributes {
${4:title?: string;${5}}
}
withDefaults(defineProps<Props>(), {});
<template>
<${3:div} :key="${1/(\w+)(\W*)/\L\1\E(?2:\-)/g}" class="${1/(\w+)(\W*)/\L\1\E(?2:\-)/g}wrapper">
${6:}
${3:div}>
]]>
sfc
text.html.vue
Vue single-file component template
This snippet has some transformations in it. This means we can
actually format what the content in the different tab stops will be.
I won't harp on what is going on too much. Just know that I can
format the content in the "Usage" comment as UpperCamelCase and I can
format the content in :key to be lower-snake-case. VS Code can do
this. The syntax for it is a bit nicer. But I prefer authoring
snippets in XML rather than JSON.
Obviously, I'm twisted.
Project workspaces
Sublime supports the concept of workspaces under the banner of a
"project". All without a plugin, by the way. You can open a folder
and save that folder as a project.
This creates an empty your-project-name.sublime-project which you can
really save wherever you like in your project as it has some features
to target where the root of the project is.
This file is just a JSON file and contains editor settings that you
are overriding for that specific project. You can target global
settings, set rules for specific folders, create a build system,
tweak/toggle LSP settings, etc. etc.
This is a lot like the .vscode/settings.json file from what I
understand. I also believe you can do this in Vim with a .vim folder
in the root of your project with a feature called "exrc". I haven't
used it personally, so I can't speak to it much.
In my brief flirtations with Neovim and Helix, you need a plugin for
this. In Zed, they also have a settings file that can be saved into a
project root to get the same thing.
Including/excluding files and folders
I think all the editors I referenced above can do this. Not much to
share. It is just nice to have an array of file configurations for a
project that may or not be in that directory, can be matched with a
glob, or just listed explicitly.
Here is an example of how I would use the project file in a Next.js
site:
{
"folders": [
{
"file_exclude_patterns": [
".gitkeep",
"*.min.*",
"*.snap",
"*.lock",
"*lock.json"
],
"folder_exclude_patterns": [
".sanity",
".netlify",
".next",
".vercel",
".cache",
"out",
"dist",
"node_modules"
],
"path": "."
}
],
"build_systems": [
{
"name": "Project - Build",
"working_dir": "$project_path",
"shell_cmd": "pnpm run build"
},
{
"name": "Project - Test",
"working_dir": "$project_path",
"shell_cmd": "pnpm run test"
},
{
"name": "Project - Open Test",
"working_dir": "$project_path",
"shell_cmd": "find tests -print | grep $file_base_name.spec | sed -n 1p | xargs subl
},
{
"name": "Project - Test Snapshots",
"working_dir": "$project_path",
"shell_cmd": "pnpm run test:snapshots"
},
{
"name": "Project - Test File",
"working_dir": "$project_path",
"shell_cmd": "pnpm run test -- $file_base_name.spec"
},
{
"name": "Project - Test File Snapshot",
"working_dir": "$project_path",
"shell_cmd": "pnpm run test:snapshots -- $file_base_name.spec"
},
{
"name": "Project - Format README.md",
"working_dir": "$project_path",
"shell_cmd": "mdformat $file"
}
],
"settings": {
"match_brackets_angle": true,
"tab_size": 2,
"translate_tabs_to_spaces": true,
"jsdocs_return_tag": "@return",
"lsp_format_on_save": false,
"lsp_code_actions_on_save": {
"source.organizeImports": false,
"source.fixAll.eslint": true
},
"LSP": {
"formatters": {
"source.ts": "LSP-biome",
"source.js": "LSP-biome",
"source.tsx": "LSP-biome",
"source.jsx": "LSP-biome",
"text.html.basic": "LSP-biome"
},
"LSP-eslint": {
"enabled": false
},
"LSP-biome": {
"enabled": true
}
}
}
}
I'm doing a lot here. Setting files to ignore, folders to exclude
from indexing, setting build commands that use pnpm as well as
mdformat, setting from editor setting for tab spacing, and finally a
few LSP tweaks that make sense for a Next project.
VS Code can do all of what I've listed here. But they split it up
into different files. Kinda annoying.
Configure plugin settings per project
You can also configure plugin settings per project. Here is how I
would add project settings for the "syntax override" plugin. This
plugin forces the editor to use a specific syntax for certain files
that match a given pattern:
{
// everything is the same as above but this is added on the end
"syntax_override": {
"\\.env.*$": [
"ShellScript",
"Shell-Unix-Generic"
],
"\\.*rc$": [
"JSON",
"JSON"
],
"\\.ts.snap$": [
"JavaScript",
"TypeScript"
],
"\\.css$": [
"Tailwind CSS",
"Tailwind CSS"
]
}
}
I don't always want all .css files to be highlighted with the
Tailwind syntax. But in this project I do. So I can set it locally
here, and when I open a .css file in this project, it will switch the
syntax for me. Nice!
Add build systems per project
You can see in the example above that I have set build systems on
this specific project. You can do this in VS Code with "tasks". Zed
has this feature as well and calls it "tasks" too. I just find it
annoying that they are in their own files. I guess that makes them
more portable.
I just like my project configuration in one central place. I must be
nuts.
Build systems
I've touched on build systems a bit already. But in summation, they
are just tasks you can run in your project.
Sometimes they call a global command (like curl), sometimes a local
dependency is installed with a package manager (think some binary in
node_modules/.bin/), or maybe just runs a command already setup in
your other tools (like composer run test or php artisan
migrate:fresh) that run in the root of the project but need some
context.
Can also be provided by a plugin
A plugin can provide build systems. The neat thing is they are just
Sublime files. JSON that ends in .sublime-build. Like snippets. So
they are really portable too. Just like the other editors with their
tasks.json file.
Just a simple file
Here is one I saved in my "User" directory called
dot-env-linter.sublime-build:
{
"cmd": ["dotenv-linter", "$file"],
"selector": "source.shell",
"file_patterns": ["\\.env.*$"],
"file_regex": "^(.*?)\\:(\\d+)(\\s)(.*+)"
}
You can see there are some special variables (like $file) that will
expand based on context. In this case, that one is the full path to
the currently active file.
Here is my phpmd.sublime-build with a bit more flavour:
{
"cmd": ["phpmd", "${file}", "text", "codesize,unusedcode,naming"],
"path": "${PATH}",
"working_dir": "$project_path",
"selector": "source.php"
}
You can see some more vars here for the project path as well as a
reference to my system PATH. Of course, we got a nice lil scope as
well.
Build systems with
You can actually write build systems in Python as well. So if you
need something more complicated, you can reach for that.
You don't even need to make any semblance of a plugin. You can toss a
.py file in your "User" directory and implement a class that takes a
sublime_plugin.WindowCommand.
Multiple cursors
Yep. Multiple cursors. I use them all the time. I know that the "vim"
way is to start recording a macro, apply the changes on a single
line, and then replay that macro on all the lines you want to change.
Or do some s//g fu for a fancy find and replace. I get it. I just
don't like it.
Most of the editors these days have multiple cursors. Including some
terminal editors like Helix. I have tried Helix and I think it is a
lot closer to what I would want from a modern editor than my previous
terminal editor of Neovim + LazyVim.
Key/mouse bindings
The key and mouse bindings are what you would expect from a modern
editor. It is basically the same as VS Code. Nothing exhilarating
here. But I do like the way conceptual key bindings are handled.
Contextual key bindings
Like any good editor, Sublime supports contextual key bindings.
I think the best usage I have seen for contextual key bindings is in
the JavaScript language support package that comes with Sublime.
When you have an active selection, and that selection is not empty,
and you are inside a string-like scope, then ` will wrap that
selection with `. Basically, it will wrap your selection as a
template string. Handy!
Just a simple file
Like the build systems and snippets, key bindings are just saved in a
file that ends with .sublime-keymap. They can be in your "User"
directory or in a plugin. Unfortunately, unlike build systems, they
cannot be saved on a per-project basis - from what I can tell.
You can also have key bindings for different platforms. They are
named as follows:
Default (Windows).sublime-keymap
Default (OSX).sublime-keymap
Default (Linux).sublime-keymap
Included niceness
These are just some notable mentions of things I like:
Python all the way down
Given how Python is probably the most popular language, at least the
last time GitHub checked, I'm surprised this isn't the go-to editor.
I don't think you need to know python to use Sublime but it helps if
you want to craft a nice plugin.
One more thing to add around authoring plugins, they are super
simple. It is just a .py file in a folder. No build system, external
dependencies, or ever-changing APIs to navigate.
You can see my recent fork of the Scratchpad plugin and how the whole
thing is mostly powered by a single python file.
This plugin has been trucking along for 11 years. I can't imagine any
VS Code plugin lasting that long!
Macros
Yep you can record macros in Sublime. You can also save them to... a
file! Then put that file (.sublime-macro) in a plugin, or, of course,
your "User" folder. The macro is just an array of key presses. They
also support scopes and can be bound to a key combo. Pretty sweet.
This is one feature that VS Code has not stolen - I mean implemented
- and that requires an additional plugin to have. Of course the
Vimmers have had this for decades.
Diff hunks (revert or show)
Sublime supports viewing inline diff hunks. Handy for when you don't
want to dive into the diff of a file. You can just ask to see the
diff hunk for that line or group of lines. You can also revert just
as easily.
Case conversion and line permute functions
There are some handy case conversion functions that are built in.
Nice with multiple cursors. VS Code has a couple of conversion
choices. Sublime has 8 different case conversions built in.
Package control and repo URLs for packages
Package control packages can be installed from the central
repository. But you can also install them from a git repo URL. You
can also clone a repo to your "Packages" folder, and it will work
too. No marketplace or anything like that is required.
This is really handy if you have forked a package and just want to
install your fork.
All the config and settings are plain files
Since the whole of a Sublime setup is mostly plain files, that makes
it really easy to sync your setup across multiple computers. I
actually symlink my Sublime folder to Dropbox. So any changes I make
will be shared across all my computers that use it.
This isn't unique to Sublime. I think it is just a benefit of having
tools that use plain text-driven configuration.
Distraction-free mode
Sublime has a "distraction free mode" which will full-screen your
editor and focus the content to the middle of the screen. I am using
it to write this post right now!
Wish List
Of course there could be a few things that could be better.
Better docs
I do find the docs for developing plugins to be sparse. There are doc
sites. There are two big ones. One for the "official" docs that
document the APIs. There is also another site that is tagged as the
"unofficial" docs.
Usually when I want to know how to do something in a plugin, I just
read the source of another plugin that does something I want to
emulate. It isn't a bad way to learn, it is just a bit tedious.
Plugin development DX
Speaking of building plugins, there does not seem to be any "stubs"
for the Sublime python API. I am by no means a python guru. I only
use it in Sublime, and I usually forget everything once I finish what
I am trying to do. But I think there could be some "plugin starter
template" that could include a Pyright setup and some basic guide for
how to get going.
I also find that getting plugins on the Package Control site to be
quite a chore. You need to open a PR to a repo and put your repo into
a list based on where it goes into an alphabet. It doesn't allow
forks and plugins that are too similar. There is also no way to mark
a package as "abandoned".
I like the way Composer/Packagist does packages. You create a repo,
submit that URL to the Packagist site, and it will automatically keep
track of it for you. You release a new version by just using git tags
and releases.
NPM is also a bit nicer. But forcing an NPM account and having to
juggle the mixing of git tags and the package.json "version" key to
be a bit unclear at times.
Key/Mouse bindings per project
Simple one here. It would be nice to have key and mouse bindings on a
project level. I don't know how often I would really use it, but it
would be nice to have just for those projects that have tedious tasks
or macros that I want to run under specific scopes.
In Summation
I like Sublime. I think it is still incredibly capable in 2025. If
you are in search of something a little snappier, classier, and not
riddled with AI slop, then give it a try.
I doubt I can pry your Vim from your HJKL riddled right hand, but if
you have been let down or uninspired by the latest offerings when it
comes to editors, you might find Sublime still has a lot to offer.
Note: if any of my information is wrong or outdated, I will update it
accordingly
Go home
---------------------------------------------------------------------
James Doyle Profile Picture
James Doyle
I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and
winner of a Canadian Developer 30 under 30 award. I'm a huge Open
Source advocate and contributor to a lot of projects in my community.
When I am not sitting at a computer, I'm trying to perfect some other
skill.
---------------------------------------------------------------------
Please enable JavaScript to view the comments powered by Disqus.
comments powered by Disqus