##// END OF EJS Templates
Added code highlighting support in wiki, using this syntax:...
Jean-Philippe Lang -
r699:42db3cac06ad
parent child
Show More
@@ -1,27 +1,31
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to(l(:label_page_index), {:action => 'special', :page => 'Page_index'}, :class => 'icon icon-index') %>
2 <%= link_to(l(:label_page_index), {:action => 'special', :page => 'Page_index'}, :class => 'icon icon-index') %>
3 </div>
3 </div>
4
4
5 <h2><%= @page.pretty_title %></h2>
5 <h2><%= @page.pretty_title %></h2>
6
6
7 <% form_for :content, @content, :url => {:action => 'edit', :page => @page.title}, :html => {:id => 'wiki_form'} do |f| %>
7 <% form_for :content, @content, :url => {:action => 'edit', :page => @page.title}, :html => {:id => 'wiki_form'} do |f| %>
8 <%= f.hidden_field :version %>
8 <%= f.hidden_field :version %>
9 <%= error_messages_for 'content' %>
9 <%= error_messages_for 'content' %>
10 <div class="contextual">
10 <div class="contextual">
11 <%= l(:setting_text_formatting) %>:
11 <%= l(:setting_text_formatting) %>:
12 <%= link_to l(:label_help), {:controller => 'help', :ctrl => 'wiki', :page => 'syntax' },
12 <%= link_to l(:label_help), {:controller => 'help', :ctrl => 'wiki', :page => 'syntax' },
13 :onclick => "window.open('#{ url_for :controller => 'help', :ctrl => 'wiki', :page => 'syntax' }', '', 'resizable=yes, location=no, width=300, height=500, menubar=no, status=no, scrollbars=yes'); return false;" %>
13 :onclick => "window.open('#{ url_for :controller => 'help', :ctrl => 'wiki', :page => 'syntax' }', '', 'resizable=yes, location=no, width=300, height=500, menubar=no, status=no, scrollbars=yes'); return false;" %>
14 </div>
14 </div>
15 <p><%= f.text_area :text, :cols => 100, :rows => 25, :class => 'wiki-edit' %></p>
15 <p><%= f.text_area :text, :cols => 100, :rows => 25, :class => 'wiki-edit' %></p>
16 <p><label><%= l(:field_comments) %></label><br /><%= f.text_field :comments, :size => 120 %></p>
16 <p><label><%= l(:field_comments) %></label><br /><%= f.text_field :comments, :size => 120 %></p>
17 <p><%= submit_tag l(:button_save) %>
17 <p><%= submit_tag l(:button_save) %>
18 <%= link_to_remote l(:label_preview),
18 <%= link_to_remote l(:label_preview),
19 { :url => { :controller => 'wiki', :action => 'preview', :id => @project, :page => @page.title },
19 { :url => { :controller => 'wiki', :action => 'preview', :id => @project, :page => @page.title },
20 :method => 'post',
20 :method => 'post',
21 :update => 'preview',
21 :update => 'preview',
22 :with => "Form.serialize('wiki_form')"
22 :with => "Form.serialize('wiki_form')"
23 } %></p>
23 } %></p>
24 <%= wikitoolbar_for 'content_text' %>
24 <%= wikitoolbar_for 'content_text' %>
25 <% end %>
25 <% end %>
26
26
27 <div id="preview" class="wiki"></div>
27 <div id="preview" class="wiki"></div>
28
29 <% content_for :header_tags do %>
30 <%= stylesheet_link_tag 'scm' %>
31 <% end %>
@@ -1,39 +1,43
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to_if_authorized(l(:button_edit), {:action => 'edit', :page => @page.title}, :class => 'icon icon-edit') if @content.version == @page.content.version %>
2 <%= link_to_if_authorized(l(:button_edit), {:action => 'edit', :page => @page.title}, :class => 'icon icon-edit') if @content.version == @page.content.version %>
3 <%= link_to_if_authorized(l(:button_delete), {:action => 'destroy', :page => @page.title}, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') %>
3 <%= link_to_if_authorized(l(:button_delete), {:action => 'destroy', :page => @page.title}, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') %>
4 <%= link_to_if_authorized(l(:button_rollback), {:action => 'edit', :page => @page.title, :version => @content.version }, :class => 'icon icon-cancel') if @content.version < @page.content.version %>
4 <%= link_to_if_authorized(l(:button_rollback), {:action => 'edit', :page => @page.title, :version => @content.version }, :class => 'icon icon-cancel') if @content.version < @page.content.version %>
5 <%= link_to(l(:label_history), {:action => 'history', :page => @page.title}, :class => 'icon icon-history') %>
5 <%= link_to(l(:label_history), {:action => 'history', :page => @page.title}, :class => 'icon icon-history') %>
6 <%= link_to(l(:label_page_index), {:action => 'special', :page => 'Page_index'}, :class => 'icon icon-index') %>
6 <%= link_to(l(:label_page_index), {:action => 'special', :page => 'Page_index'}, :class => 'icon icon-index') %>
7 </div>
7 </div>
8
8
9 <% if @content.version != @page.content.version %>
9 <% if @content.version != @page.content.version %>
10 <p>
10 <p>
11 <%= link_to(('&#171; ' + l(:label_previous)), :action => 'index', :page => @page.title, :version => (@content.version - 1)) + " - " if @content.version > 1 %>
11 <%= link_to(('&#171; ' + l(:label_previous)), :action => 'index', :page => @page.title, :version => (@content.version - 1)) + " - " if @content.version > 1 %>
12 <%= "#{l(:label_version)} #{@content.version}/#{@page.content.version}" %>
12 <%= "#{l(:label_version)} #{@content.version}/#{@page.content.version}" %>
13 <%= '(' + link_to('diff', :controller => 'wiki', :action => 'diff', :page => @page.title, :version => @content.version) + ')' if @content.version > 1 %> -
13 <%= '(' + link_to('diff', :controller => 'wiki', :action => 'diff', :page => @page.title, :version => @content.version) + ')' if @content.version > 1 %> -
14 <%= link_to((l(:label_next) + ' &#187;'), :action => 'index', :page => @page.title, :version => (@content.version + 1)) + " - " if @content.version < @page.content.version %>
14 <%= link_to((l(:label_next) + ' &#187;'), :action => 'index', :page => @page.title, :version => (@content.version + 1)) + " - " if @content.version < @page.content.version %>
15 <%= link_to(l(:label_current_version), :action => 'index', :page => @page.title) %>
15 <%= link_to(l(:label_current_version), :action => 'index', :page => @page.title) %>
16 <br />
16 <br />
17 <em><%= @content.author ? @content.author.name : "anonyme" %>, <%= format_time(@content.updated_on) %> </em><br />
17 <em><%= @content.author ? @content.author.name : "anonyme" %>, <%= format_time(@content.updated_on) %> </em><br />
18 <%=h @content.comments %>
18 <%=h @content.comments %>
19 </p>
19 </p>
20 <hr />
20 <hr />
21 <% end %>
21 <% end %>
22
22
23 <%= render(:partial => "wiki/content", :locals => {:content => @content}) %>
23 <%= render(:partial => "wiki/content", :locals => {:content => @content}) %>
24
24
25 <%= link_to_attachments @page.attachments, :delete_url => (authorize_for('wiki', 'destroy_attachment') ? {:controller => 'wiki', :action => 'destroy_attachment', :page => @page.title} : nil) %>
25 <%= link_to_attachments @page.attachments, :delete_url => (authorize_for('wiki', 'destroy_attachment') ? {:controller => 'wiki', :action => 'destroy_attachment', :page => @page.title} : nil) %>
26
26
27 <div class="contextual">
27 <div class="contextual">
28 <%= l(:label_export_to) %>
28 <%= l(:label_export_to) %>
29 <%= link_to 'HTML', {:export => 'html', :version => @content.version}, :class => 'icon icon-html' %>,
29 <%= link_to 'HTML', {:export => 'html', :version => @content.version}, :class => 'icon icon-html' %>,
30 <%= link_to 'TXT', {:export => 'txt', :version => @content.version}, :class => 'icon icon-txt' %>
30 <%= link_to 'TXT', {:export => 'txt', :version => @content.version}, :class => 'icon icon-txt' %>
31 </div>
31 </div>
32
32
33 <% if authorize_for('wiki', 'add_attachment') %>
33 <% if authorize_for('wiki', 'add_attachment') %>
34 <p><%= toggle_link l(:label_attachment_new), "add_attachment_form" %></p>
34 <p><%= toggle_link l(:label_attachment_new), "add_attachment_form" %></p>
35 <% form_tag({ :controller => 'wiki', :action => 'add_attachment', :page => @page.title }, :multipart => true, :class => "tabular", :id => "add_attachment_form", :style => "display:none;") do %>
35 <% form_tag({ :controller => 'wiki', :action => 'add_attachment', :page => @page.title }, :multipart => true, :class => "tabular", :id => "add_attachment_form", :style => "display:none;") do %>
36 <%= render :partial => 'attachments/form' %>
36 <%= render :partial => 'attachments/form' %>
37 <%= submit_tag l(:button_add) %>
37 <%= submit_tag l(:button_add) %>
38 <% end %>
38 <% end %>
39 <% end %>
39 <% end %>
40
41 <% content_for :header_tags do %>
42 <%= stylesheet_link_tag 'scm' %>
43 <% end %>
@@ -1,79 +1,95
1 require 'redcloth'
1 require 'redcloth'
2 require 'coderay'
2
3
3 module Redmine
4 module Redmine
4 module WikiFormatting
5 module WikiFormatting
5
6
6 private
7 private
7
8
8 class TextileFormatter < RedCloth
9 class TextileFormatter < RedCloth
9 RULES = [:inline_auto_link, :inline_auto_mailto, :textile ]
10 RULES = [:inline_auto_link, :inline_auto_mailto, :textile ]
10
11
11 def initialize(*args)
12 def initialize(*args)
12 super
13 super
13 self.hard_breaks=true
14 self.hard_breaks=true
14 end
15 end
15
16
16 def to_html
17 def to_html
17 super(*RULES).to_s
18 super(*RULES).to_s
18 end
19 end
19
20
20 private
21 private
21
22
22 # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
23 # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
23 # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
24 # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
24 def hard_break( text )
25 def hard_break( text )
25 text.gsub!( /(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
26 text.gsub!( /(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
26 end
27 end
27
28
29 # Patch to add code highlighting support to RedCloth
30 def smooth_offtags( text )
31 unless @pre_list.empty?
32 ## replace <pre> content
33 text.gsub!(/<redpre#(\d+)>/) do
34 content = @pre_list[$1.to_i]
35 if content.match(/<code\s+class="(\w+)">\s?(.+)/m)
36 content = "<code class=\"#{$1} CodeRay\">" +
37 CodeRay.scan($2, $1).html(:escape => false, :line_numbers => :inline)
38 end
39 content
40 end
41 end
42 end
43
28 AUTO_LINK_RE = %r{
44 AUTO_LINK_RE = %r{
29 ( # leading text
45 ( # leading text
30 <\w+.*?>| # leading HTML tag, or
46 <\w+.*?>| # leading HTML tag, or
31 [^=<>!:'"/]| # leading punctuation, or
47 [^=<>!:'"/]| # leading punctuation, or
32 ^ # beginning of line
48 ^ # beginning of line
33 )
49 )
34 (
50 (
35 (?:https?://)| # protocol spec, or
51 (?:https?://)| # protocol spec, or
36 (?:www\.) # www.*
52 (?:www\.) # www.*
37 )
53 )
38 (
54 (
39 [-\w]+ # subdomain or domain
55 [-\w]+ # subdomain or domain
40 (?:\.[-\w]+)* # remaining subdomains or domain
56 (?:\.[-\w]+)* # remaining subdomains or domain
41 (?::\d+)? # port
57 (?::\d+)? # port
42 (?:/(?:(?:[~\w\+%-]|(?:[,.;:][^\s$]))+)?)* # path
58 (?:/(?:(?:[~\w\+%-]|(?:[,.;:][^\s$]))+)?)* # path
43 (?:\?[\w\+%&=.;-]+)? # query string
59 (?:\?[\w\+%&=.;-]+)? # query string
44 (?:\#[\w\-]*)? # trailing anchor
60 (?:\#[\w\-]*)? # trailing anchor
45 )
61 )
46 ([[:punct:]]|\s|<|$) # trailing text
62 ([[:punct:]]|\s|<|$) # trailing text
47 }x unless const_defined?(:AUTO_LINK_RE)
63 }x unless const_defined?(:AUTO_LINK_RE)
48
64
49 # Turns all urls into clickable links (code from Rails).
65 # Turns all urls into clickable links (code from Rails).
50 def inline_auto_link(text)
66 def inline_auto_link(text)
51 text.gsub!(AUTO_LINK_RE) do
67 text.gsub!(AUTO_LINK_RE) do
52 all, a, b, c, d = $&, $1, $2, $3, $4
68 all, a, b, c, d = $&, $1, $2, $3, $4
53 if a =~ /<a\s/i || a =~ /![<>=]?/
69 if a =~ /<a\s/i || a =~ /![<>=]?/
54 # don't replace URL's that are already linked
70 # don't replace URL's that are already linked
55 # and URL's prefixed with ! !> !< != (textile images)
71 # and URL's prefixed with ! !> !< != (textile images)
56 all
72 all
57 else
73 else
58 text = b + c
74 text = b + c
59 %(#{a}<a href="#{b=="www."?"http://www.":b}#{c}">#{text}</a>#{d})
75 %(#{a}<a href="#{b=="www."?"http://www.":b}#{c}">#{text}</a>#{d})
60 end
76 end
61 end
77 end
62 end
78 end
63
79
64 # Turns all email addresses into clickable links (code from Rails).
80 # Turns all email addresses into clickable links (code from Rails).
65 def inline_auto_mailto(text)
81 def inline_auto_mailto(text)
66 text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
82 text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
67 text = $1
83 text = $1
68 %{<a href="mailto:#{$1}" class="email">#{text}</a>}
84 %{<a href="mailto:#{$1}" class="email">#{text}</a>}
69 end
85 end
70 end
86 end
71 end
87 end
72
88
73 public
89 public
74
90
75 def self.to_html(text, options = {})
91 def self.to_html(text, options = {})
76 TextileFormatter.new(text).to_html
92 TextileFormatter.new(text).to_html
77 end
93 end
78 end
94 end
79 end
95 end
@@ -1,123 +1,123
1
1
2 div.action_M { background: #fd8 }
2 div.action_M { background: #fd8 }
3 div.action_D { background: #f88 }
3 div.action_D { background: #f88 }
4 div.action_A { background: #bfb }
4 div.action_A { background: #bfb }
5
5
6
6
7 tr.spacing {
7 tr.spacing {
8 border: 1px solid #d7d7d7;
8 border: 1px solid #d7d7d7;
9 }
9 }
10
10
11 .line-num {
11 .line-num {
12 border: 1px solid #d7d7d7;
12 border: 1px solid #d7d7d7;
13 font-size: 0.8em;
13 font-size: 0.8em;
14 text-align: right;
14 text-align: right;
15 width: 3em;
15 width: 3em;
16 padding-right: 3px;
16 padding-right: 3px;
17 }
17 }
18
18
19 .line-code {
19 .line-code {
20 font-size: 1.4em;
20 font-size: 1.4em;
21 }
21 }
22
22
23 table.list thead th.list-filename {
23 table.list thead th.list-filename {
24 background-color: #ddc;
24 background-color: #ddc;
25 font-weight: bolder;
25 font-weight: bolder;
26 text-align: left;
26 text-align: left;
27 }
27 }
28
28
29
29
30 /************* Coderay styles *************/
30 /************* Coderay styles *************/
31
31
32 .CodeRay {
32 table.CodeRay {
33 background-color: #fafafa;
33 background-color: #fafafa;
34 }
34 }
35 .CodeRay pre { margin: 0px }
35 .CodeRay pre { margin: 0px }
36
36
37 span.CodeRay { white-space: pre; border: 0px; padding: 2px }
37 span.CodeRay { white-space: pre; border: 0px; padding: 2px }
38
38
39 .CodeRay .no { padding: 0px 4px }
39 .CodeRay .no { padding: 0px 4px }
40 .CodeRay .code { width: 100% }
40 .CodeRay .code { width: 100% }
41
41
42 ol.CodeRay { font-size: 10pt }
42 ol.CodeRay { font-size: 10pt }
43 ol.CodeRay li { white-space: pre }
43 ol.CodeRay li { white-space: pre }
44
44
45 .CodeRay .code pre { overflow: auto }
45 .CodeRay .code pre { overflow: auto }
46
46
47 .CodeRay .debug { color:white ! important; background:blue ! important; }
47 .CodeRay .debug { color:white ! important; background:blue ! important; }
48
48
49 .CodeRay .af { color:#00C }
49 .CodeRay .af { color:#00C }
50 .CodeRay .an { color:#007 }
50 .CodeRay .an { color:#007 }
51 .CodeRay .av { color:#700 }
51 .CodeRay .av { color:#700 }
52 .CodeRay .aw { color:#C00 }
52 .CodeRay .aw { color:#C00 }
53 .CodeRay .bi { color:#509; font-weight:bold }
53 .CodeRay .bi { color:#509; font-weight:bold }
54 .CodeRay .c { color:#666; }
54 .CodeRay .c { color:#666; }
55
55
56 .CodeRay .ch { color:#04D }
56 .CodeRay .ch { color:#04D }
57 .CodeRay .ch .k { color:#04D }
57 .CodeRay .ch .k { color:#04D }
58 .CodeRay .ch .dl { color:#039 }
58 .CodeRay .ch .dl { color:#039 }
59
59
60 .CodeRay .cl { color:#B06; font-weight:bold }
60 .CodeRay .cl { color:#B06; font-weight:bold }
61 .CodeRay .co { color:#036; font-weight:bold }
61 .CodeRay .co { color:#036; font-weight:bold }
62 .CodeRay .cr { color:#0A0 }
62 .CodeRay .cr { color:#0A0 }
63 .CodeRay .cv { color:#369 }
63 .CodeRay .cv { color:#369 }
64 .CodeRay .df { color:#099; font-weight:bold }
64 .CodeRay .df { color:#099; font-weight:bold }
65 .CodeRay .di { color:#088; font-weight:bold }
65 .CodeRay .di { color:#088; font-weight:bold }
66 .CodeRay .dl { color:black }
66 .CodeRay .dl { color:black }
67 .CodeRay .do { color:#970 }
67 .CodeRay .do { color:#970 }
68 .CodeRay .ds { color:#D42; font-weight:bold }
68 .CodeRay .ds { color:#D42; font-weight:bold }
69 .CodeRay .e { color:#666; font-weight:bold }
69 .CodeRay .e { color:#666; font-weight:bold }
70 .CodeRay .en { color:#800; font-weight:bold }
70 .CodeRay .en { color:#800; font-weight:bold }
71 .CodeRay .er { color:#F00; background-color:#FAA }
71 .CodeRay .er { color:#F00; background-color:#FAA }
72 .CodeRay .ex { color:#F00; font-weight:bold }
72 .CodeRay .ex { color:#F00; font-weight:bold }
73 .CodeRay .fl { color:#60E; font-weight:bold }
73 .CodeRay .fl { color:#60E; font-weight:bold }
74 .CodeRay .fu { color:#06B; font-weight:bold }
74 .CodeRay .fu { color:#06B; font-weight:bold }
75 .CodeRay .gv { color:#d70; font-weight:bold }
75 .CodeRay .gv { color:#d70; font-weight:bold }
76 .CodeRay .hx { color:#058; font-weight:bold }
76 .CodeRay .hx { color:#058; font-weight:bold }
77 .CodeRay .i { color:#00D; font-weight:bold }
77 .CodeRay .i { color:#00D; font-weight:bold }
78 .CodeRay .ic { color:#B44; font-weight:bold }
78 .CodeRay .ic { color:#B44; font-weight:bold }
79
79
80 .CodeRay .il { background: #eee }
80 .CodeRay .il { background: #eee }
81 .CodeRay .il .il { background: #ddd }
81 .CodeRay .il .il { background: #ddd }
82 .CodeRay .il .il .il { background: #ccc }
82 .CodeRay .il .il .il { background: #ccc }
83 .CodeRay .il .idl { font-weight: bold; color: #888 }
83 .CodeRay .il .idl { font-weight: bold; color: #888 }
84
84
85 .CodeRay .in { color:#B2B; font-weight:bold }
85 .CodeRay .in { color:#B2B; font-weight:bold }
86 .CodeRay .iv { color:#33B }
86 .CodeRay .iv { color:#33B }
87 .CodeRay .la { color:#970; font-weight:bold }
87 .CodeRay .la { color:#970; font-weight:bold }
88 .CodeRay .lv { color:#963 }
88 .CodeRay .lv { color:#963 }
89 .CodeRay .oc { color:#40E; font-weight:bold }
89 .CodeRay .oc { color:#40E; font-weight:bold }
90 .CodeRay .of { color:#000; font-weight:bold }
90 .CodeRay .of { color:#000; font-weight:bold }
91 .CodeRay .op { }
91 .CodeRay .op { }
92 .CodeRay .pc { color:#038; font-weight:bold }
92 .CodeRay .pc { color:#038; font-weight:bold }
93 .CodeRay .pd { color:#369; font-weight:bold }
93 .CodeRay .pd { color:#369; font-weight:bold }
94 .CodeRay .pp { color:#579 }
94 .CodeRay .pp { color:#579 }
95 .CodeRay .pt { color:#339; font-weight:bold }
95 .CodeRay .pt { color:#339; font-weight:bold }
96 .CodeRay .r { color:#080; font-weight:bold }
96 .CodeRay .r { color:#080; font-weight:bold }
97
97
98 .CodeRay .rx { background-color:#fff0ff }
98 .CodeRay .rx { background-color:#fff0ff }
99 .CodeRay .rx .k { color:#808 }
99 .CodeRay .rx .k { color:#808 }
100 .CodeRay .rx .dl { color:#404 }
100 .CodeRay .rx .dl { color:#404 }
101 .CodeRay .rx .mod { color:#C2C }
101 .CodeRay .rx .mod { color:#C2C }
102 .CodeRay .rx .fu { color:#404; font-weight: bold }
102 .CodeRay .rx .fu { color:#404; font-weight: bold }
103
103
104 .CodeRay .s { background-color:#fff0f0 }
104 .CodeRay .s { background-color:#fff0f0 }
105 .CodeRay .s .s { background-color:#ffe0e0 }
105 .CodeRay .s .s { background-color:#ffe0e0 }
106 .CodeRay .s .s .s { background-color:#ffd0d0 }
106 .CodeRay .s .s .s { background-color:#ffd0d0 }
107 .CodeRay .s .k { color:#D20 }
107 .CodeRay .s .k { color:#D20 }
108 .CodeRay .s .dl { color:#710 }
108 .CodeRay .s .dl { color:#710 }
109
109
110 .CodeRay .sh { background-color:#f0fff0 }
110 .CodeRay .sh { background-color:#f0fff0 }
111 .CodeRay .sh .k { color:#2B2 }
111 .CodeRay .sh .k { color:#2B2 }
112 .CodeRay .sh .dl { color:#161 }
112 .CodeRay .sh .dl { color:#161 }
113
113
114 .CodeRay .sy { color:#A60 }
114 .CodeRay .sy { color:#A60 }
115 .CodeRay .sy .k { color:#A60 }
115 .CodeRay .sy .k { color:#A60 }
116 .CodeRay .sy .dl { color:#630 }
116 .CodeRay .sy .dl { color:#630 }
117
117
118 .CodeRay .ta { color:#070 }
118 .CodeRay .ta { color:#070 }
119 .CodeRay .tf { color:#070; font-weight:bold }
119 .CodeRay .tf { color:#070; font-weight:bold }
120 .CodeRay .ts { color:#D70; font-weight:bold }
120 .CodeRay .ts { color:#D70; font-weight:bold }
121 .CodeRay .ty { color:#339; font-weight:bold }
121 .CodeRay .ty { color:#339; font-weight:bold }
122 .CodeRay .v { color:#036 }
122 .CodeRay .v { color:#036 }
123 .CodeRay .xt { color:#444 }
123 .CodeRay .xt { color:#444 }
@@ -1,262 +1,268
1 require "set"
1 require "set"
2
2
3 module CodeRay
3 module CodeRay
4 module Encoders
4 module Encoders
5
5
6 # = HTML Encoder
6 # = HTML Encoder
7 #
7 #
8 # This is CodeRay's most important highlighter:
8 # This is CodeRay's most important highlighter:
9 # It provides save, fast XHTML generation and CSS support.
9 # It provides save, fast XHTML generation and CSS support.
10 #
10 #
11 # == Usage
11 # == Usage
12 #
12 #
13 # require 'coderay'
13 # require 'coderay'
14 # puts CodeRay.scan('Some /code/', :ruby).html #-> a HTML page
14 # puts CodeRay.scan('Some /code/', :ruby).html #-> a HTML page
15 # puts CodeRay.scan('Some /code/', :ruby).html(:wrap => :span)
15 # puts CodeRay.scan('Some /code/', :ruby).html(:wrap => :span)
16 # #-> <span class="CodeRay"><span class="co">Some</span> /code/</span>
16 # #-> <span class="CodeRay"><span class="co">Some</span> /code/</span>
17 # puts CodeRay.scan('Some /code/', :ruby).span #-> the same
17 # puts CodeRay.scan('Some /code/', :ruby).span #-> the same
18 #
18 #
19 # puts CodeRay.scan('Some code', :ruby).html(
19 # puts CodeRay.scan('Some code', :ruby).html(
20 # :wrap => nil,
20 # :wrap => nil,
21 # :line_numbers => :inline,
21 # :line_numbers => :inline,
22 # :css => :style
22 # :css => :style
23 # )
23 # )
24 # #-> <span class="no">1</span> <span style="color:#036; font-weight:bold;">Some</span> code
24 # #-> <span class="no">1</span> <span style="color:#036; font-weight:bold;">Some</span> code
25 #
25 #
26 # == Options
26 # == Options
27 #
27 #
28 # === :escape
29 # Escape html entities
30 # Default: true
31 #
28 # === :tab_width
32 # === :tab_width
29 # Convert \t characters to +n+ spaces (a number.)
33 # Convert \t characters to +n+ spaces (a number.)
30 # Default: 8
34 # Default: 8
31 #
35 #
32 # === :css
36 # === :css
33 # How to include the styles; can be :class or :style.
37 # How to include the styles; can be :class or :style.
34 #
38 #
35 # Default: :class
39 # Default: :class
36 #
40 #
37 # === :wrap
41 # === :wrap
38 # Wrap in :page, :div, :span or nil.
42 # Wrap in :page, :div, :span or nil.
39 #
43 #
40 # You can also use Encoders::Div and Encoders::Span.
44 # You can also use Encoders::Div and Encoders::Span.
41 #
45 #
42 # Default: nil
46 # Default: nil
43 #
47 #
44 # === :line_numbers
48 # === :line_numbers
45 # Include line numbers in :table, :inline, :list or nil (no line numbers)
49 # Include line numbers in :table, :inline, :list or nil (no line numbers)
46 #
50 #
47 # Default: nil
51 # Default: nil
48 #
52 #
49 # === :line_number_start
53 # === :line_number_start
50 # Where to start with line number counting.
54 # Where to start with line number counting.
51 #
55 #
52 # Default: 1
56 # Default: 1
53 #
57 #
54 # === :bold_every
58 # === :bold_every
55 # Make every +n+-th number appear bold.
59 # Make every +n+-th number appear bold.
56 #
60 #
57 # Default: 10
61 # Default: 10
58 #
62 #
59 # === :hint
63 # === :hint
60 # Include some information into the output using the title attribute.
64 # Include some information into the output using the title attribute.
61 # Can be :info (show token type on mouse-over), :info_long (with full path)
65 # Can be :info (show token type on mouse-over), :info_long (with full path)
62 # or :debug (via inspect).
66 # or :debug (via inspect).
63 #
67 #
64 # Default: false
68 # Default: false
65 class HTML < Encoder
69 class HTML < Encoder
66
70
67 include Streamable
71 include Streamable
68 register_for :html
72 register_for :html
69
73
70 FILE_EXTENSION = 'html'
74 FILE_EXTENSION = 'html'
71
75
72 DEFAULT_OPTIONS = {
76 DEFAULT_OPTIONS = {
77 :escape => true,
73 :tab_width => 8,
78 :tab_width => 8,
74
79
75 :level => :xhtml,
80 :level => :xhtml,
76 :css => :class,
81 :css => :class,
77
82
78 :style => :cycnus,
83 :style => :cycnus,
79
84
80 :wrap => nil,
85 :wrap => nil,
81
86
82 :line_numbers => nil,
87 :line_numbers => nil,
83 :line_number_start => 1,
88 :line_number_start => 1,
84 :bold_every => 10,
89 :bold_every => 10,
85
90
86 :hint => false,
91 :hint => false,
87 }
92 }
88
93
89 helper :output, :css
94 helper :output, :css
90
95
91 attr_reader :css
96 attr_reader :css
92
97
93 protected
98 protected
94
99
95 HTML_ESCAPE = { #:nodoc:
100 HTML_ESCAPE = { #:nodoc:
96 '&' => '&amp;',
101 '&' => '&amp;',
97 '"' => '&quot;',
102 '"' => '&quot;',
98 '>' => '&gt;',
103 '>' => '&gt;',
99 '<' => '&lt;',
104 '<' => '&lt;',
100 }
105 }
101
106
102 # This was to prevent illegal HTML.
107 # This was to prevent illegal HTML.
103 # Strange chars should still be avoided in codes.
108 # Strange chars should still be avoided in codes.
104 evil_chars = Array(0x00...0x20) - [?\n, ?\t, ?\s]
109 evil_chars = Array(0x00...0x20) - [?\n, ?\t, ?\s]
105 evil_chars.each { |i| HTML_ESCAPE[i.chr] = ' ' }
110 evil_chars.each { |i| HTML_ESCAPE[i.chr] = ' ' }
106 #ansi_chars = Array(0x7f..0xff)
111 #ansi_chars = Array(0x7f..0xff)
107 #ansi_chars.each { |i| HTML_ESCAPE[i.chr] = '&#%d;' % i }
112 #ansi_chars.each { |i| HTML_ESCAPE[i.chr] = '&#%d;' % i }
108 # \x9 (\t) and \xA (\n) not included
113 # \x9 (\t) and \xA (\n) not included
109 #HTML_ESCAPE_PATTERN = /[\t&"><\0-\x8\xB-\x1f\x7f-\xff]/
114 #HTML_ESCAPE_PATTERN = /[\t&"><\0-\x8\xB-\x1f\x7f-\xff]/
110 HTML_ESCAPE_PATTERN = /[\t"&><\0-\x8\xB-\x1f]/
115 HTML_ESCAPE_PATTERN = /[\t"&><\0-\x8\xB-\x1f]/
111
116
112 TOKEN_KIND_TO_INFO = Hash.new { |h, kind|
117 TOKEN_KIND_TO_INFO = Hash.new { |h, kind|
113 h[kind] =
118 h[kind] =
114 case kind
119 case kind
115 when :pre_constant
120 when :pre_constant
116 'Predefined constant'
121 'Predefined constant'
117 else
122 else
118 kind.to_s.gsub(/_/, ' ').gsub(/\b\w/) { $&.capitalize }
123 kind.to_s.gsub(/_/, ' ').gsub(/\b\w/) { $&.capitalize }
119 end
124 end
120 }
125 }
121
126
122 TRANSPARENT_TOKEN_KINDS = [
127 TRANSPARENT_TOKEN_KINDS = [
123 :delimiter, :modifier, :content, :escape, :inline_delimiter,
128 :delimiter, :modifier, :content, :escape, :inline_delimiter,
124 ].to_set
129 ].to_set
125
130
126 # Generate a hint about the given +classes+ in a +hint+ style.
131 # Generate a hint about the given +classes+ in a +hint+ style.
127 #
132 #
128 # +hint+ may be :info, :info_long or :debug.
133 # +hint+ may be :info, :info_long or :debug.
129 def self.token_path_to_hint hint, classes
134 def self.token_path_to_hint hint, classes
130 title =
135 title =
131 case hint
136 case hint
132 when :info
137 when :info
133 TOKEN_KIND_TO_INFO[classes.first]
138 TOKEN_KIND_TO_INFO[classes.first]
134 when :info_long
139 when :info_long
135 classes.reverse.map { |kind| TOKEN_KIND_TO_INFO[kind] }.join('/')
140 classes.reverse.map { |kind| TOKEN_KIND_TO_INFO[kind] }.join('/')
136 when :debug
141 when :debug
137 classes.inspect
142 classes.inspect
138 end
143 end
139 " title=\"#{title}\""
144 " title=\"#{title}\""
140 end
145 end
141
146
142 def setup options
147 def setup options
143 super
148 super
144
149
145 @HTML_ESCAPE = HTML_ESCAPE.dup
150 @HTML_ESCAPE = HTML_ESCAPE.dup
146 @HTML_ESCAPE["\t"] = ' ' * options[:tab_width]
151 @HTML_ESCAPE["\t"] = ' ' * options[:tab_width]
147
152
153 @escape = options[:escape]
148 @opened = [nil]
154 @opened = [nil]
149 @css = CSS.new options[:style]
155 @css = CSS.new options[:style]
150
156
151 hint = options[:hint]
157 hint = options[:hint]
152 if hint and not [:debug, :info, :info_long].include? hint
158 if hint and not [:debug, :info, :info_long].include? hint
153 raise ArgumentError, "Unknown value %p for :hint; \
159 raise ArgumentError, "Unknown value %p for :hint; \
154 expected :info, :debug, false, or nil." % hint
160 expected :info, :debug, false, or nil." % hint
155 end
161 end
156
162
157 case options[:css]
163 case options[:css]
158
164
159 when :class
165 when :class
160 @css_style = Hash.new do |h, k|
166 @css_style = Hash.new do |h, k|
161 c = Tokens::ClassOfKind[k.first]
167 c = Tokens::ClassOfKind[k.first]
162 if c == :NO_HIGHLIGHT and not hint
168 if c == :NO_HIGHLIGHT and not hint
163 h[k.dup] = false
169 h[k.dup] = false
164 else
170 else
165 title = if hint
171 title = if hint
166 HTML.token_path_to_hint(hint, k[1..-1] << k.first)
172 HTML.token_path_to_hint(hint, k[1..-1] << k.first)
167 else
173 else
168 ''
174 ''
169 end
175 end
170 if c == :NO_HIGHLIGHT
176 if c == :NO_HIGHLIGHT
171 h[k.dup] = '<span%s>' % [title]
177 h[k.dup] = '<span%s>' % [title]
172 else
178 else
173 h[k.dup] = '<span%s class="%s">' % [title, c]
179 h[k.dup] = '<span%s class="%s">' % [title, c]
174 end
180 end
175 end
181 end
176 end
182 end
177
183
178 when :style
184 when :style
179 @css_style = Hash.new do |h, k|
185 @css_style = Hash.new do |h, k|
180 if k.is_a? ::Array
186 if k.is_a? ::Array
181 styles = k.dup
187 styles = k.dup
182 else
188 else
183 styles = [k]
189 styles = [k]
184 end
190 end
185 type = styles.first
191 type = styles.first
186 classes = styles.map { |c| Tokens::ClassOfKind[c] }
192 classes = styles.map { |c| Tokens::ClassOfKind[c] }
187 if classes.first == :NO_HIGHLIGHT and not hint
193 if classes.first == :NO_HIGHLIGHT and not hint
188 h[k] = false
194 h[k] = false
189 else
195 else
190 styles.shift if TRANSPARENT_TOKEN_KINDS.include? styles.first
196 styles.shift if TRANSPARENT_TOKEN_KINDS.include? styles.first
191 title = HTML.token_path_to_hint hint, styles
197 title = HTML.token_path_to_hint hint, styles
192 style = @css[*classes]
198 style = @css[*classes]
193 h[k] =
199 h[k] =
194 if style
200 if style
195 '<span%s style="%s">' % [title, style]
201 '<span%s style="%s">' % [title, style]
196 else
202 else
197 false
203 false
198 end
204 end
199 end
205 end
200 end
206 end
201
207
202 else
208 else
203 raise ArgumentError, "Unknown value %p for :css." % options[:css]
209 raise ArgumentError, "Unknown value %p for :css." % options[:css]
204
210
205 end
211 end
206 end
212 end
207
213
208 def finish options
214 def finish options
209 not_needed = @opened.shift
215 not_needed = @opened.shift
210 @out << '</span>' * @opened.size
216 @out << '</span>' * @opened.size
211 unless @opened.empty?
217 unless @opened.empty?
212 warn '%d tokens still open: %p' % [@opened.size, @opened]
218 warn '%d tokens still open: %p' % [@opened.size, @opened]
213 end
219 end
214
220
215 @out.extend Output
221 @out.extend Output
216 @out.css = @css
222 @out.css = @css
217 @out.numerize! options[:line_numbers], options
223 @out.numerize! options[:line_numbers], options
218 @out.wrap! options[:wrap]
224 @out.wrap! options[:wrap]
219
225
220 super
226 super
221 end
227 end
222
228
223 def token text, type
229 def token text, type
224 if text.is_a? ::String
230 if text.is_a? ::String
225 if text =~ /#{HTML_ESCAPE_PATTERN}/o
231 if @escape && (text =~ /#{HTML_ESCAPE_PATTERN}/o)
226 text = text.gsub(/#{HTML_ESCAPE_PATTERN}/o) { |m| @HTML_ESCAPE[m] }
232 text = text.gsub(/#{HTML_ESCAPE_PATTERN}/o) { |m| @HTML_ESCAPE[m] }
227 end
233 end
228 @opened[0] = type
234 @opened[0] = type
229 if style = @css_style[@opened]
235 if style = @css_style[@opened]
230 @out << style << text << '</span>'
236 @out << style << text << '</span>'
231 else
237 else
232 @out << text
238 @out << text
233 end
239 end
234 else
240 else
235 case text
241 case text
236 when :open
242 when :open
237 @opened[0] = type
243 @opened[0] = type
238 @out << (@css_style[@opened] || '<span>')
244 @out << (@css_style[@opened] || '<span>')
239 @opened << type
245 @opened << type
240 when :close
246 when :close
241 if @opened.empty?
247 if @opened.empty?
242 # nothing to close
248 # nothing to close
243 else
249 else
244 if $DEBUG and (@opened.size == 1 or @opened.last != type)
250 if $DEBUG and (@opened.size == 1 or @opened.last != type)
245 raise 'Malformed token stream: Trying to close a token (%p) \
251 raise 'Malformed token stream: Trying to close a token (%p) \
246 that is not open. Open are: %p.' % [type, @opened[1..-1]]
252 that is not open. Open are: %p.' % [type, @opened[1..-1]]
247 end
253 end
248 @out << '</span>'
254 @out << '</span>'
249 @opened.pop
255 @opened.pop
250 end
256 end
251 when nil
257 when nil
252 raise 'Token with nil as text was given: %p' % [[text, type]]
258 raise 'Token with nil as text was given: %p' % [[text, type]]
253 else
259 else
254 raise 'unknown token kind: %p' % text
260 raise 'unknown token kind: %p' % text
255 end
261 end
256 end
262 end
257 end
263 end
258
264
259 end
265 end
260
266
261 end
267 end
262 end
268 end
General Comments 0
You need to be logged in to leave comments. Login now