Very frequently, I find myself taking screenshots of code to share (e.g. via Slack or email) in order to preserve highlighting and syntax coloring.
Generally, I do this on macOS via Cmd+Shift+4 then space, to be able to draw a box around what I want to screenshot.
It would be useful if Sublime Text had a built-in way to take a screenshot of the current code (or only the currently selected code) with a few twiddly bits (include line numbers or not, show the filename or not, etc.)
I’m not aware of an easy way to do this via the Python API, and doing it the current way includes unnecessary line elements.
Currently, even with some fine-tuning of the window size, etc., various UI elements are included that don’t need to be.
The API can’t capture an image of the screen (though if you have some external application that can capture the current window, you could cause it to be executed).
It’s trivial to send the selected text (or the whole file) to a new tab in a new window, then disable the status bar, menu and tab bar so that you only see the file content and nothing else.
In that case, you could just select some text and hit a key and a new window would appear that you can size and capture as appropriate.
Carbon would be a wonderful service if it was available offline. I can’t upload my company’s code excerpts to arbitrary third-party services, unfortunately.
I’ve never written a plugin yet for Sublime Text, would you mind linking to the relevant documentation for taking a code excerpt, opening a new window, and disabling the minimap / tab bar / status bar?
Bonus points if we can fake the line numbers so they match the original line numbers.
This will toggle the sidebar, status bar, minimap, main menu and the tabs. Once you are done, press the same keybinding to come back to the original state. These all are also available as individual menu items in View menu of the main menu.
You can also achieve this by tweaking a few default settings in the User version Distraction Free.sublime-settings (which also then goes to full screen mode)
import sublime
import sublime_plugin
class ShareCodeCommand(sublime_plugin.WindowCommand):
def run(self):
# Get the currently active file in this window and also it's name.
view = self.window.active_view()
name = view.file_name() or view.name() or 'Untitled'
# Get the text we're interested in; either the contents of the first
# selection, or the whole file if the current selection is empty.
span = view.sel()[0] if view.sel()[0] else sublime.Region(0, len(view))
# Extend the ends of the spanned region to make sure that it encompasses
# whole lines, then grab the text
span = view.line(span)
code = view.substr(span)
# Get the first line of the spanned text and generate enough newlines to
# bump the output code to start at the correct line
first_line = view.rowcol(span.a)[0]
extra = "\n" * first_line
# Create a new window
sublime.run_command("new_window")
window = sublime.active_window()
# Create a scratch buffer and insert the text into it, using the same
# syntax as the source file. Also set the buffer name so it appears in
# the title bar.
view = window.new_file(syntax=view.settings().get("syntax"))
view.set_scratch(True)
view.set_name(name)
view.run_command("append", {"characters": extra})
view.run_command("append", {"characters": code})
# Set the visibile viewport position to the top line of the actual
# inserted code.
offs = first_line * view.line_height()
sublime.set_timeout(lambda: view.set_viewport_position((0.0, offs), False))
# Turn off undesirable UI elements
window.set_tabs_visible(False)
window.set_minimap_visible(False)
window.set_status_bar_visible(False)
window.set_menu_visible(False)
It implements a share_code command that you can bind to a key, put in a menu, etc. It will create a new window either with the currently selected text or the whole file, depending on the situation. If text is selected, it makes sure to grab full lines.
It then creates a new window, makes a scratch buffer in the window using the same syntax as the source file, and appends the content in, followed by turning off all of the UI elements leaving just the file content.
Unfortunately there’s no way to maintain the line numbers other than to duplicate the whole content of the file and then size the window as appropriate; plugins don’t have access to change line numbers and they always come from the physical constraints of the file.
You can tweak as desired (for example if you wanted the menu to appear, etc). If you’re unsure of how to apply the plugin, there are instructions in this video on how to use plugins.
There are downsides to this though. One would be that it has no effect on the size of a maximized window (if that matters). New windows tend to mimic the size of the window they were created from, so this can end you with it not doing anything (though that might be platform specific).
Also, the text metrics available to the API are in dip, but as far as I’m aware based on some quick testing the command above uses physical screen coordinates. So, you’d need some way to calculate that.
I edited the sample above to do both things; it inserts a number of newlines equal to the number of lines before the first part of the grabbed text, then sets the viewport of the new view so that the first line appears at the top of the window.
It also sets the name of the view to either the name of the current file, the text currently displayed in the tab (if the tab isn’t representing file content) or the text Untitled if you have an unsaved, untitled non-text buffer.
You can use the built in exec command to execute an arbitrary program if you’d like. For example:
It’s important to use self.window and not window (based on the sample above) because the exec command will make an output panel appear in the window that executes it if you have show_panel_on_build turned on.
However, this is probably not something you want to automate if you can’t get the window to size correctly first. Though maybe it’s handy to get something running to make the screenshot easier after the manual adjustment.
Is there a way to query the Sublime API to ask whether a given line is visible, or if the cursor is visible, or what the first / last visible line number are?
We might be able to get accurate, dynamic resizing that way.
While it’s an interesting exercise to think about this, why is pasting the snippet as text for your colleagues insufficient?
If your messaging app understands markdown, perhaps a better way to go about this is to have a command that wraps the selection in triple backticks and puts a markdown language identifier at the top triple backticks? and then puts it in the clipboard with sublime.set_clipboard so you can paste it immediately?
My use-case is pasting code snippets into Slack, which does not support syntax highlighting and can wrap at unfortunate places (e.g. in a thread on the right hand side).
Taking a screenshot ensures formatting, syntax highlighting, etc. are all maintained.
This is true! It’s just more clicks than I’d prefer vs having a screenshot magically pop up with the right window with the right dimensions, language is already known, and it’s easier to view a screenshot on mobile than a snippet.
I definitely need to make more use of snippets, but it can’t compete with the ease of three-backtick fenced code blocks, with the caveat of bad wrapping and no syntax highlighting. Either snippets or backticks, neither have line numbers that honor the originals.
Snippets in the Thread sidebar also don’t expand very wide, so you continue to run into the bad-line-wrapping behavior of Slack threads. Screenshots work and make the formatting, color, syntax, etc. all concrete and identical regardless of viewing environment (mobile vs desktop) and where they’re posted (main channel vs. thread).