Sublime Forum

Build system output as HTML?

#1

I’ve been doing research the last few days to determine if Sublime Text 3 can replace Text Mate (Mac) for my IDE.

The current problem I’m stuck on is how to format build system results so they can contain clickable links which can select error lines in sources files. In Text Mate this was very easy because the build scripts could output HTML to a window which could easily be formatted anyway I like.

In Sublime Text 3 I figured out how to create custom build systems with scripts but the output is always displayed in the little console pane at the bottom of the window. Additionally there is a syntax option which applies a .tmLanguage syntax to the output but this doesn’t allow for clickable links so I can jump to the errors in my code.

I read that the build system calls into the exec.py file which can be overridden but there is no more information I could find on that front. I’ve also seen evidence in exec.py that Sublime Text can insert HTML/CSS into the text but again I have no idea where to start.

Can anyone point me in the right direction? Thanks.

0 Likes

#2

You need to add correct file_regex and line_regex keys to your built-system. They are used to parse the output and create those links. If they are set up properly, you can click on such output strings and ST will highlight open the file and scroll to the position in a normal view.

http://docs.sublimetext.info/en/latest/file_processing/build_systems.html

0 Likes

#3

Ah ok, I found out you need to DOUBLE CLICK on the links and they jump to the line. Single click would be better but that’s getting there.

I’m seeing now that Sublime Text doesn’t go to the first error after a build which is typical behavior of an IDE. There doesn’t seem to be an option for this in the build settings so do I need to run a python script or something?

Thanks.

0 Likes

#4

Navigation is handled by keybindings F4 which runs next_result and shift+F4 which runs previous_command. Not sure whether built-system can handle it, but maybe you can run next_result after built to jump to the first issue?

0 Likes

#5

The build command just has a “cmd” key which is used to execute a script/program but can it run a python script that imports the Sublime Text API? If you had access to the python API I guess you could call next_result after running the script that “cmd” is currently using. I don’t know much about python but I think that’s how that would work if I could find an example.

0 Likes

#6

I think a simple macro would be just enough here. Maybe

Macrofile:

Built and Show.sublime-macro

[
    {"command": "build" },
    {"command": "next_result" }
]

Than a keybinding to it:

