Sublime Forum

How to generate pop up user input?

#1

Hi I’m trying to generate a popup like sublime.yes_no_cancel_dialog but with an input to write something there, i’m using sublime.Window.show_input_panel but aint working as I expected, is there anything else I could use?

PD: my main problem with sublime.Window.show_input_panel its because self.on_done stores the input user inside a def and I can’t use it outside

0 Likes

#2
def on_done(self, string):
    self.string = string

Assign the string to a class variable and use it from wherever you want. Or not?

0 Likes

#3

Sorry for late answere, I was doing it alredy but seems like sublime doesn’t wait user to input something and gets the string null

0 Likes

#4

As you have correctly surmised, Sublime does indeed not wait for you to enter the text. For that to happen, your command would have to pause while the user is entering text. Since only one command can run at a time and things like moving the cursor or entering text are handled by running commands, if your command never stopped running Sublime would be effectively hung.

Instead, show_input_panel returns immediately after showing the panel, and while other things are happening the user can enter text. When they press enter and the input is complete, the on_done callback gets called to tell you what the text was.

I would guess that your code that doesn’t work looks something like this:

import sublime
import sublime_plugin


class ExampleOneCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        def on_done(input_string):
            self.text = input_string

        window = self.view.window()
        window.show_input_panel("Text to Insert:", "Hello, World!",
                                 on_done, None, None)

        self.view.insert(edit, 0, self.text)

This compiles, and when you run the command you get asked to enter text, but when you press enter nothing happens. If you look in the console, you see this:

AttributeError: 'ExampleOneCommand' object has no attribute 'text'

Why does that happen? What it’s trying to do is:

  1. Make a call to ask the user for input
  2. Use the input the user entered to do something
  3. Get notified that the user entered text

Or if you will, it’s trying to use the text before the user has even managed to type anything. If you were to run the same command again, it would immediately insert the text you entered the first time while still prompting you for text, because it’s using the value that it knows about from last time you ran it.

The proper way to do this is to re-order the steps that it’s trying to take. In this example, that would be:

  1. Make a call to ask the user for input
  2. Get notified that the user entered text
  3. Use the input the user entered to do something

By way of an example, here’s the example code from above rewritten to follow the steps in this order:

import sublime
import sublime_plugin


class ExampleTwoCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        def on_done(input_string):
            self.view.run_command("move_to", {"to": "bof"})
            self.view.run_command("insert", {"characters": input_string})

        def on_change(input_string):
            print("Input changed: %s" % input_string)

        def on_cancel():
            print("User cancelled the input")

        window = self.view.window()
        window.show_input_panel("Text to Insert:", "Hello, World!",
                                 on_done, on_change, on_cancel)

Now when you run the command, it uses show_input_panel to ask for input, and leaves. As you’re entering text, on_change keeps getting called, which is printed to the console to tell you what the text has been changed to. If you press Esc, on_cancel gets called and the console reports that the you canceled the input.

If you press Enter, on_done gets called, and then at this point it gets the text you typed entered and can do something with it (here insert it at the start of the current file).

Basically whatever it is you want the input for, you need to alter the flow of your code. For example you might try putting everything that’s supposed to happen after the user pressed enter into another function, and then call it from on_done so it doesn’t happen until the text input is complete.

3 Likes

#5

Hi, this is my function, it just autoCommit a file with a reference ID that gets from another php file, but there is sometimes I dont get that ID so I ask the user to Input it, and that Input is what i’m having troubles to use

class SideBarGitAddCommitBotNewCommand(sublime_plugin.WindowCommand):
def on_done(self, input_user):
global inputUser
inputUser = input_user
return inputUser

def on_change(self, input_user):
print(“Input changed: %s” % input_user)

def on_cancel(self):
sublime.status_message(“Cancelado”)
sublime.message_dialog(“No se pudo hacer el commit”)

def run(self, paths = [], input = True, content = ‘’,):
file_name_php=self.window.active_view().file_name()
file_object = open(file_name_php, “r”, encoding=“utf8”)
file_object_read = file_object.read()

  nombreTienda = re.search('nombreTienda\s?=\s?[\'\"](.*?)[\'\"]\;', file_object_read)
  pais = re.search('pais\s?=\s?[\'\"](.*?)[\'\"]', file_object_read)
  if re.search('addMarket', file_object_read):
  	idTipo = 3
  	tipo = 'Store'
  elif re.search('addCatalog', file_object_read):
  	idTipo = 4
  	tipo = 'Catalog'
  else:
  	message = ("El plugin no ha podido especificar que tipo de bot es.")
  	answer = sublime.yes_no_cancel_dialog(message, "Tienda", "Catalog")
  	if answer == sublime.DIALOG_YES:
  		tipo = 'Store'
  		idTipo = 3
  	elif answer == sublime.DIALOG_NO:
  		tipo = 'Catalog'
  		idTipo = 4
  if idTipo:
  	script_dir = os.path.dirname(__file__)+"\\refs.php"
  	proc = subprocess.Popen('php "'+script_dir+'" '+urllib.parse.quote(nombreTienda.group(1))+' '+pais.group(1)+' '+str(idTipo), shell=True, stdout=subprocess.PIPE)
  	content = proc.stdout.read().decode("utf-8")
  	print("Referencia: "+content)
  
  	if content == '':
  		sublime.error_message("No se encuentra la referencia, a continuación podrá escribirla manualmente en input de abajo.")
  		sublime.Window.show_input_panel(sublime.active_window(), "Referencia: ", "#Id del ticket", self.on_done, self.on_change, self.on_cancel)
  		content = "(refs "+inputUser+") "
  	nombreTienda = nombreTienda.group(1).replace("+", "_")
  	content = str(content+"NEW "+tipo+"_"+nombreTienda+"-"+pais.group(1))
  	for repo in SideBarGit().getSelectedRepos(SideBarSelection(paths).getSelectedItems()):
  		commitCommandAdd = ['git', 'add', '--']
  		commitCommandCommit = ['git', 'commit', '-m', content, '--']
  		for item in repo.items:
  			commitCommandAdd.append(item.forCwdSystemPathRelativeFromRecursive(repo.repository.path()))
  			commitCommandCommit.append(item.forCwdSystemPathRelativeFrom(repo.repository.path()))
  		object = Object()
  		object.item = repo.repository
  		object.command = commitCommandAdd
  		# SideBarGit().run(object)
  		object = Object()
  		object.item = repo.repository
  		object.to_status_bar = True
  		object.command = commitCommandCommit
  		# SideBarGit().run(object)
  else:
  	sublime.error_message("No se pudo hacer el commit")

def is_enabled(self, paths = []):
return SideBarSelection(paths).len() > 0

I’m trying to set the text in to a global var and then use it later but still the same error, as you explained before, any tips on how to correct that? thanks

0 Likes