Home Download Buy Blog Forum Support

Copy text with highlighted colors

Copy text with highlighted colors

Postby Jacobs on Tue Jan 17, 2012 10:28 am

Hi, I know this is probably pretty perverse, but is there some way to copy text out of sublime including the highlighted colors?

My motivation is that I'd like to paste code samples into HTML mail or rich text editor with the highlighting for clarity and readability...

Thanks for any hints.
Jacobs
 
Posts: 4
Joined: Mon Jan 16, 2012 8:35 am

Re: Copy text with highlighted colors

Postby Rozza on Tue Jan 17, 2012 12:01 pm

Yeah I need the same as they are handy for presentations.

Guess I'll have to port https://github.com/drnic/copy-as-rtf-tmbundle - so it'll be mac only..
Rozza
 
Posts: 4
Joined: Tue Jan 17, 2012 12:00 pm

Re: Copy text with highlighted colors

Postby Jacobs on Tue Jan 17, 2012 1:22 pm

That'd do :-) Although platform independent version would be even better...
Jacobs
 
Posts: 4
Joined: Mon Jan 16, 2012 8:35 am

Re: Copy text with highlighted colors

Postby Rozza on Tue Jan 24, 2012 1:08 pm

Perhaps using the pygments library would work cross platform:

https://bitbucket.org/asmodai/pygments-plugin/src

Unfortunately, I cant get that plugin to work - any ideas?
Rozza
 
Posts: 4
Joined: Tue Jan 17, 2012 12:00 pm

Re: Copy text with highlighted colors

Postby dhergert on Fri Apr 06, 2012 5:42 pm

I would very much like to see this functionality.

See viewtopic.php?f=3&t=1871 (in Tech Support) for related information.
dhergert
 
Posts: 2
Joined: Fri Apr 06, 2012 5:29 pm

Re: Copy text with highlighted colors

Postby agibsonsw on Sat Apr 07, 2012 4:56 pm

I'm interested in this topic, although I'm not sure how far I might pursue it at this stage. There are a couple of available options currently:

Copy the text to another editor, which has a print - or print/save to HTML - option. Within this other editor, chose a theme which is similar to your chosen ST one;
There is an (online or downloadable) pigmentizer tool. This would need a lot of customizing to match your chosen theme;
There is a Pygmentize plugin for ST, which presumably enables the previous tool from within ST;
There is a copy-as-rtf bundle, but this is for TextMate and might be tricky to port. In particular, it isn't written in Python (Perl?).

I welcome comments on the following approach:
Parse the attached theme (as XML or JSON) to create a dictionary of scopes and colours;
Read the chosen font, and perhaps other ST settings - such as line-padding;
Parse the document using scope_name, extract_scope, substr to create HTML DIVs, with the scope name used as class-names;
Create and attach, or insert, a stylesheet created from the dictionary obtained in step one.

The main obstacle I foresee is that the scopes extracted from the theme file won't precisely match the scope obtained from 'extract_scope'. So it might involve some complicated use of 'score_selector'. And I would need to handle line breaks and tabs, and use html entities for certain characters.

I welcome suggestions, opinions on this topic. Andy.
"I'm here to save your life. But if I'm going to do that, I'll need total uninanonynymity." Me Myself & Irene.
agibsonsw
 
Posts: 901
Joined: Fri Jan 27, 2012 9:11 pm

Re: Copy text with highlighted colors

Postby agibsonsw on Sun Apr 08, 2012 5:50 pm

Well I'm making some progress with this ;) ; the attached screenshot is an HTML document generated from a keyboard combination from within ST.

But, as you can see, I'm losing tabs and newlines. I'm using 'scope_name' to extend a region which contains the same scope, and then 'substr' to grab the regions text. How can I retain the tabs and newlines? :?:

I also need to retrieve the background and foreground colours (from the theme file) and escape HTML entities, but I know how to do this bit :)

Code: Select all
import sublime, sublime_plugin
from xml.dom import minidom

class PrintHtmlCommand(sublime_plugin.TextCommand):
   def run(self, edit):
      self.colours = {}
      self.the_content = []
      path_packages = sublime.packages_path()
      settings = sublime.load_settings('Preferences.sublime-settings')
      colour_scheme = settings.get('color_scheme')
      font_size = settings.get('font_size') or 10
      font_face = settings.get('font_face') or 'Consolas'
      # print locals()
      # print path_packages # need to remove 'Packages'
      doc = minidom.parse(path_packages + '\\User\\Andy.tmTheme')      # use os.path.sep
      the_dict = doc.getElementsByTagName('dict')[0]
      the_array = the_dict.getElementsByTagName('array')[0]
      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
      size = curr_view.size()
      pt = 0; end = 1
      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 = curr_view.scope_name(pt).strip()
         if self.colours.has_key(the_key):
            the_colour = self.colours[the_key]
         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
         self.the_content.append((curr_view.substr(region), curr_view.scope_name(pt),
            the_colour))
         pt = end
         end = pt + 1
      the_html = open('test.html', 'w')         # defaults to Packages\User currently
      the_html.write('<html>\n<head>\n<style type=\"text/css\">\n')
      the_html.write('\tdiv { display: inline-block; }\n')
      the_html.write('</style>\n</head>\n<body>\n')

      for divs in self.the_content:
         the_html.write('<div style=\"color:' + divs[2] + '\">')
         the_html.write(divs[0] + '</div>\n')
      the_html.write('</body>\n</html>')
      the_html.close()

