Sublime Forum

Feature: Take a screenshot of current code, optionally include line numbers, optionally only the selected code

#1

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.

Ideally, the output would automatically hide the tab bar and status bar, so that you get something more akin to:

Is there sufficient support in the Python API to achieve this?

0 Likes

#2

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.

1 Like

#3

https://packagecontrol.io/packages/Carbon maybe. It just uses the API from https://carbon.now.sh/ . Or just use any screenshot software…

1 Like

#4

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.

Is there a way to run Carbon locally?

0 Likes

#5

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.

0 Likes

#6

seems yes https://github.com/carbon-app/carbon/issues/976
but I don’t read through and try it.

0 Likes

#7

If you have the Chain of Command plugin installed, you can write a simple key binding that will toggle all of that.

{
		"keys": ["ctrl+alt+shift+3"],
		"command": "chain",
		"args": {
			"commands": [
				["toggle_side_bar"],
				["toggle_tabs"],
				["toggle_status_bar"],
				["toggle_menu"],
				["toggle_minimap"]
			]
		},
	},

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)

3 Likes

#8

The API documentation is available at: http://www.sublimetext.com/docs/api_reference.html.

That said, here’s a plugin that does this:

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.

1 Like

#9

Would be cool if it then also took a screenshot of that window using the OS’s default screen grabber (e.g. screenshot.app on macOS).

1 Like

#10

@rwols Having a nice screenshot of the code is the ultimate goal here.

@OdatNurd Is there a way to grow / shrink the new window to be X lines high?

I think I should be able to “fix” line numbers by inserting enough newlines before the selected text.

Also, is there a way to set the contents of the title bar via API? (to have the correct filename)

0 Likes

#11

There’s a resize_window command; you can try running this in the Sublime console, for example:

window.run_command("resize_window", {"width": 1280, "height": 720})

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:

self.window.run_command('exec', {"shell_cmd": "take_screenshot.sh"})

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.

2 Likes

#12

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.

0 Likes

#13

for macOS, a function like this will suffice to take a screenshot:


def take_screenshot_osx(window, filename):
    homedir = os.path.expanduser("~")
    destination = os.path.join(homedir, "Desktop", filename)
    subprocess.check_call(("/usr/sbin/screencapture", "-w", destination))
    subprocess.check_call(("/usr/bin/open", destination))

EDIT: You have to give Sublime Text permission to record your screen though :slight_smile:

1 Like

#14

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?

1 Like

#15

If it’s email, consider using the ExportHtml plugin and send an HTML email?

0 Likes

#16

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.

0 Likes

#17

Then just use this :man_shrugging:


import sublime_plugin
import subprocess


class ShareCodeCommand(sublime_plugin.ApplicationCommand):
    def run(self):
        subprocess.check_call(("/usr/sbin/screencapture", "-i", "-c"))

Running this will allow you to select a region. It will copy the image to your clipboard so you can paste it in Slack or an email.

0 Likes

#18

Slack has highlighting for some languages if you make a snippet.

3 Likes

#19

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).

0 Likes