-
Notifications
You must be signed in to change notification settings - Fork 40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement syntax highlight #70
Merged
greyblake
merged 8 commits into
crystal-community:master
from
makenowjust:feature/syntax-highlight
Nov 14, 2017
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
99c696d
Implement syntax highlight
makenowjust bc6a7aa
Add missing keywords
makenowjust 4d677f6
Not raise an error when string array literal is not closed
makenowjust 3a064b8
Refactor Highlighter
makenowjust 96d500d
Introduce `--no-color` flag to disable highlight
makenowjust 9d7de52
Remove highlight from invitation
makenowjust 771fd93
Fix to use string array literal delimiter as same as input
makenowjust 0f116af
Work highlight against heredoc correctly
makenowjust File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,230 @@ | ||
class Icr::Highlighter | ||
record Highlight, | ||
color : Symbol, | ||
bold : Bool = false, | ||
underline : Bool = false do | ||
def to_s(io) | ||
case color | ||
when :black | ||
io << 30 | ||
when :red | ||
io << 31 | ||
when :green | ||
io << 32 | ||
when :yellow | ||
io << 33 | ||
when :blue | ||
io << 34 | ||
when :magenta | ||
io << 35 | ||
when :cyan | ||
io << 36 | ||
when :white | ||
io << 37 | ||
end | ||
|
||
io << ";1" if bold | ||
io << ";4" if underline | ||
end | ||
end | ||
|
||
def initialize(@invitation : String) | ||
@highlight_stack = [] of Highlight | ||
end | ||
|
||
KEYWORDS = Set{ | ||
"new", | ||
:abstract, :alias, :as, :as?, :asm, :begin, :break, :case, :class, | ||
:def, :do, :else, :elsif, :end, :ensure, :enum, :extend, :for, :fun, | ||
:if, :in, :include, :instance_sizeof, :is_a?, :lib, :macro, :module, | ||
:next, :nil?, :of, :out, :pointerof, :private, :protected, :require, | ||
:rescue, :responds_to?, :return, :select, :sizeof, :struct, :super, | ||
:then, :type, :typeof, :undef, :union, :uninitialized, :unless, :until, | ||
:when, :while, :with, :yield, | ||
} | ||
|
||
SPECIAL_VALUES = Set{ | ||
:true, :false, :nil, :self, | ||
:__FILE__, :__DIR__, :__LINE__, :__END_LINE__, | ||
} | ||
|
||
OPERATORS = Set{ | ||
:"+", :"-", :"*", :"/", | ||
:"=", :"==", :"<", :"<=", :">", :">=", :"!", :"!=", :"=~", :"!~", | ||
:"&", :"|", :"^", :"~", :"**", :">>", :"<<", :"%", | ||
:"[]", :"[]?", :"[]=", :"<=>", :"===", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't know there is |
||
} | ||
|
||
def highlight(code) | ||
lexer = Crystal::Lexer.new(code) | ||
lexer.comments_enabled = true | ||
lexer.count_whitespace = true | ||
lexer.wants_raw = true | ||
|
||
String.build do |io| | ||
io.print @invitation | ||
begin | ||
highlight_normal_state lexer, io | ||
io.puts "\e[m" | ||
rescue Crystal::SyntaxException | ||
end | ||
end | ||
end | ||
|
||
private def highlight_normal_state(lexer, io, break_on_rcurly = false) | ||
last_is_def = false | ||
|
||
while true | ||
token = lexer.next_token | ||
case token.type | ||
when :NEWLINE | ||
io.puts | ||
io.print "#{@invitation} " | ||
when :SPACE | ||
io << token.value | ||
if token.passed_backslash_newline | ||
io.print "#{@invitation} " | ||
end | ||
when :COMMENT | ||
highlight token.value.to_s, :comment, io | ||
when :NUMBER | ||
highlight token.raw, :number, io | ||
when :CHAR | ||
highlight token.raw, :char, io | ||
when :SYMBOL | ||
highlight token.raw, :symbol, io | ||
when :CONST, :"::" | ||
highlight token, :const, io | ||
when :DELIMITER_START | ||
highlight_delimiter_state lexer, token, io | ||
when :STRING_ARRAY_START, :SYMBOL_ARRAY_START | ||
highlight_string_array lexer, token, io | ||
when :EOF | ||
break | ||
when :IDENT | ||
if last_is_def | ||
last_is_def = false | ||
highlight token, :method, io | ||
else | ||
case | ||
when KEYWORDS.includes? token.value | ||
highlight token, :keyword, io | ||
when SPECIAL_VALUES.includes? token.value | ||
highlight token, :literal, io | ||
else | ||
io << token | ||
end | ||
end | ||
when :"}" | ||
if break_on_rcurly | ||
break | ||
else | ||
io << token | ||
end | ||
else | ||
if OPERATORS.includes? token.type | ||
highlight token, :operator, io | ||
else | ||
io << token | ||
end | ||
end | ||
|
||
unless token.type == :SPACE | ||
last_is_def = token.keyword? :def | ||
end | ||
end | ||
end | ||
|
||
private def highlight_delimiter_state(lexer, token, io) | ||
start_highlight :string, io | ||
|
||
print_raw io, token.raw | ||
|
||
while true | ||
token = lexer.next_string_token(token.delimiter_state) | ||
case token.type | ||
when :DELIMITER_END | ||
print_raw io, token.raw | ||
end_highlight io | ||
break | ||
when :INTERPOLATION_START | ||
end_highlight io | ||
highlight "\#{", :interpolation, io | ||
highlight_normal_state lexer, io, break_on_rcurly: true | ||
start_highlight :string, io | ||
highlight "}", :interpolation, io | ||
when :EOF | ||
break | ||
else | ||
print_raw io, token.raw | ||
end | ||
end | ||
end | ||
|
||
private def highlight_string_array(lexer, token, io) | ||
start_highlight :string, io | ||
print_raw io, token.raw | ||
first = true | ||
while true | ||
lexer.next_string_array_token | ||
case token.type | ||
when :STRING | ||
io << " " unless first | ||
print_raw io, token.value | ||
first = false | ||
when :STRING_ARRAY_END | ||
print_raw io, token.raw | ||
end_highlight io | ||
break | ||
when :EOF | ||
end_highlight io | ||
break | ||
end | ||
end | ||
end | ||
|
||
private def print_raw(io, raw) | ||
io << raw.to_s.gsub("\n", "\n#{@invitation} ") | ||
end | ||
|
||
private def highlight(token, type, io) | ||
start_highlight type, io | ||
io << token | ||
end_highlight io | ||
end | ||
|
||
private def start_highlight(type, io) | ||
@highlight_stack << highlight_type(type) | ||
io << "\e[0;#{@highlight_stack.last}m" | ||
end | ||
|
||
private def end_highlight(io) | ||
@highlight_stack.pop | ||
io << "\e[0;#{@highlight_stack.last?}m" | ||
end | ||
|
||
private def highlight_type(type) | ||
case type | ||
when :comment | ||
Highlight.new(:black, bold: true) | ||
when :number, :char | ||
Highlight.new(:blue) | ||
when :symbol | ||
Highlight.new(:yellow) | ||
when :const | ||
Highlight.new(:blue, underline: true) | ||
when :string | ||
Highlight.new(:red) | ||
when :interpolation | ||
Highlight.new(:red, bold: true) | ||
when :keyword | ||
Highlight.new(:green) | ||
when :operator | ||
Highlight.new(:white) | ||
when :method | ||
Highlight.new(:blue) | ||
else | ||
Highlight.new(:default) | ||
end | ||
end | ||
end |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a possibility to reuse standard library
colorize
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm
colorize
expert (see crystal-lang/crystal#3925) ;-) But I don't usecolorize
for this due to some reason.One of the biggest reasons is
colorize
doesn't support outputting escape sequence without content.