Sublime Text basic plugins

Published
20 November 2019
Tagged

The hard drive on my laptop recently ate itself, and while I was able to recover most of my important data through the magic of regular backup, I did lose a few files. Among them: my directory of custom SublimeText plugins. It'd been a good while since I had to make a Sublime Text plugin, so this was a good refresher course.

While I've built a few plugins over the course of time, the two main ones I miss were:

  • A plugin to name, save, and open a new file, and
  • A plugin to delete the current file.

(For what it's worth, my key resource for building Sublime Text plugins is the official manual, which is surprisingly verbose.)

Making a new file

Sublime Text will happily create new files for you. But then you have to save them - opening up the save window dialogue and making it a whole thing. The point of this plugin is that you type in where you want to save your file (with autocomplete!) and Sublime Text will create the file for you, save it as an empty file, and then open it for you to edit. No save dialogue required.

We build this as a Window Plugin - we're not acting on the text in a given file, and we have no reason to save changes to the edit stack. Here we go, for the bare-bones plugin:

import os
import sublime
import sublime_plugin


class JyrNewFileCommand(sublime_plugin.WindowCommand):
	def create_file(self, str):
		root_dir = self.window.folders()[0]

		save_path = root_dir + "/" + str

		open(save_path, "a").close()
		self.window.open_file(save_path)

	def show_input_panel(self, str):
		self.window.show_input_panel("New file name:", str, self.create_file, None, None)

	def run(self):
		self.show_input_panel("")

Nice and easy. We create this command as a WindowCommand, and set it up to ask the user for the name of the new file. Once the user hits "enter", Sublime Text will create the file at the given path (using the path to the first open folder in the sidebar as a "working directory") and open it for you.

The only thing of note here: window.show_input_panel() takes a couple of additional callbacks, for when the input panel's contents change (ie. when the user enters new content), and for when it's cancelled. We don't care about the cancel callback, but that "on change" callback allows us to build in some kind of fancy autocomplete for folders:

import os
import sublime
import sublime_plugin


class JyrNewFileCommand(sublime_plugin.WindowCommand):
  def auto_complete_path(self, path):
    root_dir = self.window.folders()[0]
    fragment_dir = os.path.dirname(path)
    fragment_base = os.path.basename(path)

    files = os.listdir(root_dir + fragment_dir)
    
    matching_files = [mf for mf in files if mf.startswith(fragment_base) and os.path.isdir(root_dir + fragment_dir + "/" + mf)]

    if (len(matching_files) == 1):
      matching_file = matching_files[0] + "/"

      self.show_input_panel(matching_file)
    else:
      self.show_input_panel(path)

  def on_input_change(self, str):
    if str.endswith("\t"):
      self.auto_complete_path(str[:-1])

  def create_file(self, str):
    root_dir = self.window.folders()[0]

    save_path = root_dir + "/" + str

    open(save_path, "a").close()
    self.window.open_file(save_path)

  def show_input_panel(self, str):
    self.window.show_input_panel("New file name:", str, self.create_file, self.on_input_change, None)

  def run(self):
    self.show_input_panel("") 

This advanced-level new file command now has a couple of tricks up its sleeve. This assumes you're typing in the path to the new file, and navigating a set of already-existing folders. In this case, once you've typed enough letters for Sublime Text to identify which folder you're talking about, you can hit tab and it will auto-complete for you.

There's a fun trick here: it looks like we're modifying the existing input panel, but we're actually just killing the old panel and creating a new one, in record time.

Deleting an old file

What about getting rid of the current file? It turns out Sublime Text doesn't have a nice command for doing this inside of the app, but python will do it for you. However, we can't just delete the file - this will leave the remnants of the file open in Sublime Text. Instead, we need to both delete the file and close the current view.

import os
import sublime
import sublime_plugin


class JyrDeleteFileCommand(sublime_plugin.WindowCommand):
  def run(self):
    active_view = self.window.active_view()
    file_name = active_view.file_name()
    active_view.close()
    
    os.remove(file_name)