Sublime Forum

How to overwrite the content of an untitled view

#1

I’d like to know how can i overwrite the content of an existing unsaved view, for instance, if i use:

def open_file(window, title, text):
    window.run_command("open_file",{"file": title, "contents": text})

And i call it multiple times:

open_file(sublime.active_window(), “Untitled”, “text1”)
open_file(sublime.active_window(), “Untitled”, “text2”)
open_file(sublime.active_window(), “Untitled”, “text3”)

The view’s content will remain as the specified by the first call, i.e. text1

Question is, how can i overwrite the content of the unsaved view each time? (i.e: in the above example after the 3 calls the view would have the text from the last call, text3)

0 Likes

#2

The open_file command (and the window.open_file() API call) both create a new view on each invocation, as you’ve noticed. I don’t think Sublime has an analog for opening a file into an already existing view out of the box.

As such, if you want to replace the contents of a view that already exists with another file, I believe your plugin would need to manually load the new file content itself, then tell the view to replace it’s contents with the data that you loaded, or close the view and then open a new one.

That said, it looks like your sample code is using the open_file command as a shortcut for creating a new temporary view and putting some data into it. If that is the case, then you need to track the view after you’ve created it and then just replace it’s contents as needed.

Either way, the following contrived code is an example of tracking and re-using a view. Every time you invoke the example command, it will either create a new temporary view or re-use the previously created view, and swap the content and title to something new so you can tell it’s working.

As written this always uses whatever the active window is, so if you invoke the command in a window that doesn’t have a temporary view while another window does, you end up with a new one specific to the window. If that’s not what you want, you need to modify the _find_views() example code to scan all views in all windows instead of all views in the current window.

import sublime
import sublime_plugin

import random
import string


def _find_view(window=None):
    """
    Check the current window for our temporary view.
    """
    window = sublime.active_window() if window is None else window

    for view in window.views():
        if view.settings().get("_temp_view", False):
            return view


def _temp_view(title, text, window=None, view=None):
    """
    Set the tab title and content of the provided view. If no view is provided,
    a new one is created first.
    """
    window = sublime.active_window() if window is None else window

    if view is None:
        view = window.new_file()
        view.settings().set("_temp_view", True)
    else:
        view.sel().clear()
        view.sel().add(sublime.Region(0, view.size()))
        view.run_command("left_delete")

    view.set_name(title)
    view.run_command("append", {"characters": text})


class ExampleCommand(sublime_plugin.ApplicationCommand):
    """
    Contrived example of creating or re-using a view.
    """
    count = 0

    def run(self):
        self.count += 1
        msg = "".join([random.choice(string.ascii_letters) for i in range(32)])

        _temp_view("Untitled %d" % self.count, "Text: %s" % msg,
                   view=_find_view())
0 Likes

#3

@OdatNurd Terence, thanks for the provided example, as usual, you’re a lifesaver :wink:

Here’s one which contains few modifications froms yours, as I didn’t understand why you were using the view settings:

def find_views(window, key=""):
    if isinstance(key, str):
        key = lambda view: view.name() == key

    window = sublime.active_window() if window is None else window
    return [view for view in window.views() if key(view)]


def update_views(views, update):
    for view in views:
        update(view)

class ExampleCommand(sublime_plugin.WindowCommand):

    def run(self, **kwargs):
        prefix = "untitled"
        title = prefix + " {}"
        content = "Text: {}".format(
            "".join([random.choice(string.ascii_letters) for i in range(32)])
        )
        views = find_views(
            self.window,
            lambda view: view.name().startswith(prefix)
        )

        if not views:
            view = self.window.new_file()
            view.set_name(title.format("-1"))
            views.append(view)

        def callback(view):
            view.set_name(title.format(int(view.name().split()[1]) + 1))
            view.sel().clear()
            view.sel().add(sublime.Region(0, view.size()))
            view.run_command("left_delete")
            view.run_command("append", {"characters": content})
        update_views(views, callback)   

