Sublime Forum

Copy text with highlighted colors

#21

Just thought I would let you know. I had this same issue with my Plist to Json convert plugin.

Python is full of neat little tricks like that. Line numbers would be great, it would be cool if you could use the theme color for line numbers if it is available as well.

<key>gutterForeground</key>
0 Likes

#22

@facelessuser: Good to be aware of a potential issue :wink:

An alternative version with line numbers shouldn’t be too tricky - reading gutterForeground is straight-forward as well. I would wrap everything in ol/li items. Instead of just inserting
I would close, and open, a new ‘li’. But I would also need to close the current ‘span’ and re-open it with the same colour. Not a problem really :smile:

I’ll probably need to tweak (reset) the css-behaviour for block-li items as well. (Just thinking out loud… )

Andy.

0 Likes

#23

Nice when things work out within 20 minutes :smiley: . Have to test a bit though. Andy.


0 Likes

#24

Here are some suggestions that I think would be great.

Currently if the view does not exist on disk, the generation of the HTML will fail because you rely on the file name path from the view. I would suggest instead of doing that, maybe just generate temporary html and auto-open it in a web browser (there is a Markdown plugin that does something similar on Package Control). That way you can save the HTML if you want, or just copy and paste from the web page into en email or whatever.

Another suggestion is, I might code with a black background etc, but if I want to copy and paste colorized code to an email, I might want to use a different white background theme. So maybe add a setting:

// false to use the current color theme in use or a string to use the desired alternate color theme(relative to Packages)
"use_alternate_print_color_theme": "User/MyColorTheme.tmTheme"

Anyways, I hope that makes sense.

0 Likes

#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