From 469dba8c6378fb62ebf12c4a2f32b6be433bb16e Mon Sep 17 00:00:00 2001 From: Kornelius Kalnbach Date: Mon, 11 Mar 2013 02:23:22 +0100 Subject: added Sass scanner (work in progress) --- lib/coderay/scanners/sass.rb | 203 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 lib/coderay/scanners/sass.rb (limited to 'lib/coderay/scanners/sass.rb') diff --git a/lib/coderay/scanners/sass.rb b/lib/coderay/scanners/sass.rb new file mode 100644 index 0000000..89d7737 --- /dev/null +++ b/lib/coderay/scanners/sass.rb @@ -0,0 +1,203 @@ +module CodeRay +module Scanners + + # A scanner for Sass. + class Sass < CSS + + register_for :sass + file_extension 'sass' + + SASS_FUNCTION = /(?:inline-image|linear-gradient|color-stops|mix|lighten|darken|rotate|image-url|image-width|image-height|sprite|sprite-url|sprite-path|sprite-file|sprite-map|sprite-position|unquote|join|round|ceil|floor|nth)/ + + STRING_CONTENT_PATTERN = { + "'" => /(?:[^\n\'\#]+|\\\n|#{RE::Escape}|#(?!\{))+/, + '"' => /(?:[^\n\"\#]+|\\\n|#{RE::Escape}|#(?!\{))+/, + } + + protected + + def setup + @state = :initial + @value_expected = false + end + + def scan_tokens encoder, options + states = Array(options[:state] || @state) + string_delimiter = nil + value_expected = @value_expected + + until eos? + + if match = scan(/\s+/) + encoder.text_token match, :space + value_expected = false if match.index(/\n/) + + elsif states.last == :sass_inline && (match = scan(/\}/)) + encoder.text_token match, :inline_delimiter + encoder.end_group :inline + states.pop + + elsif case states.last + when :initial, :media, :sass_inline + if match = scan(/(?>#{RE::Ident})(?!\()/ox) + encoder.text_token match, value_expected ? :value : (check(/.*:/) ? :key : :type) + next + elsif !value_expected && (match = scan(/\*/)) + encoder.text_token match, :type + next + elsif match = scan(RE::Class) + encoder.text_token match, :class + next + elsif match = scan(RE::Id) + encoder.text_token match, :constant + next + elsif match = scan(RE::PseudoClass) + encoder.text_token match, :pseudo_class + next + elsif match = scan(RE::AttributeSelector) + # TODO: Improve highlighting inside of attribute selectors. + encoder.text_token match[0,1], :operator + encoder.text_token match[1..-2], :attribute_name if match.size > 2 + encoder.text_token match[-1,1], :operator if match[-1] == ?] + next + elsif match = scan(/(\=|@mixin +)#{RE::Ident}/o) + encoder.text_token match, :function + next + elsif match = scan(/@media/) + encoder.text_token match, :directive + # states.push :media_before_name + next + end + + when :block + if match = scan(/(?>#{RE::Ident})(?!\()/ox) + if value_expected + encoder.text_token match, :value + else + encoder.text_token match, :key + end + next + end + + when :string + if match = scan(STRING_CONTENT_PATTERN[string_delimiter]) + encoder.text_token match, :content + elsif match = scan(/['"]/) + encoder.text_token match, :delimiter + encoder.end_group :string + string_delimiter = nil + states.pop + elsif match = scan(/#\{/) + encoder.begin_group :inline + encoder.text_token match, :inline_delimiter + states.push :sass_inline + elsif match = scan(/ \\ | $ /x) + encoder.end_group state + encoder.text_token match, :error unless match.empty? + states.pop + else + raise_inspect "else case #{string_delimiter} reached; %p not handled." % peek(1), encoder + end + + else + #:nocov: + raise_inspect 'Unknown state', encoder + #:nocov: + + end + + elsif match = scan(/\$#{RE::Ident}/o) + encoder.text_token match, :variable + next + + elsif match = scan(/&/) + encoder.text_token match, :local_variable + + elsif match = scan(/\+#{RE::Ident}/o) + encoder.text_token match, :include + value_expected = true + + elsif match = scan(/\/\*(?:.*?\*\/|.*)|\/\/.*/) + encoder.text_token match, :comment + + elsif match = scan(/#\{/) + encoder.begin_group :inline + encoder.text_token match, :inline_delimiter + states.push :sass_inline + + elsif match = scan(/\{/) + value_expected = false + encoder.text_token match, :operator + states.push :block + + elsif match = scan(/\}/) + value_expected = false + encoder.text_token match, :operator + if states.last == :block || states.last == :media + states.pop + end + + elsif match = scan(/['"]/) + encoder.begin_group :string + string_delimiter = match + encoder.text_token match, :delimiter + states.push :string + + elsif match = scan(/#{SASS_FUNCTION}/o) + encoder.text_token match, :predefined + + elsif match = scan(/#{RE::Function}/o) + encoder.begin_group :function + start = match[/^[-\w]+\(/] + encoder.text_token start, :delimiter + if match[-1] == ?) + encoder.text_token match[start.size..-2], :content + encoder.text_token ')', :delimiter + else + encoder.text_token match[start.size..-1], :content + end + encoder.end_group :function + + elsif match = scan(/(?: #{RE::Dimension} | #{RE::Percentage} | #{RE::Num} )/ox) + encoder.text_token match, :float + + elsif match = scan(/#{RE::HexColor}/o) + encoder.text_token match, :color + + elsif match = scan(/! *(?:important|optional)/) + encoder.text_token match, :important + + elsif match = scan(/(?:rgb|hsl)a?\([^()\n]*\)?/) + encoder.text_token match, :color + + elsif match = scan(/@else if\b|#{RE::AtKeyword}/) + encoder.text_token match, :directive + value_expected = true + + elsif match = scan(/ == | != | [-+*\/>~:;,.=()] /x) + if match == ':' + value_expected = true + elsif match == ';' + value_expected = false + end + encoder.text_token match, :operator + + else + encoder.text_token getch, :error + + end + + end + + if options[:keep_state] + @state = states + @value_expected = value_expected + end + + encoder + end + + end + +end +end -- cgit v1.2.1 From 2708fd32f45a6de34249ffbd5331c6df9c985889 Mon Sep 17 00:00:00 2001 From: Kornelius Kalnbach Date: Thu, 11 Apr 2013 21:02:31 +0200 Subject: fix order of SASS_FUNCTIONs --- lib/coderay/scanners/sass.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/coderay/scanners/sass.rb') diff --git a/lib/coderay/scanners/sass.rb b/lib/coderay/scanners/sass.rb index 89d7737..b1b1cba 100644 --- a/lib/coderay/scanners/sass.rb +++ b/lib/coderay/scanners/sass.rb @@ -7,7 +7,7 @@ module Scanners register_for :sass file_extension 'sass' - SASS_FUNCTION = /(?:inline-image|linear-gradient|color-stops|mix|lighten|darken|rotate|image-url|image-width|image-height|sprite|sprite-url|sprite-path|sprite-file|sprite-map|sprite-position|unquote|join|round|ceil|floor|nth)/ + SASS_FUNCTION = /(?:inline-image|linear-gradient|color-stops|mix|lighten|darken|rotate|image-url|image-width|image-height|sprite-url|sprite-path|sprite-file|sprite-map|sprite-position|sprite|unquote|join|round|ceil|floor|nth)/ STRING_CONTENT_PATTERN = { "'" => /(?:[^\n\'\#]+|\\\n|#{RE::Escape}|#(?!\{))+/, -- cgit v1.2.1 From 8563acc7e29e3d5bf5281aedbc0b7088abe44877 Mon Sep 17 00:00:00 2001 From: Kornelius Kalnbach Date: Sun, 9 Jun 2013 19:30:49 +0200 Subject: fix Sass highlighting in diff --- lib/coderay/scanners/sass.rb | 3 --- 1 file changed, 3 deletions(-) (limited to 'lib/coderay/scanners/sass.rb') diff --git a/lib/coderay/scanners/sass.rb b/lib/coderay/scanners/sass.rb index b1b1cba..17514c0 100644 --- a/lib/coderay/scanners/sass.rb +++ b/lib/coderay/scanners/sass.rb @@ -18,13 +18,11 @@ module Scanners def setup @state = :initial - @value_expected = false end def scan_tokens encoder, options states = Array(options[:state] || @state) string_delimiter = nil - value_expected = @value_expected until eos? @@ -191,7 +189,6 @@ module Scanners if options[:keep_state] @state = states - @value_expected = value_expected end encoder -- cgit v1.2.1 From 5bea8e3041de85266c3596627224ced102f195e0 Mon Sep 17 00:00:00 2001 From: Kornelius Kalnbach Date: Sun, 9 Jun 2013 19:31:10 +0200 Subject: don't nest :string recursively in :sass_inline --- lib/coderay/scanners/sass.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'lib/coderay/scanners/sass.rb') diff --git a/lib/coderay/scanners/sass.rb b/lib/coderay/scanners/sass.rb index 17514c0..0eb2caa 100644 --- a/lib/coderay/scanners/sass.rb +++ b/lib/coderay/scanners/sass.rb @@ -139,7 +139,14 @@ module Scanners encoder.begin_group :string string_delimiter = match encoder.text_token match, :delimiter - states.push :string + if states.include? :sass_inline + content = scan_until(/(?=#{string_delimiter}|\}|\z)/) + encoder.text_token content, :content unless content.empty? + encoder.text_token string_delimiter, :delimiter if scan(/#{string_delimiter}/) + encoder.end_group :string + else + states.push :string + end elsif match = scan(/#{SASS_FUNCTION}/o) encoder.text_token match, :predefined -- cgit v1.2.1 From b159057934d2c84c0a25c83f6cbe59010345d0a7 Mon Sep 17 00:00:00 2001 From: Kornelius Kalnbach Date: Tue, 11 Jun 2013 03:29:54 +0200 Subject: tweak CSS tag/id token kind patch from noprompt --- lib/coderay/scanners/sass.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/coderay/scanners/sass.rb') diff --git a/lib/coderay/scanners/sass.rb b/lib/coderay/scanners/sass.rb index 0eb2caa..218ee57 100644 --- a/lib/coderay/scanners/sass.rb +++ b/lib/coderay/scanners/sass.rb @@ -38,16 +38,16 @@ module Scanners elsif case states.last when :initial, :media, :sass_inline if match = scan(/(?>#{RE::Ident})(?!\()/ox) - encoder.text_token match, value_expected ? :value : (check(/.*:/) ? :key : :type) + encoder.text_token match, value_expected ? :value : (check(/.*:/) ? :key : :tag) next elsif !value_expected && (match = scan(/\*/)) - encoder.text_token match, :type + encoder.text_token match, :tag next elsif match = scan(RE::Class) encoder.text_token match, :class next elsif match = scan(RE::Id) - encoder.text_token match, :constant + encoder.text_token match, :id next elsif match = scan(RE::PseudoClass) encoder.text_token match, :pseudo_class -- cgit v1.2.1 From 2e4e83bf84282d6ce7fbc9eeff2cbe1c79788a9b Mon Sep 17 00:00:00 2001 From: Kornelius Kalnbach Date: Tue, 11 Jun 2013 16:30:26 +0200 Subject: tweak Sass scanner: comments, includes, functions --- lib/coderay/scanners/sass.rb | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) (limited to 'lib/coderay/scanners/sass.rb') diff --git a/lib/coderay/scanners/sass.rb b/lib/coderay/scanners/sass.rb index 218ee57..167051d 100644 --- a/lib/coderay/scanners/sass.rb +++ b/lib/coderay/scanners/sass.rb @@ -7,8 +7,6 @@ module Scanners register_for :sass file_extension 'sass' - SASS_FUNCTION = /(?:inline-image|linear-gradient|color-stops|mix|lighten|darken|rotate|image-url|image-width|image-height|sprite-url|sprite-path|sprite-file|sprite-map|sprite-position|sprite|unquote|join|round|ceil|floor|nth)/ - STRING_CONTENT_PATTERN = { "'" => /(?:[^\n\'\#]+|\\\n|#{RE::Escape}|#(?!\{))+/, '"' => /(?:[^\n\"\#]+|\\\n|#{RE::Escape}|#(?!\{))+/, @@ -26,10 +24,22 @@ module Scanners until eos? - if match = scan(/\s+/) + if bol? && (match = scan(/(?>( +)?(\/[\*\/])(.+)?)(?=\n)/)) + encoder.text_token self[1], :space if self[1] + encoder.begin_group :comment + encoder.text_token self[2], :delimiter + encoder.text_token self[3], :content if self[3] + if match = scan(/(?:\n+#{self[1]} .*)+/) + encoder.text_token match, :content + end + encoder.end_group :comment + elsif match = scan(/\n|[^\n\S]+\n?/) encoder.text_token match, :space - value_expected = false if match.index(/\n/) - + if match.index(/\n/) + value_expected = false + states.pop if states.last == :include + end + elsif states.last == :sass_inline && (match = scan(/\}/)) encoder.text_token match, :inline_delimiter encoder.end_group :inline @@ -61,7 +71,11 @@ module Scanners elsif match = scan(/(\=|@mixin +)#{RE::Ident}/o) encoder.text_token match, :function next - elsif match = scan(/@media/) + elsif match = scan(/@import\b/) + encoder.text_token match, :directive + states << :include + next + elsif match = scan(/@media\b/) encoder.text_token match, :directive # states.push :media_before_name next @@ -90,13 +104,19 @@ module Scanners encoder.text_token match, :inline_delimiter states.push :sass_inline elsif match = scan(/ \\ | $ /x) - encoder.end_group state + encoder.end_group :string encoder.text_token match, :error unless match.empty? states.pop else raise_inspect "else case #{string_delimiter} reached; %p not handled." % peek(1), encoder end + when :include + if match = scan(/[^\s'",]+/) + encoder.text_token match, :include + next + end + else #:nocov: raise_inspect 'Unknown state', encoder @@ -148,9 +168,6 @@ module Scanners states.push :string end - elsif match = scan(/#{SASS_FUNCTION}/o) - encoder.text_token match, :predefined - elsif match = scan(/#{RE::Function}/o) encoder.begin_group :function start = match[/^[-\w]+\(/] @@ -163,6 +180,9 @@ module Scanners end encoder.end_group :function + elsif match = scan(/[a-z][-a-z_]*(?=\()/o) + encoder.text_token match, :predefined + elsif match = scan(/(?: #{RE::Dimension} | #{RE::Percentage} | #{RE::Num} )/ox) encoder.text_token match, :float -- cgit v1.2.1