BTW The above code won't run for you currently, unless you manually change the theme-filename within the code.
Attachments
SThtml1.png
SThtml1.png (49.25 KiB) Viewed 8385 times
"I'm here to save your life. But if I'm going to do that, I'll need total uninanonynymity." Me Myself & Irene.
agibsonsw
 
Posts: 901
Joined: Fri Jan 27, 2012 9:11 pm

Re: Copy text with highlighted colors

Postby agibsonsw on Sun Apr 08, 2012 6:04 pm

Actually, no worries ;) (for the minute). substr is capturing the newlines and tabs, but I need to convert them to <br /> and ' ' (four spaces) within the HTML.
"I'm here to save your life. But if I'm going to do that, I'll need total uninanonynymity." Me Myself & Irene.
agibsonsw
 
Posts: 901
Joined: Fri Jan 27, 2012 9:11 pm

Re: Copy text with highlighted colors

Postby agibsonsw on Sun Apr 08, 2012 6:57 pm

Making progress ;) - see screenshot. (Although you may not like my colours!)
Attachments
SThtml2.png
SThtml2.png (43.44 KiB) Viewed 8370 times
"I'm here to save your life. But if I'm going to do that, I'll need total uninanonynymity." Me Myself & Irene.
agibsonsw
 
Posts: 901
Joined: Fri Jan 27, 2012 9:11 pm

Re: Copy text with highlighted colors

Postby agibsonsw on Sun Apr 08, 2012 10:08 pm

Below is a working version if anyone would like to give it a try ;) . There are two screenshots below to compare the ST and HTML version.

Windows only at the moment!
Assign it a key-binding and it will create an HTML file version of the current view/file;
The HTML will be saved in the same folder as 'yourfilename_parsed.html';
It works with tabs or four spaces at the moment. I should be able to modify it to read, and adjust to, your preference for this setting;
I haven't tried it with any HUGE files yet - it would probably need re-drafting to write each line as it parses the text and, sensibly, to work in a different thread.

Code: Select all
import sublime, sublime_plugin
from xml.dom import minidom
import re
from os import path

class PrintHtmlCommand(sublime_plugin.TextCommand):
   def run(self, edit):
      self.colours = {}
      self.the_content = []
      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 = colour_scheme.replace('Packages', '')
      font_size = settings.get('font_size') or 10
      font_face = settings.get('font_face') or 'Consolas'
      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 = ''
      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()
         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
      size = curr_view.size()
      pt = 0; end = 1
      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 = curr_view.scope_name(pt).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;' * 4)
         tidied_text = tidied_text.replace('    ', '&nbsp;' * 4)
         tidied_text = tidied_text.replace('\n', '<br>')

         self.the_content.append((tidied_text, curr_view.scope_name(pt), the_colour))
         pt = end
         end = pt + 1
      curr_file = curr_view.file_name()
      head, tail = path.split(curr_file)
      fname, ext = path.splitext(tail)
      print head, tail, fname
      the_html = open(head + '\\' + fname + '_parsed.html', 'w')         # defaults to Packages\User currently
      the_html.write('<html>\n<head>\n<style type=\"text/css\">\n')
      the_html.write('\tspan { display: inline; border: 0; margin: 0; padding: 0; }\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, \"Times New Roman\";')
      the_html.write('\n}\n')
      the_html.write('</style>\n</head>\n<body>\n')

      for spans in self.the_content:
         the_html.write('<span style=\"color:' + spans[2] + '\">')
         the_html.write(spans[0] + '</span>')
      the_html.write('</body>\n</html>')
      the_html.close()

I should put it on GitHub at some point, but it's still at an early stage.

Things for me to consider:
1) Parsing the theme file once and persisting a dictionary of colour values.
2) If I, instead, code to create a separate stylesheet, and use (a version of) the scopes as class-names, then it would be possible for you to tweak the stylesheet. (A biggish job ;) )
3) Perhaps giving an opportunity to change the name and location of the generated file. (Don't think this is necessary at this stage.)
4) Adapt for Apple, linux - probably need a little guidance with this;
5) More error handling.
Attachments
SThtml4.png
HTML version
SThtml4.png (34.68 KiB) Viewed 8353 times
SThtml5.png
ST version
SThtml5.png (30.63 KiB) Viewed 8365 times
"I'm here to save your life. But if I'm going to do that, I'll need total uninanonynymity." Me Myself & Irene.
agibsonsw
 
Posts: 901
Joined: Fri Jan 27, 2012 9:11 pm

Next

Return to Ideas and Feature Requests

Who is online

Users browsing this forum: Google [Bot] and 8 guests