{ "keys": ["f7"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Delete Left Right.sublime-macro"} }

Not sure whether it works as build is an asynchronous task running in the background. The macro therefore may not wait for build to complete before calling next_result.

If it does not work, you’d need to write a python script to hook the on_finished() event of the build command to run the next_result afterwards. The build process is implemented in Default.sublime-package/exec.py

To run a command from python you need to call sublime.run_command("..") or window.run_command(...) or view.run_command(...) depending on the command type. The api is described in the docs https://www.sublimetext.com/docs/3/api_reference.html or you can have into the sublime.py and sublime_plugin.py in the install_dir.

0 Likes

#7

Thanks for the ideas I appreciate it.

  1. I tried the Macro idea first but I’m confused about “res://Packages/Default/Delete Left Right.sublime-macro” I assume you meant that to do a path to the “Built and Show.sublime-macro” file?

  2. where is “next_result” defined? I don’t see it listed in the API.

  3. Do you have an example of the “target” key in the sublime-build file? The docs say “A Sublime Text WindowCommand. Defaults to exec (Packages/Default/exec.py). This command receives all the target command arguments specified in the .sublime-build file (as **kwargs).” but that doesn’t explain much.

I was going to try to run the command below as a test since I could find any examples but I believe I need the “target” set instead of putting the script in the “cmd” key which didn’t work.

import sublime
import sublime_plugin
import re
import importlib
defaultExec = importlib.import_module("Default.exec")

class ExecCommand(defaultExec.ExecCommand):

    def on_finished(self, proc):

        super(ExecCommand, self).on_finished(proc)
0 Likes

#8

Yes, that example is showing you how you would bind a key to execute a macro. You’d create your own macro and replace the path as appropriate. That would involve replacing the Default with the name of the package you’ve stored the macro in, as well as the name of the macro itself.

next_result, along with run_macro_file are not in the API because they’re commands that are already defined. The unofficial documentation has a list of commands and the arguments they take.

There are three (currently) examples of customized build targets available here. Each of the plugin files contains usage information.

In a nutshell, by default the build system uses the exec command to execute an external process. You can override that by having a target key set in the sublime-build file, which specifies the command that should be run to execute the build. When the command runs, all of the items in the sublime-build file are passed as arguments to your command, so you get cmd argument that says what to execute, etc.

More information on build systems in general is also available in the unofficial documentation.

0 Likes

#9

Thanks, I see next_result can be called with run_command.

I’m not understanding how “target” works. In your example “target”: “my_custom_build” is my_custom_build a file named my_custom_build.py? I tried adding a file named fpc_exec.py with the ExecCommand override example I found and changed “cmd” to “command” but it doesn’t work (“Unable to find target command: fpc_exec” in the console). Do I need to do something else? The .py file is in the same directory as the .sublime-build file.

0 Likes

#10

No, each file in that part of the repository is meant to be a stand alone example, with the comment telling you how you would go about using the code that it contains.

Command names come from the names of the classes that implement them, not the source file that they’re contained in. Any class that subclasses somehow from TextCommand, WindowCommand or ApplicationCommand is turned into a command when the file is loaded by Sublime.

The name of the class is turned into the name of the command by dropping the Command suffix on the name (if it exists), and then replacing all upper case letters with and underscore followed by their lower case equivalent, except for a leading upper case character, which is just made lower case.

So in the case of custom_build_variables.py, the comment tells you that you should set the target to my_custom_build, and below the comment the class MyCustomBuildCommand is defined, which following the rules laid out above becomes my_custom_build.

0 Likes

#11

That makes sense now but it still can’t find the target command. Just as a test I added in your custom_build_variables.py file and changed the build command to:

{
    "cmd": ["fpc", "-vbr", "$file"],
    "file_regex": "^(.*):([0-9]+): (warning|error|note|fatal)+: [0-9]+: (.*)$",
    "working_dir": "${file_path}",
    "selector": "source.pascal",
    "syntax": "fpc_console.tmLanguage",
    "target": "my_custom_build",
    "shell": false,
}

but in the console I still get “Unable to find target command: my_custom_build”. Everything is in the same directory so I don’t understand why it’s not loading. If I remove the “target” key it loads ExecCommand from Default and in fact if I change target to “exec” it also loads properly. Any ideas?

0 Likes

#12

Where are you putting the custom_build_variables.py file? It needs to be at the top level of a package folder, for example Packages\User if it’s in your user package. If it’s nested any deeper than that, Sublime won’t load it automatically, so you need to take care of loading it yourself.

0 Likes

#13

Ah, that’s it, thanks. I was putting it in a directory inside Packages/User along with the sublime-build file but moving it to /User fixed the problem.

Is this overriding code I found valid? I’ve never used Python before so I’m just copying and pasting at this point. The string is added to the panel and the command runs so I assume it’s ok but I tried adding a ExecEventListener class in the file also (like I saw in defaults/exec.py) but this doesn’t seem to get called so I commented it out. I wanted to see if I could be notified when the build results panel closes so I could hide the phantoms that are cluttering things up (I like them but there’s no way to clear them all).

import sublime
import sublime_plugin
import re
import importlib
defaultExec = importlib.import_module("Default.exec")

class MyExecCommand(defaultExec.ExecCommand):

    def on_finished(self, proc):

        super(MyExecCommand, self).on_finished(proc)
        self.append_string(proc, "[======= OVERRIDE MY EXEC =======]\n")
        self.window.run_command("next_result")

# class ExecEventListener(sublime_plugin.EventListener):
#     def on_pre_close(self, view)
#         w = view.window()
#         w.run_command("next_result")
0 Likes

#14

I confirmed the event listener is working but I should probably start another thread to figure out how to clear phantoms or get a notification when the panel closes. I’ll look over the API more first though. Thanks for your help, I appreciate it.

   class MyExecEventListener(sublime_plugin.EventListener):
        def on_pre_close(self, view):
            print("will close")
        def on_close(self, view):
        		print("did close")
        def on_modified(self, view):
        		print("modified "+str(view.id()))
0 Likes

#15

The style seems a little weird (although I am by no means a python guru), but it seems mostly serviceable. Note that Sublime binds to Python 3.3.6 internally (you can execute import sys; sys.version in the console to check) so depending on where you get your code from, you may need to tweak it if it’s not already for Python 3.

If you check the implementation of on_finished in exec.py, you’ll see that it involves a single call to sublime.set_timeout that is invoking self.finish after a 0 millisecond timeout. That’s because on_finished is happening in a background process (because the build runs in the background) and this is a handy “trick” to flip back to the main UI thread.

If you dig further into the implementation of finish (in the same file) you will see among other things a call to self.output_view.find_all_results(), which is the call that actually matches the contents in the output panel to find errors.

Taken together, that means that as you’ve outlined the code above, you’re trying to run the next_result command before the build has actually finished setting up the build results.

To get around that you can override finish instead, since that’s always happening in the main thread, and if you invoke the super version first, you know that all of the processing that exec normally does is all finished:

import sublime
import sublime_plugin

from Default.exec import ExecCommand

class MyExecCommand(ExecCommand):
    def finish(self, proc):
        super().finish(proc)

        if self.errs_by_file:
            self.window.run_command("next_result")

This version also checks to see if there are actually any errors before it runs next_result, which may or may not be interesting since the command does nothing if there is no next result to go to anyway, but if there is any processing that you only want to happen when there are errors (or not), you can put it there.

0 Likes

#16

Thanks, I’ll try this.

0 Likes