Btw, when you create a new untitled view in sublime, these ones don’t have the name “untitled”… I guess the only way to identify them would be considering if not view.name() ?

0 Likes

#4

The idea of the using the view setting is just to flag the view with something specific that you can later find so you can identify the view that’s temporary. Primarily that’s because if you keep a reference to the actual view object when it’s first created, you also need to have an event listener to be able to tell when it’s going away. Otherwise if the user closes the view, your code will stop updating the view without your being able to tell.

In OverrideAudit I use the name of the tab as you’re doing here to locate the particular view, since there’s a few of them. In some cases that’s done in combination with some specific view settings that are applied that indicate what the view is actually being used for as well.

In general, a newly created empty view will report a name that’s the empty string (view.name() == '') until you save the file for the first time and it gets a name associated with it.

An exception to that is that when the syntax is set to Plain Text (as it is by default), the name of the tab gets set to the first line of text in the file, which updates as the text is being modified. Once this happens, if you set the syntax to something else, the next buffer modification will revert the name back to the empty string. You can see the full logic for that in Default/set_unsaved_view_name.py.

In cases where you create a new tab and immediately set the syntax to something else before typing, the name will remain as the empty string until the file is saved.

So in general, if you’re creating temporary views and you want to re-use them, unless you’re specifically giving them a name, you probably want to go the setting route so that you don’t accidentally destroy someone’s unsaved work by picking the wrong view and clobbering the contents.

0 Likes

#5

In general, a newly created empty view will report a name that’s the empty string (view.name() == ‘’) until you save the file for the first time and it gets a name associated with it.

An exception to that is that when the syntax is set to Plain Text (as it is by default), the name of the tab gets set to the first line of text in the file, which updates as the text is being modified. Once this happens, if you set the syntax to something else, the next buffer modification will revert the name back to the empty string. You can see the full logic for that in Default/set_unsaved_view_name.py.

In cases where you create a new tab and immediately set the syntax to something else before typing, the name will remain as the empty string until the file is saved.

So in general, if you’re creating temporary views and you want to re-use them, unless you’re specifically giving them a name, you probably want to go the setting route so that you don’t accidentally destroy someone’s unsaved work by picking the wrong view and clobbering the contents.

Mmm, you sure? then maybe the ST devs changed this behaviour in ST at some point? Which version you’re using over there? Asking you this cos few hours ago when I had created my own modified version I had thought exactly what you’re commenting above about clobbering the contents of someon’s unsaved work, which was definitely possible at the moment you allow the user to find views by using something as powerful as callback(view)… but then I had realized that wasn’t possible unless they specifically set the name programatically. Please take a look to this test to see what I mean. Using ST build 3131 over here on win7.

0 Likes

#6

Ah yeah, that’s my bad on that first paragraph; I blended file_name() and name() together in my head there. Let’s try that one again. I think in fact name and file_name are mutually exclusive to some degree, although I’ve never explored it in detail.

If you create a new buffer and the syntax is Plain Text, as you modify the buffer the plugin I mentioned above in the Default package will update the name to be the first part of the text as the buffer is modified, which is also reflected in the tab title.

If you change the syntax after this has happened to something else, the name property gets reset back to the empty string on the next buffer modification. However, the tab name remains the same until you focus away from the tab and back again a couple of times (which I believe is a bug in Sublime).

If your buffer has a name set (even if you manually did it), saving the file resets name back to the empty string, and attempts to change it again silently do nothing after that.

As such, as long as you’re looking for an explicit name that you set, you can be fairly sure that the view that you found is the one that you’re looking for, unless you’re using something generic enough that some other plugin might have set the same name or the user might have created an empty text file and entered your name as the first line.

Similarly, when the name is empty you can see if the file_name is set to anything, and if so you can probably deduce that the buffer is a file that exists on the disk and so it’s not a good idea to fiddle with the content. However with no name or file_name there’s no way to tell what view is what without inspecting the contents of it or trying to differentiate it via a setting or something.

0 Likes