Sublime Forum

Copy text with highlighted colors

#25

The attached is a nice touch :wink:


0 Likes

#26

@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 :wink:. 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 :question: Mmm

Having a setting for an alternative, printable, theme is certainly a good idea :wink:. This shouldn’t be tricky. I suppose there could also be a setting to specify: false == don’t apply background, or ‘#NNNNNN’. Andy.

0 Likes

#27

[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 :wink:. 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 :wink:. 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 :smile: .

0 Likes

#28

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('&', '&amp;')
		tidied_text = tidied_text.replace('<', '&lt;')
		tidied_text = tidied_text.replace('>', '&gt;')
		tidied_text = tidied_text.replace('\t', '&nbsp;' * tab_size)
		tidied_text = tidied_text.replace(' ' * tab_size, '&nbsp;' * 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'),
		# 		('&amp;', '&lt;', '&gt;', '&nbsp;' * tab_size, '&nbsp;' * 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.

0 Likes

#29

@facelessuser

Yes, I’ve tried this myself and the results weren’t good. I even tried inverting all the colour-numbers :open_mouth: but most of the text ended up blue-ish :laughing:

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.

0 Likes

#30

Actual numbers opposed to always starting from 1 :smile: :

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)

0 Likes

#31

[quote=“agibsonsw”]@facelessuser

Yes, I’ve tried this myself and the results weren’t good. I even tried inverting all the colour-numbers :open_mouth: but most of the text ended up blue-ish :laughing:

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.

0 Likes

#32

@facelessuser :sunglasses:

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.

0 Likes

#33

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 :smile:.

[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('&', '&amp;')
     tidied_text = tidied_text.replace('<', '&lt;')
     tidied_text = tidied_text.replace('>', '&gt;')
     tidied_text = tidied_text.replace('\t', '&nbsp;' * tab_size)
     tidied_text = tidied_text.replace(' ' * tab_size, '&nbsp;' * 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'),
     #       ('&amp;', '&lt;', '&gt;', '&nbsp;' * tab_size, '&nbsp;' * 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]

0 Likes

#34

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.

0 Likes

#35

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.

0 Likes

#36

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

0 Likes

#37

[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 :smiley:

0 Likes

#38

[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 :smiley:[/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).

0 Likes

#39

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

0 Likes

#40

Oh, one more thing. I think this is the last.

This destroys my alignment in code.

tidied_text = tidied_text.replace(' ' * tab_size, '&nbsp;' * tab_size)

Please use this instead for spaces (you can still convert tabs, but spaces is bad).

tidied_text = tidied_text.replace(' ' , '&nbsp;' )
0 Likes

#41

@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 :wink: 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).

0 Likes

#42

[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 :wink: 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 :smile: ). The fact that I am so vocal just shows that I really like the plugin and intend to use it.

0 Likes

#43

@facelessuser: Hey, no problem at all! I welcome your comments and assistance :wink:

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 :laughing: )

Keep the commentary coming! Andy.

0 Likes

#44

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…?

0 Likes