The attached is a nice touch
Copy text with highlighted colors
@facelessuser: "maybe just generate temporary html " - is it possible to feed an HTML string to a browser, without first saving the file? I suppose so - Iâve never tried to do this before . But I could certainly modify the code to create a temporary/bogus file name for the HTML.
Myself, and a number of other posters, have struggled to obtain the right command that previews in a browser. I did consider this, but I assume a lot of users already have their own method of doing this reliably (for them). I wouldnât want to rely on the installation of a different package to do this; although, perhaps youâre suggesting I could examine the code for the Markdown package?
I suppose I could read another setting, that identifies the usersâ command to run once the HTML is generated Mmm
Having a setting for an alternative, printable, theme is certainly a good idea . This shouldnât be tricky. I suppose there could also be a setting to specify: false == donât apply background, or â#NNNNNNâ. Andy.
[quote=âagibsonswâ]@facelessuser: "maybe just generate temporary html " - is it possible to feed an HTML string to a browser, without first saving the file? I suppose so - Iâve never tried to do this before . But I could certainly modify the code to create a temporary/bogus file name for the HTML.
Myself, and a number of other posters, have struggled to obtain the right command that previews in a browser. I did consider this, but I assume a lot of users already have their own method of doing this reliably (for them). I wouldnât want to rely on the installation of a different package to do this; although, perhaps youâre suggesting I could examine the code for the Markdown package?
[/quote]
No sense in re-inventing the wheel. I was more like you guessed, just examine some code to understand what others do to have it work reliably. I just think, auto-opening the file in a browser would make the workflow very nice. Also, the current method is going to create HTMLs all over, so creating temp HTMLs in one place will keep junk files to a minimum.
[quote=âagibsonswâ]
Having a setting for an alternative, printable, theme is certainly a good idea . This shouldnât be tricky. I suppose there could also be a setting to specify: false == donât apply background, or â#NNNNNNâ. Andy.[/quote]
I donât think ignoring the background will be too useful (tough to say); usually the colors of text and such only work well with the background selected. But alternative color themesâŚthat would be nice .
Below is the version that optionally allows you to print line numbers, using a different key-binding. I havenât updated my repo yet.
{ "keys": "ctrl+alt+m"], "command": "print_html", "args": { "numbers": false } },
{ "keys": "ctrl+alt+n"], "command": "print_html", "args": { "numbers": true } },
[code]import sublime, sublime_plugin
from xml.dom import minidom
import re
from os import path
class PrintHtmlCommand(sublime_plugin.TextCommand):
def run(self, edit, numbers): # numbers == True: output line numbers
self.colours = {}
path_packages = sublime.packages_path()
settings = sublime.load_settings(âPreferences.sublime-settingsâ)
colour_scheme = settings.get(âcolor_schemeâ)
# colour_scheme = colour_scheme.replace(â/â, â\\â)
colour_scheme = path.normpath(colour_scheme)
colour_scheme = colour_scheme.replace(âPackagesâ, ââ)
font_size = settings.get(âfont_sizeâ) or 10
font_face = settings.get(âfont_faceâ) or âConsolasâ
tab_size = settings.get(âtab_sizeâ) or 4
# padd_bottom = settings.get(âline_padding_bottomâ) or 0
doc = minidom.parse(path_packages + colour_scheme)
the_dict = doc.getElementsByTagName(âdictâ)[0]
the_array = the_dict.getElementsByTagName(âarrayâ)[0]
colour_settings = the_array.getElementsByTagName(âdictâ)[0]
bground = ââ; fground = ââ; gfground = ââ
for key_tag in colour_settings.getElementsByTagName(âkeyâ):
try:
if key_tag.firstChild.data.strip() == âbackgroundâ:
bground = key_tag.nextSibling.nextSibling.firstChild.data.strip()
elif key_tag.firstChild.data.strip() == âforegroundâ:
fground = key_tag.nextSibling.nextSibling.firstChild.data.strip()
elif key_tag.firstChild.data.strip() == âgutterForegroundâ:
gfground = key_tag.nextSibling.nextSibling.firstChild.data.strip()
except:
pass
dict_items = the_array.getElementsByTagName(âdictâ)[1:]
for item in dict_items:
scope = ââ; colour = ââ
for key_tag in item.getElementsByTagName(âkeyâ):
try:
if key_tag.firstChild.data.strip() == âscopeâ:
scope = key_tag.nextSibling.nextSibling.firstChild.data.strip()
elif key_tag.firstChild.data.strip() == âforegroundâ:
colour = key_tag.nextSibling.nextSibling.firstChild.data.strip()
except:
pass
if scope != ââ and colour != ââ:
self.colours[scope] = colour
curr_view = self.view
curr_file = curr_view.file_name()
head, tail = path.split(curr_file)
fname, ext = path.splitext(tail)
ext = ext.replace('.', '_')
curr_sel = curr_view.sel()[0]
if curr_sel.empty() or abs(curr_sel.end() - curr_sel.begin()) < 4:
the_html = open(head + path.sep + fname + ext + '_parsed.html', 'w')
size = curr_view.size()
pt = 0; end = 1
else:
the_html = open(head + path.sep + fname + ext + '_part.html', 'w')
size = curr_sel.end()
pt = curr_sel.begin()
end = pt + 1
the_html.write('<!DOCTYPE html>\n')
the_html.write('<html>\n<head>\n<title>' + fname + ext + '</title>\n')
the_html.write('<style type=\"text/css\">\n')
the_html.write('\tspan { display: inline; border: 0; margin: 0; padding: 0; }\n')
if numbers and gfground != '':
the_html.write('\tli { color: ' + gfground + '; }\n')
the_html.write('\tbody { ')
if fground != '': the_html.write('color: ' + fground + ';')
if bground != '': the_html.write(' background-color: ' + bground + ';')
the_html.write(' font: ' + `font_size` + 'pt \"' + font_face + '\", Consolas, Monospace;')
the_html.write('\n}\n')
the_html.write('</style>\n</head>\n<body>\n')
if numbers: the_html.write('<ol>\n<li>')
while end <= size:
scope_name = curr_view.scope_name(pt)
while curr_view.scope_name(end) == scope_name and end <= size:
end += 1
region = sublime.Region(pt, end)
the_key = scope_name.strip()
if self.colours.has_key(the_key):
the_colour = self.colours[the_key]
else:
if re.match('source\.[a-zA-Z_]*$', the_key) is not None:
self.colours[the_key] = fground
the_colour = fground
else:
best_match = -1
for key in self.colours:
if curr_view.score_selector(pt, key) > best_match:
best_match = curr_view.score_selector(pt, key)
the_colour = self.colours[key]
self.colours[the_key] = the_colour
tidied_text = curr_view.substr(region)
tidied_text = tidied_text.replace('&', '&')
tidied_text = tidied_text.replace('<', '<')
tidied_text = tidied_text.replace('>', '>')
tidied_text = tidied_text.replace('\t', ' ' * tab_size)
tidied_text = tidied_text.replace(' ' * tab_size, ' ' * tab_size)
if numbers:
new_li = '</span></li>\n<li><span style=\"color:' + the_colour + '\">'
tidied_text = tidied_text.replace('\n', new_li)
else:
tidied_text = tidied_text.replace('\n', '<br>')
# for x, y in zip(('&', '<', '>', '\t', ' ' * tab_size, '\n'),
# ('&', '<', '>', ' ' * tab_size, ' ' * tab_size, '<br>')):
# tidied_text = tidied_text.replace(x, y)
the_html.write('<span style=\"color:' + the_colour + '\">')
the_html.write(tidied_text + '</span>')
pt = end
end = pt + 1
if numbers: the_html.write('</li>\n</ol>')
the_html.write('\n</body>\n</html>')
the_html.close()[/code]
Let me know if it misbehaves on different systems/browsers; in particular, I may need to tweak the css so that un-wanted gaps between each line are removed.
Yes, Iâve tried this myself and the results werenât good. I even tried inverting all the colour-numbers but most of the text ended up blue-ish
You mean to store the HTML in their default temp folder? I think that this, and other approaches, might wait for more user feedback. In the meantime, I should modify the code so that, if the view isnât saved, a temporary name is created. Iâll probably just use âtemp_parsed.htmlâ for the moment.
Actual numbers opposed to always starting from 1 :
row = curr_view.rowcol(pt)[0] + 1
....
// Apply start number to first list item
if numbers: the_html.write('<ol>\n<li value="%d">' % row)
[quote=âagibsonswâ]@facelessuser
Yes, Iâve tried this myself and the results werenât good. I even tried inverting all the colour-numbers but most of the text ended up blue-ish
You mean to store the HTML in their default temp folder? I think that this, and other approaches, might wait for more user feedback. In the meantime, I should modify the code so that, if the view isnât saved, a temporary name is created. Iâll probably just use âtemp_parsed.htmlâ for the moment.[/quote]
Hmm. Maybe I will look into then. The clutter created by writing the HTMLs in the same folder as the actual file I find less than desirable. If you arenât anxious to do such a thing, I will probably play around with it and issue a pull request if I come up with something good.
If they havenât saved the view, how do I retrieve their current project or default path reliably/cross-os? For the moment I want to store a temp HTML there.
I already have it working. You just need the the Desktop module from Paul Boddie boddie.org.uk/python/downloa ⌠0.4.tar.gz. Really, it is just the folder called desktop in the tar ball you need. It is the same package that âSideBarEnhancementsâ uses and âMarkdown Previewâ. It is super easy .
[code]import sublime, sublime_plugin
from xml.dom import minidom
import tempfile
import desktop
import re
from os import path
class PrintHtmlCommand(sublime_plugin.TextCommand):
def run(self, edit, numbers): # numbers == True: output line numbers
self.colours = {}
path_packages = sublime.packages_path()
settings = sublime.load_settings(âPreferences.sublime-settingsâ)
colour_scheme = settings.get(âcolor_schemeâ)
# colour_scheme = colour_scheme.replace(â/â, â\\â)
colour_scheme = path.normpath(colour_scheme)
colour_scheme = colour_scheme.replace(âPackagesâ, ââ)
font_size = settings.get(âfont_sizeâ) or 10
font_face = settings.get(âfont_faceâ) or âConsolasâ
tab_size = settings.get(âtab_sizeâ) or 4
# padd_bottom = settings.get(âline_padding_bottomâ) or 0
doc = minidom.parse(path_packages + colour_scheme)
the_dict = doc.getElementsByTagName(âdictâ)[0]
the_array = the_dict.getElementsByTagName(âarrayâ)[0]
colour_settings = the_array.getElementsByTagName(âdictâ)[0]
bground = ââ; fground = ââ; gfground = ââ
for key_tag in colour_settings.getElementsByTagName(âkeyâ):
try:
if key_tag.firstChild.data.strip() == âbackgroundâ:
bground = key_tag.nextSibling.nextSibling.firstChild.data.strip()
elif key_tag.firstChild.data.strip() == âforegroundâ:
fground = key_tag.nextSibling.nextSibling.firstChild.data.strip()
elif key_tag.firstChild.data.strip() == âgutterForegroundâ:
gfground = key_tag.nextSibling.nextSibling.firstChild.data.strip()
except:
pass
dict_items = the_array.getElementsByTagName(âdictâ)[1:]
for item in dict_items:
scope = ââ; colour = ââ
for key_tag in item.getElementsByTagName(âkeyâ):
try:
if key_tag.firstChild.data.strip() == âscopeâ:
scope = key_tag.nextSibling.nextSibling.firstChild.data.strip()
elif key_tag.firstChild.data.strip() == âforegroundâ:
colour = key_tag.nextSibling.nextSibling.firstChild.data.strip()
except:
pass
if scope != ââ and colour != ââ:
self.colours[scope] = colour
curr_view = self.view
curr_file = curr_view.file_name()
head, tail = path.split(curr_file)
fname, ext = path.splitext(tail)
ext = ext.replace('.', '_')
curr_sel = curr_view.sel()[0]
the_html = tempfile.NamedTemporaryFile(delete=False, suffix='.html')
if curr_sel.empty() or abs(curr_sel.end() - curr_sel.begin()) < 4:
size = curr_view.size()
pt = 0; end = 1
else:
size = curr_sel.end()
pt = curr_sel.begin()
end = pt + 1
row = curr_view.rowcol(pt)[0] + 1
the_html.write('<!DOCTYPE html>\n')
the_html.write('<html>\n<head>\n<title>' + fname + ext + '</title>\n')
the_html.write('<style type=\"text/css\">\n')
the_html.write('\tspan { display: inline; border: 0; margin: 0; padding: 0; }\n')
if numbers and gfground != '':
the_html.write('\tli { color: ' + gfground + '; }\n')
the_html.write('\tbody { ')
if fground != '': the_html.write('color: ' + fground + ';')
if bground != '': the_html.write(' background-color: ' + bground + ';')
the_html.write(' font: ' + `font_size` + 'pt \"' + font_face + '\", Consolas, Monospace;')
the_html.write('\n}\n')
the_html.write('</style>\n</head>\n<body>\n')
if numbers: the_html.write('<ol>\n<li value="%d">' % row)
while end <= size:
scope_name = curr_view.scope_name(pt)
while curr_view.scope_name(end) == scope_name and end <= size:
end += 1
region = sublime.Region(pt, end)
the_key = scope_name.strip()
if self.colours.has_key(the_key):
the_colour = self.colours[the_key]
else:
if re.match('source\.[a-zA-Z_]*$', the_key) is not None:
self.colours[the_key] = fground
the_colour = fground
else:
best_match = -1
for key in self.colours:
if curr_view.score_selector(pt, key) > best_match:
best_match = curr_view.score_selector(pt, key)
the_colour = self.colours[key]
self.colours[the_key] = the_colour
tidied_text = curr_view.substr(region)
tidied_text = tidied_text.replace('&', '&')
tidied_text = tidied_text.replace('<', '<')
tidied_text = tidied_text.replace('>', '>')
tidied_text = tidied_text.replace('\t', ' ' * tab_size)
tidied_text = tidied_text.replace(' ' * tab_size, ' ' * tab_size)
if numbers:
new_li = '</span></li>\n<li><span style=\"color:' + the_colour + '\">'
tidied_text = tidied_text.replace('\n', new_li)
else:
tidied_text = tidied_text.replace('\n', '<br>')
# for x, y in zip(('&', '<', '>', '\t', ' ' * tab_size, '\n'),
# ('&', '<', '>', ' ' * tab_size, ' ' * tab_size, '<br>')):
# tidied_text = tidied_text.replace(x, y)
the_html.write('<span style=\"color:' + the_colour + '\">')
the_html.write(tidied_text + '</span>')
pt = end
end = pt + 1
if numbers: the_html.write('</li>\n</ol>')
the_html.write('\n</body>\n</html>')
the_html.close()
desktop.open(the_html.name)
[/code]
Mmm Iâm reluctant to use any non-standard libraries, creating a dependency. Perhaps this could be plan C? Or just an alternative version.
I think Iâve been unnecessarily mucking around with paths; it will save in their current/default folder anyway. Andy.
The external module is only for automatic opening of the browser. You would be including the dependencies, and it allows it to work on Linux, Mac, and Windows. I understand your general feeling of not wanting to include a external module, but trying to get support for Linux, Mac, and Windows when someone has already done it, will be a huge headache. That is kind of why these things existâŚto make peoples lives easier.
Also, with other packages using this same module, it shows it is effective and stable. I have to be honest, in this case, unless you want to write your own library for interfacing with Windows, Mac, and Linux KDE and Gnome environments, I donât see why you wouldnât want to use it.
The temp file stuff on the other hand is built-in.
Side note: There does seem to be some issues with some languages guessing scopes. C++ particularly has most of the text colored as comments. The guessing algorithm probably will need some work for better guessing.
This seems to guess colors better in C++ etc and not assign comment colors to everything.
default_scope = curr_view.scope_name(end).split(' ')[0]
while end <= size:
scope_name = curr_view.scope_name(pt)
while curr_view.scope_name(end) == scope_name and end <= size:
end += 1
region = sublime.Region(pt, end)
the_key = scope_name.strip()
if self.colours.has_key(the_key):
the_colour = self.colours[the_key]
else:
if re.match('source\.[a-zA-Z_]*$', the_key) is not None:
self.colours[the_key] = fground
the_colour = fground
else:
best_match = -1
the_colour = fground
for key in self.colours:
if curr_view.score_selector(pt, key) > best_match:
best_match = curr_view.score_selector(pt, key)
the_colour = self.colours[key]
if curr_view.score_selector(pt, default_scope) > best_match:
the_colour = fground
self.colours[the_key] = the_colour
[quote=âfacelessuserâ]This seems to guess colors better in C++ etc and not assign comment colors to everything.
default_scope = curr_view.scope_name(end).split(' ')[0]
while end <= size:
scope_name = curr_view.scope_name(pt)
while curr_view.scope_name(end) == scope_name and end <= size:
end += 1
region = sublime.Region(pt, end)
the_key = scope_name.strip()
if self.colours.has_key(the_key):
the_colour = self.colours[the_key]
else:
if re.match('source\.[a-zA-Z_]*$', the_key) is not None:
self.colours[the_key] = fground
the_colour = fground
else:
best_match = -1
the_colour = fground
for key in self.colours:
if curr_view.score_selector(pt, key) > best_match:
best_match = curr_view.score_selector(pt, key)
the_colour = self.colours[key]
if curr_view.score_selector(pt, default_scope) > best_match:
the_colour = fground
self.colours[the_key] = the_colour
[/quote]
Can you describe briefly the change(s) youâve made - save me some time
[quote=âagibsonswâ]
[quote=âfacelessuserâ]This seems to guess colors better in C++ etc and not assign comment colors to everything.
default_scope = curr_view.scope_name(end).split(' ')[0]
while end <= size:
scope_name = curr_view.scope_name(pt)
while curr_view.scope_name(end) == scope_name and end <= size:
end += 1
region = sublime.Region(pt, end)
the_key = scope_name.strip()
if self.colours.has_key(the_key):
the_colour = self.colours[the_key]
else:
if re.match('source\.[a-zA-Z_]*$', the_key) is not None:
self.colours[the_key] = fground
the_colour = fground
else:
best_match = -1
the_colour = fground
for key in self.colours:
if curr_view.score_selector(pt, key) > best_match:
best_match = curr_view.score_selector(pt, key)
the_colour = self.colours[key]
if curr_view.score_selector(pt, default_scope) > best_match:
the_colour = fground
self.colours[the_key] = the_colour
[/quote]
Can you describe briefly the change(s) youâve made - save me some time [/quote]
The source scope is always the first scope.
souce.language otherscope.etc.etc.
So, I store the the default scope. And after you did your best_match guessing, I compare the scope against the default. If the score is greater for the default scope than it is for the guessed scope, use the default scope instead (fground color).
@facelessuser TQ. I shall examine/incorporate this. I want to look at it in a little more detail, to discover the cause. (Presumably âcommentâ is the first colour in your theme?)
I adopted youâre âstart fromâŚâ line numbering suggestion, but only when printing a selection. That is, I want the line numbering to agree with the code. Andy.
Oh, one more thing. I think this is the last.
This destroys my alignment in code.
tidied_text = tidied_text.replace(' ' * tab_size, ' ' * tab_size)
Please use this instead for spaces (you can still convert tabs, but spaces is bad).
tidied_text = tidied_text.replace(' ' , ' ' )
@facelessuser: No problem. That replaces all spaces - I might try to pursued it to only apply at the beginning of the line, otherwise it seems like overkill.
I do take your comments on board about the desktop library and non-Windows users. But Iâll explore the C++ (and similar) issue(s) first.
Iâve updated my repository with recent changes (not including the spaces, yet, or the C++ changes).
[quote=âagibsonswâ]@facelessuser: No problem. That replaces all spaces - I might try to pursued it to only apply at the beginning of the line, otherwise it seems like overkill.
I do take your comments on board about the desktop library and non-Windows users. But Iâll explore the C++ (and similar) issue(s) first.
Iâve updated my repository with recent changes (not including the spaces, yet, or the C++ changes).[/quote]
Thanks. I hope I am not coming off two aggressive or anything. I think this plugin is really cool, so I am just trying to give straight forward and honest feedback.
I do realize not everything I may suggest will be taken up, but I think it is better to throw it out there anyways. Ultimately it is your plugin, so you make the calls. People are always going to throw there 2 cents in (like me ). The fact that I am so vocal just shows that I really like the plugin and intend to use it.
@facelessuser: Hey, no problem at all! I welcome your comments and assistance
I intend to understand, and agree with (mostlyâŚ) any changes made to the code. So Iâll learn about pulling, forking, bundling other libraries, etc., along the way.
I only started this yesterday out of interest, but if it proves useful as a plug-in then thatâs great. In the meantime, Iâve learnt about, coded (and discarded!) three different xml parsers.
(Originally I considered creating an .rtf, or using COM automation to format the text in Word - glad I took the HTML road )
Keep the commentary coming! Andy.
I havenât applied your changes yet, but my C++ sample is formatting source.c++ and other scopes with a ârandomâ scope way down in the file. Maybe itâs the pluses ++ that are causing the issue. Are there any other languages youâve tested?
Added: C# seems to be okay, even coping with four spaces(?). Do you use four spaces