From 4b6e0916c91b0284e16014cbf5ace6225fadf68b Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Fri, 19 Apr 2013 13:03:42 -0700 Subject: [PATCH 01/12] Swap out Markdown render logic --- package.json | 1 + .../lib/markdown-preview-view.coffee | 19 ++++++++----------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 85dec3096..b59587873 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "plist": "git://github.com/nathansobo/node-plist.git", "space-pen": "git://github.com/nathansobo/space-pen.git", "less": "git://github.com/nathansobo/less.js.git", + "roaster": "0.0.3", "jqueryui-browser": "1.10.2-1" }, diff --git a/src/packages/markdown-preview/lib/markdown-preview-view.coffee b/src/packages/markdown-preview/lib/markdown-preview-view.coffee index 9b336cc2e..433cbb565 100644 --- a/src/packages/markdown-preview/lib/markdown-preview-view.coffee +++ b/src/packages/markdown-preview/lib/markdown-preview-view.coffee @@ -1,6 +1,7 @@ $ = require 'jquery' ScrollView = require 'scroll-view' {$$$} = require 'space-pen' +roaster = require 'roaster' module.exports = class MarkdownPreviewView extends ScrollView @@ -59,15 +60,11 @@ class MarkdownPreviewView extends ScrollView setLoading: -> @html($$$ -> @div class: 'markdown-spinner', 'Loading Markdown...') - fetchRenderedMarkdown: (text) -> + fetchRenderedMarkdown: -> @setLoading() - $.ajax - url: 'https://api.github.com/markdown' - type: 'POST' - dataType: 'html' - contentType: 'application/json; charset=UTF-8' - data: JSON.stringify - mode: 'markdown' - text: @buffer.getText() - success: (html) => @html(html) - error: (result) => @setErrorHtml(result) + roaster(@buffer.getText(), {}, (err, html) => + if err + @setErrorHtml(err) + else + @html(html) + ) From 6c748c2f87bd19d194326ece030cb6ff1ca86de3 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Fri, 19 Apr 2013 16:02:28 -0700 Subject: [PATCH 02/12] Fix tests --- .../lib/markdown-preview-view.coffee | 2 +- .../spec/markdown-preview-view-spec.coffee | 16 +++++----------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/packages/markdown-preview/lib/markdown-preview-view.coffee b/src/packages/markdown-preview/lib/markdown-preview-view.coffee index 433cbb565..6f4707453 100644 --- a/src/packages/markdown-preview/lib/markdown-preview-view.coffee +++ b/src/packages/markdown-preview/lib/markdown-preview-view.coffee @@ -43,7 +43,7 @@ class MarkdownPreviewView extends ScrollView @buffer.getPath() setErrorHtml: (result)-> - try failureMessage = JSON.parse(result.responseText).message + try failureMessage = JSON.parse(result).message @html $$$ -> @h2 'Previewing Markdown Failed' diff --git a/src/packages/markdown-preview/spec/markdown-preview-view-spec.coffee b/src/packages/markdown-preview/spec/markdown-preview-view-spec.coffee index 42cac9b84..c0f133502 100644 --- a/src/packages/markdown-preview/spec/markdown-preview-view-spec.coffee +++ b/src/packages/markdown-preview/spec/markdown-preview-view-spec.coffee @@ -6,7 +6,6 @@ describe "MarkdownPreviewView", -> [buffer, preview] = [] beforeEach -> - spyOn($, 'ajax') project.setPath(project.resolve('markdown')) buffer = project.bufferForPath('file.markdown') preview = new MarkdownPreviewView(buffer) @@ -15,22 +14,17 @@ describe "MarkdownPreviewView", -> buffer.release() describe "on construction", -> - ajaxArgs = null - - beforeEach -> - ajaxArgs = $.ajax.argsForCall[0][0] it "shows a loading spinner and fetches the rendered markdown", -> + preview.setLoading() expect(preview.find('.markdown-spinner')).toExist() - expect($.ajax).toHaveBeenCalled() + expect(preview.buffer.getText()).toBe buffer.getText() - expect(JSON.parse(ajaxArgs.data).text).toBe buffer.getText() - - ajaxArgs.success($$$ -> @div "WWII", class: 'private-ryan') - expect(preview.find(".private-ryan")).toExist() + preview.fetchRenderedMarkdown() + expect(preview.find(".emoji")).toExist() it "shows an error message on error", -> - ajaxArgs.error() + preview.setErrorHtml("Not a real file") expect(preview.text()).toContain "Failed" describe "serialization", -> From 1cc4c52c7aacdc4f1f8bfa27649d9983b3828a4f Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Fri, 19 Apr 2013 16:02:36 -0700 Subject: [PATCH 03/12] Style emoji --- src/app/edit-session.coffee | 28 +++++++++---------- .../lib/markdown-preview-view.coffee | 21 +++++++++++++- .../stylesheets/markdown-preview.less | 5 ++++ 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 31f224edf..e8b6ae40b 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -94,7 +94,7 @@ class EditSession # Retrieves the filename of the open file. # # This is `'untitled'` if the file is new and not saved to the disk. - # + # # Returns a {String}. getTitle: -> if path = @getPath() @@ -175,7 +175,7 @@ class EditSession setSoftWrap: (@softWrap) -> # Retrieves that character used to indicate a tab. - # + # # If soft tabs are enabled, this is a space (`" "`) times the {.getTabLength} value. # Otherwise, it's a tab (`\t`). # @@ -191,22 +191,22 @@ class EditSession # # tabLength - A {Number} that defines the new tab length. setTabLength: (tabLength) -> @displayBuffer.setTabLength(tabLength) - + # Given a position, this clips it to a real position. # # For example, if `position`'s row exceeds the row count of the buffer, - # or if its column goes beyond a line's length, this "sanitizes" the value + # or if its column goes beyond a line's length, this "sanitizes" the value # to a real position. # # position - The {Point} to clip # # Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed. clipBufferPosition: (bufferPosition) -> @buffer.clipPosition(bufferPosition) - + # Given a range, this clips it to a real range. # # For example, if `range`'s row exceeds the row count of the buffer, - # or if its column goes beyond a line's length, this "sanitizes" the value + # or if its column goes beyond a line's length, this "sanitizes" the value # to a real range. # # range - The {Point} to clip @@ -319,13 +319,13 @@ class EditSession # {Delegates to: DisplayBuffer.screenPositionForBufferPosition} screenPositionForBufferPosition: (bufferPosition, options) -> @displayBuffer.screenPositionForBufferPosition(bufferPosition, options) - + # {Delegates to: DisplayBuffer.bufferPositionForScreenPosition} bufferPositionForScreenPosition: (screenPosition, options) -> @displayBuffer.bufferPositionForScreenPosition(screenPosition, options) - + # {Delegates to: DisplayBuffer.screenRangeForBufferRange} screenRangeForBufferRange: (bufferRange) -> @displayBuffer.screenRangeForBufferRange(bufferRange) - + # {Delegates to: DisplayBuffer.bufferRangeForScreenRange} bufferRangeForScreenRange: (screenRange) -> @displayBuffer.bufferRangeForScreenRange(screenRange) @@ -592,7 +592,7 @@ class EditSession # Given a buffer row, this returns a suggested indentation level. # - # The indentation level provided is based on the current language. + # The indentation level provided is based on the current language. # # bufferRow - A {Number} indicating the buffer row # @@ -806,7 +806,7 @@ class EditSession # {Delegates to: DisplayBuffer.getMarkerHeadScreenPosition} getMarkerHeadScreenPosition: (args...) -> @displayBuffer.getMarkerHeadScreenPosition(args...) - + # {Delegates to: DisplayBuffer.setMarkerHeadScreenPosition} setMarkerHeadScreenPosition: (args...) -> @displayBuffer.setMarkerHeadScreenPosition(args...) @@ -826,7 +826,7 @@ class EditSession # {Delegates to: DisplayBuffer.setMarkerTailScreenPosition} setMarkerTailScreenPosition: (args...) -> @displayBuffer.setMarkerTailScreenPosition(args...) - + # {Delegates to: DisplayBuffer.getMarkerTailBufferPosition} getMarkerTailBufferPosition: (args...) -> @displayBuffer.getMarkerTailBufferPosition(args...) @@ -990,7 +990,7 @@ class EditSession # # Returns an {Array} of {Selection}s. getSelections: -> new Array(@selections...) - + # Gets the selection at the specified index. # # index - The id {Number} of the selection @@ -1268,7 +1268,7 @@ class EditSession # Selects all the text from the current cursor position to the beginning of the next word. selectToBeginningOfNextWord: -> @expandSelectionsForward (selection) => selection.selectToBeginningOfNextWord() - + # Selects the current word. selectWord: -> @expandSelectionsForward (selection) => selection.selectWord() diff --git a/src/packages/markdown-preview/lib/markdown-preview-view.coffee b/src/packages/markdown-preview/lib/markdown-preview-view.coffee index 6f4707453..1ec526b68 100644 --- a/src/packages/markdown-preview/lib/markdown-preview-view.coffee +++ b/src/packages/markdown-preview/lib/markdown-preview-view.coffee @@ -1,7 +1,10 @@ $ = require 'jquery' +_ = require 'underscore' ScrollView = require 'scroll-view' {$$$} = require 'space-pen' roaster = require 'roaster' +LanguageMode = require 'language-mode' +Buffer = require 'text-buffer' module.exports = class MarkdownPreviewView extends ScrollView @@ -66,5 +69,21 @@ class MarkdownPreviewView extends ScrollView if err @setErrorHtml(err) else - @html(html) + result = @html(html) + preList = result.find("pre") + for pre in preList + grammar = _.find syntax.grammars, (grammar) -> + return "ruby" == grammar.scopeName.split(".").pop() + + if grammar + languageMode = new LanguageMode(this, grammar) + console.log pre + code = pre.childNodes[0] + text = code.textContent.split("\n") + + for line in text + tokens = languageMode.tokenizeLine(text) + console.log tokens + #codeBuffer = new Buffer("", text) + #x = 1 ) diff --git a/src/packages/markdown-preview/stylesheets/markdown-preview.less b/src/packages/markdown-preview/stylesheets/markdown-preview.less index 608389a88..533eab598 100644 --- a/src/packages/markdown-preview/stylesheets/markdown-preview.less +++ b/src/packages/markdown-preview/stylesheets/markdown-preview.less @@ -400,4 +400,9 @@ background-color: transparent; border: none; } + + .emoji { + height: 20px; + width: 20px; + } } From dd041945d203e651610ea0cd4a04a7860f344c35 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Mon, 29 Apr 2013 15:20:25 -0700 Subject: [PATCH 04/12] Transform specs to something useful --- spec/fixtures/markdown/file.markdown | 19 ++++++++++++++++++- .../spec/markdown-preview-view-spec.coffee | 10 ++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/spec/fixtures/markdown/file.markdown b/spec/fixtures/markdown/file.markdown index 0eec6a120..964679b09 100644 --- a/spec/fixtures/markdown/file.markdown +++ b/spec/fixtures/markdown/file.markdown @@ -1,3 +1,20 @@ ## File.markdown -:cool: \ No newline at end of file +:cool: + +```ruby +def func + x = 1 +end +``` + +``` +function f(x) { + return x++; +} +``` + +```kombucha +drink-that-stuff: + tastes-weird~ +``` diff --git a/src/packages/markdown-preview/spec/markdown-preview-view-spec.coffee b/src/packages/markdown-preview/spec/markdown-preview-view-spec.coffee index c0f133502..5459ad681 100644 --- a/src/packages/markdown-preview/spec/markdown-preview-view-spec.coffee +++ b/src/packages/markdown-preview/spec/markdown-preview-view-spec.coffee @@ -31,3 +31,13 @@ describe "MarkdownPreviewView", -> it "reassociates with the same buffer when deserialized", -> newPreview = deserialize(preview.serialize()) expect(newPreview.buffer).toBe buffer + + fdescribe "code block tokenization", -> + describe "when the code block's fence name has a matching grammar", -> + it "tokenizes the code block with the grammar", -> + expect(preview.find("pre code.lang-ruby .entity.name.function.ruby")).toExist() + + describe "when the code block's fence name doesn't have a matching grammar", -> + it "does not tokenize the code block", -> + expect(preview.find("pre code:not([class])").children().length).toBe 0 + expect(preview.find("pre code.lang-kombucha").children().length).toBe 0 From cf239ccac0642896f56dbb2181597bb8316759a2 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Mon, 29 Apr 2013 16:43:46 -0700 Subject: [PATCH 05/12] Implement highlighting of Markdown pre blocks --- src/app/editor.coffee | 60 +++++++------------ .../lib/markdown-preview-view.coffee | 45 ++++++++------ .../spec/markdown-preview-view-spec.coffee | 3 +- 3 files changed, 49 insertions(+), 59 deletions(-) diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 3f37df655..d433e890f 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -1342,42 +1342,24 @@ class Editor extends View invisibles.push(@invisibles.cr) if @invisibles.cr and screenLine.lineEnding is '\r\n' invisibles.push(@invisibles.eol) if @invisibles.eol invisibles - - buildEmptyLineHtml: (screenLine, screenRow) -> + + buildEmptyLineHtml: (screenRow) -> if not @mini and @showIndentGuide - indentation = 0 + guideIndentation = 0 while --screenRow >= 0 bufferRow = @activeEditSession.bufferPositionForScreenPosition([screenRow]).row bufferLine = @activeEditSession.lineForBufferRow(bufferRow) unless bufferLine is '' - indentation = Math.ceil(@activeEditSession.indentLevelForLine(bufferLine)) + guideIndentation = Math.ceil(@activeEditSession.indentLevelForLine(bufferLine)) break if indentation > 0 - tabLength = @activeEditSession.getTabLength() - invisibles = @getEndOfLineInvisibles(screenLine) - indentGuideHtml = [] - for level in [0...indentation] - indentLevelHtml = [""] - for characterPosition in [0...tabLength] - if invisible = invisibles.shift() - indentLevelHtml.push("#{invisible}") - else - indentLevelHtml.push(' ') - indentLevelHtml.push("") - indentGuideHtml.push(indentLevelHtml.join('')) + indentationHtml = "#{_.multiplyString(' ', @activeEditSession.getTabLength())}" + return _.multiplyString(indentationHtml, indentation) - for invisible in invisibles - indentGuideHtml.push("#{invisible}") - return indentGuideHtml.join('') + return ' ' unless @showInvisibles - invisibles = @buildEndOfLineInvisibles(screenLine) - if invisibles.length > 0 - invisibles - else - ' ' - - buildLineHtml: (screenLine, screenRow) -> + @buildLineHtmlHelper: ({tokens, text, lineEnding, fold, isSoftWrapped, attributes, guideIndentation, showInvisibles}) -> scopeStack = [] line = [] @@ -1402,35 +1384,35 @@ class Editor extends View scopeStack.pop() line.push("") - if fold = screenLine.fold - lineAttributes = { class: 'fold line', 'fold-id': fold.id } - else - lineAttributes = { class: 'line' } - attributePairs = [] - attributePairs.push "#{attributeName}=\"#{value}\"" for attributeName, value of lineAttributes + attributePairs.push "#{attributeName}=\"#{value}\"" for attributeName, value of attributes line.push("
") - invisibles = @invisibles if @showInvisibles + invisibles = @invisibles if showInvisibles if screenLine.text == '' - html = @buildEmptyLineHtml(screenLine, screenRow) + html = @buildEmptyLineHtml(screenRow) line.push(html) if html else - firstNonWhitespacePosition = screenLine.text.search(/\S/) - firstTrailingWhitespacePosition = screenLine.text.search(/\s*$/) + firstNonWhitespacePosition = text.search(/\S/) + firstTrailingWhitespacePosition = text.search(/\s*$/) lineIsWhitespaceOnly = firstTrailingWhitespacePosition is 0 position = 0 - for token in screenLine.tokens + for token in tokens updateScopeStack(token.scopes) hasLeadingWhitespace = position < firstNonWhitespacePosition hasTrailingWhitespace = position + token.value.length > firstTrailingWhitespacePosition - hasIndentGuide = not @mini and @showIndentGuide and (hasLeadingWhitespace or lineIsWhitespaceOnly) + hasIndentGuide = not @mini and guideIndentation? and (hasLeadingWhitespace or lineIsWhitespaceOnly) line.push(token.getValueAsHtml({invisibles, hasLeadingWhitespace, hasTrailingWhitespace, hasIndentGuide})) position += token.value.length popScope() while scopeStack.length > 0 - line.push(@buildEndOfLineInvisibles(screenLine)) unless screenLine.text == '' + if invisibles and not @mini and not screenLine.isSoftWrapped() + if invisibles.cr and screenLine.lineEnding is '\r\n' + line.push("#{invisibles.cr}") + if invisibles.eol + line.push("#{invisibles.eol}") + line.push("") if fold line.push('
') diff --git a/src/packages/markdown-preview/lib/markdown-preview-view.coffee b/src/packages/markdown-preview/lib/markdown-preview-view.coffee index 1ec526b68..e343f52a4 100644 --- a/src/packages/markdown-preview/lib/markdown-preview-view.coffee +++ b/src/packages/markdown-preview/lib/markdown-preview-view.coffee @@ -3,8 +3,10 @@ _ = require 'underscore' ScrollView = require 'scroll-view' {$$$} = require 'space-pen' roaster = require 'roaster' -LanguageMode = require 'language-mode' -Buffer = require 'text-buffer' +Editor = require 'editor' + +fenceNameToExtension = + "ruby": "rb" module.exports = class MarkdownPreviewView extends ScrollView @@ -63,27 +65,32 @@ class MarkdownPreviewView extends ScrollView setLoading: -> @html($$$ -> @div class: 'markdown-spinner', 'Loading Markdown...') + tokenizeCodeBlocks: (html) => + html = $(html) + preList = $(html.filter("pre")) + + for codeBlock in preList.toArray() + codeBlock = $(codeBlock.firstChild) + if className = codeBlock.attr('class') + fenceName = className.replace(/^lang-/, '') + + if extension = fenceNameToExtension[fenceName] + text = codeBlock.text() + syntax.selectGrammar("foo.#{extension}", text) + if grammar = syntax.selectGrammar("foo.#{extension}", text) + continue if grammar is syntax.nullGrammar + tokens = grammar.tokenizeLines(text) + grouping = "" + for token in tokens + grouping += Editor.buildLineHtml(token, text) + codeBlock.replaceWith(grouping) + html + fetchRenderedMarkdown: -> @setLoading() roaster(@buffer.getText(), {}, (err, html) => if err @setErrorHtml(err) else - result = @html(html) - preList = result.find("pre") - for pre in preList - grammar = _.find syntax.grammars, (grammar) -> - return "ruby" == grammar.scopeName.split(".").pop() - - if grammar - languageMode = new LanguageMode(this, grammar) - console.log pre - code = pre.childNodes[0] - text = code.textContent.split("\n") - - for line in text - tokens = languageMode.tokenizeLine(text) - console.log tokens - #codeBuffer = new Buffer("", text) - #x = 1 + @html(@tokenizeCodeBlocks(html)) ) diff --git a/src/packages/markdown-preview/spec/markdown-preview-view-spec.coffee b/src/packages/markdown-preview/spec/markdown-preview-view-spec.coffee index 5459ad681..747f7f777 100644 --- a/src/packages/markdown-preview/spec/markdown-preview-view-spec.coffee +++ b/src/packages/markdown-preview/spec/markdown-preview-view-spec.coffee @@ -8,6 +8,7 @@ describe "MarkdownPreviewView", -> beforeEach -> project.setPath(project.resolve('markdown')) buffer = project.bufferForPath('file.markdown') + atom.activatePackage('ruby.tmbundle', sync: true) preview = new MarkdownPreviewView(buffer) afterEach -> @@ -35,7 +36,7 @@ describe "MarkdownPreviewView", -> fdescribe "code block tokenization", -> describe "when the code block's fence name has a matching grammar", -> it "tokenizes the code block with the grammar", -> - expect(preview.find("pre code.lang-ruby .entity.name.function.ruby")).toExist() + expect(preview.find("pre span.entity.name.function.ruby")).toExist() describe "when the code block's fence name doesn't have a matching grammar", -> it "does not tokenize the code block", -> From 95e14486af9af38e22bdee856ea79bff5f4cf870 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Tue, 30 Apr 2013 09:22:08 -0700 Subject: [PATCH 06/12] Fix specs for testing highlighted lines --- spec/app/editor-spec.coffee | 2 +- src/app/editor.coffee | 41 +++++++++++-------- .../lib/markdown-preview-view.coffee | 35 +++++++++------- .../spec/markdown-preview-view-spec.coffee | 6 +-- 4 files changed, 50 insertions(+), 34 deletions(-) diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index 0aca4b840..a222d8303 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -1765,7 +1765,7 @@ describe "Editor", -> editor.edit(emptyEditSession) expect(editor.gutter.lineNumbers.find('.line-number').length).toBe 1 - describe "when the editor is mini", -> + fdescribe "when the editor is mini", -> it "hides the gutter", -> miniEditor = new Editor(mini: true) miniEditor.attachToDom() diff --git a/src/app/editor.coffee b/src/app/editor.coffee index d433e890f..1b4f66761 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -1317,17 +1317,24 @@ class Editor extends View buildLineElementsForScreenRows: (startRow, endRow) -> div = document.createElement('div') - div.innerHTML = @buildLinesHtml(startRow, endRow) + div.innerHTML = @buildHtmlLines(startRow, endRow) new Array(div.children...) - buildLinesHtml: (startRow, endRow) -> + buildHtmlLines: (startRow, endRow) -> lines = @activeEditSession.linesForScreenRows(startRow, endRow) htmlLines = [] screenRow = startRow for line in @activeEditSession.linesForScreenRows(startRow, endRow) - htmlLines.push(@buildLineHtml(line, screenRow++)) + htmlLines.push(@buildHtmlFromLine(line, screenRow++)) htmlLines.join('\n\n') + @buildEmptyLineHtml: (guideIndentation, showInvisibles, activeEditSession) -> + if guideIndentation > 0 + indentationHtml = "#{_.multiplyString(' ', @activeEditSession.getTabLength())}" + return _.multiplyString(indentationHtml, indentation) + else if not showInvisibles + ' ' + buildEndOfLineInvisibles: (screenLine) -> invisibles = [] for invisible in @getEndOfLineInvisibles(screenLine) @@ -1337,12 +1344,19 @@ class Editor extends View getEndOfLineInvisibles: (screenLine) -> return [] unless @showInvisibles and @invisibles return [] if @mini or screenLine.isSoftWrapped() + + buildLineHtml: (screenLine, screenRow) -> + { tokens, text, lineEnding, fold } = screenLine + if fold + attributes = { class: 'fold line', 'fold-id': fold.id } + else + attributes = { class: 'line' } invisibles = [] invisibles.push(@invisibles.cr) if @invisibles.cr and screenLine.lineEnding is '\r\n' invisibles.push(@invisibles.eol) if @invisibles.eol invisibles - + buildEmptyLineHtml: (screenRow) -> if not @mini and @showIndentGuide guideIndentation = 0 @@ -1353,13 +1367,10 @@ class Editor extends View guideIndentation = Math.ceil(@activeEditSession.indentLevelForLine(bufferLine)) break - if indentation > 0 - indentationHtml = "#{_.multiplyString(' ', @activeEditSession.getTabLength())}" - return _.multiplyString(indentationHtml, indentation) + invisibles = @invisibles if @showInvisibles + Editor.buildLineHtmlHelper({tokens, text, lineEnding, fold, isSoftWrapped, invisibles, attributes, guideIndentation, @showInvisibles, @activeEditSession, @mini}) - return ' ' unless @showInvisibles - - @buildLineHtmlHelper: ({tokens, text, lineEnding, fold, isSoftWrapped, attributes, guideIndentation, showInvisibles}) -> + @buildHtmlHelper: ({tokens, text, lineEnding, fold, isSoftWrapped, invisibles, attributes, guideIndentation, showInvisibles, activeEditSession, mini}) => scopeStack = [] line = [] @@ -1388,10 +1399,8 @@ class Editor extends View attributePairs.push "#{attributeName}=\"#{value}\"" for attributeName, value of attributes line.push("
") - invisibles = @invisibles if showInvisibles - - if screenLine.text == '' - html = @buildEmptyLineHtml(screenRow) + if text == '' + html = Editor.buildEmptyLineHtml(guideIndentation, showInvisibles, activeEditSession) line.push(html) if html else firstNonWhitespacePosition = text.search(/\S/) @@ -1407,8 +1416,8 @@ class Editor extends View position += token.value.length popScope() while scopeStack.length > 0 - if invisibles and not @mini and not screenLine.isSoftWrapped() - if invisibles.cr and screenLine.lineEnding is '\r\n' + if invisibles and not @mini and not isSoftWrapped + if invisibles.cr and lineEnding is '\r\n' line.push("#{invisibles.cr}") if invisibles.eol line.push("#{invisibles.eol}") diff --git a/src/packages/markdown-preview/lib/markdown-preview-view.coffee b/src/packages/markdown-preview/lib/markdown-preview-view.coffee index e343f52a4..acbcd0a62 100644 --- a/src/packages/markdown-preview/lib/markdown-preview-view.coffee +++ b/src/packages/markdown-preview/lib/markdown-preview-view.coffee @@ -21,7 +21,7 @@ class MarkdownPreviewView extends ScrollView initialize: (@buffer) -> super - @fetchRenderedMarkdown() + @renderMarkdown() @on 'core:move-up', => @scrollUp() @on 'core:move-down', => @scrollDown() @@ -71,22 +71,29 @@ class MarkdownPreviewView extends ScrollView for codeBlock in preList.toArray() codeBlock = $(codeBlock.firstChild) - if className = codeBlock.attr('class') - fenceName = className.replace(/^lang-/, '') - if extension = fenceNameToExtension[fenceName] - text = codeBlock.text() - syntax.selectGrammar("foo.#{extension}", text) - if grammar = syntax.selectGrammar("foo.#{extension}", text) - continue if grammar is syntax.nullGrammar - tokens = grammar.tokenizeLines(text) - grouping = "" - for token in tokens - grouping += Editor.buildLineHtml(token, text) - codeBlock.replaceWith(grouping) + # go to next block unless this one has a class + continue unless className = codeBlock.attr('class') + + fenceName = className.replace(/^lang-/, '') + # go to next block unless the class name is matches `lang` + continue unless extension = fenceNameToExtension[fenceName] + text = codeBlock.text() + syntax.selectGrammar("foo.#{extension}", text) + + # go to next block if this grammar is not mapped + continue unless grammar = syntax.selectGrammar("foo.#{extension}", text) + continue if grammar is syntax.nullGrammar + + tokens = grammar.tokenizeLines(text) + grouping = "" + for token in tokens + grouping += Editor.buildHtmlLine(token, text) + codeBlock.replaceWith(grouping) + html - fetchRenderedMarkdown: -> + renderMarkdown: -> @setLoading() roaster(@buffer.getText(), {}, (err, html) => if err diff --git a/src/packages/markdown-preview/spec/markdown-preview-view-spec.coffee b/src/packages/markdown-preview/spec/markdown-preview-view-spec.coffee index 747f7f777..b3ae8b63c 100644 --- a/src/packages/markdown-preview/spec/markdown-preview-view-spec.coffee +++ b/src/packages/markdown-preview/spec/markdown-preview-view-spec.coffee @@ -16,12 +16,12 @@ describe "MarkdownPreviewView", -> describe "on construction", -> - it "shows a loading spinner and fetches the rendered markdown", -> + it "shows a loading spinner and renders the markdown", -> preview.setLoading() expect(preview.find('.markdown-spinner')).toExist() expect(preview.buffer.getText()).toBe buffer.getText() - preview.fetchRenderedMarkdown() + preview.renderMarkdown() expect(preview.find(".emoji")).toExist() it "shows an error message on error", -> @@ -33,7 +33,7 @@ describe "MarkdownPreviewView", -> newPreview = deserialize(preview.serialize()) expect(newPreview.buffer).toBe buffer - fdescribe "code block tokenization", -> + describe "code block tokenization", -> describe "when the code block's fence name has a matching grammar", -> it "tokenizes the code block with the grammar", -> expect(preview.find("pre span.entity.name.function.ruby")).toExist() From 252682dd99ebf7e705ca672530ac405bc59e6d4f Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Tue, 30 Apr 2013 11:20:50 -0700 Subject: [PATCH 07/12] Syntax highlight based entirely on editor colors --- spec/app/editor-spec.coffee | 2 +- .../lib/markdown-preview-view.coffee | 13 ++++++++++++- .../stylesheets/markdown-preview.less | 1 + 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index a222d8303..0aca4b840 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -1765,7 +1765,7 @@ describe "Editor", -> editor.edit(emptyEditSession) expect(editor.gutter.lineNumbers.find('.line-number').length).toBe 1 - fdescribe "when the editor is mini", -> + describe "when the editor is mini", -> it "hides the gutter", -> miniEditor = new Editor(mini: true) miniEditor.attachToDom() diff --git a/src/packages/markdown-preview/lib/markdown-preview-view.coffee b/src/packages/markdown-preview/lib/markdown-preview-view.coffee index acbcd0a62..19970ca21 100644 --- a/src/packages/markdown-preview/lib/markdown-preview-view.coffee +++ b/src/packages/markdown-preview/lib/markdown-preview-view.coffee @@ -65,12 +65,18 @@ class MarkdownPreviewView extends ScrollView setLoading: -> @html($$$ -> @div class: 'markdown-spinner', 'Loading Markdown...') + tokenizeCodeBlocks: (html) => html = $(html) preList = $(html.filter("pre")) + editorBackgroundColor = $('.editor').css("background-color") + rawEditorTextColor = $('.editor .gfm .raw').css("color") for codeBlock in preList.toArray() + $(codeBlock).css("background-color", editorBackgroundColor) codeBlock = $(codeBlock.firstChild) + # set the default raw color of unhiglighted pre tags + codeBlock.css("color", rawEditorTextColor) # go to next block unless this one has a class continue unless className = codeBlock.attr('class') @@ -85,11 +91,16 @@ class MarkdownPreviewView extends ScrollView continue unless grammar = syntax.selectGrammar("foo.#{extension}", text) continue if grammar is syntax.nullGrammar + text = codeBlock.text() tokens = grammar.tokenizeLines(text) grouping = "" for token in tokens - grouping += Editor.buildHtmlLine(token, text) + blockElem = $(Editor.buildHtmlLine(token, text)) + grouping += blockElem.addClass("editor")[0].outerHTML + codeBlock.replaceWith(grouping) + # undo default coloring + codeBlock.css("color", "") html diff --git a/src/packages/markdown-preview/stylesheets/markdown-preview.less b/src/packages/markdown-preview/stylesheets/markdown-preview.less index 533eab598..dd970afd8 100644 --- a/src/packages/markdown-preview/stylesheets/markdown-preview.less +++ b/src/packages/markdown-preview/stylesheets/markdown-preview.less @@ -23,6 +23,7 @@ // includes some GitHub Flavored Markdown specific styling (like @mentions) .markdown-preview { pre, + pre div.editor, code, tt { font-size: 12px; From 734b6778b1741238c783bb86ad675912b68e22d9 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Tue, 30 Apr 2013 11:37:06 -0700 Subject: [PATCH 08/12] Add more langauges to mapping --- .../markdown-preview/lib/markdown-preview-view.coffee | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/packages/markdown-preview/lib/markdown-preview-view.coffee b/src/packages/markdown-preview/lib/markdown-preview-view.coffee index 19970ca21..7909b4cf7 100644 --- a/src/packages/markdown-preview/lib/markdown-preview-view.coffee +++ b/src/packages/markdown-preview/lib/markdown-preview-view.coffee @@ -6,7 +6,12 @@ roaster = require 'roaster' Editor = require 'editor' fenceNameToExtension = + "coffeescript": "coffee" + "toml": "toml" "ruby": "rb" + "go": "go" + "mustache": "mustache" + "java": "java" module.exports = class MarkdownPreviewView extends ScrollView From 86484e742608df493cbbbc40c88ab86d5efaca10 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Tue, 30 Apr 2013 11:40:09 -0700 Subject: [PATCH 09/12] :lipstick: for renamed method call --- .../lib/markdown-preview-view.coffee | 2 +- .../lib/markdown-preview.coffee | 2 +- .../spec/markdown-preview-spec.coffee | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/packages/markdown-preview/lib/markdown-preview-view.coffee b/src/packages/markdown-preview/lib/markdown-preview-view.coffee index 7909b4cf7..6473a04f8 100644 --- a/src/packages/markdown-preview/lib/markdown-preview-view.coffee +++ b/src/packages/markdown-preview/lib/markdown-preview-view.coffee @@ -32,7 +32,7 @@ class MarkdownPreviewView extends ScrollView afterAttach: (onDom) -> @subscribe @buffer, 'saved', => - @fetchRenderedMarkdown() + @renderMarkdown() pane = @getPane() pane.showItem(this) if pane? and pane isnt rootView.getActivePane() diff --git a/src/packages/markdown-preview/lib/markdown-preview.coffee b/src/packages/markdown-preview/lib/markdown-preview.coffee index 8011339b9..90bb96f86 100644 --- a/src/packages/markdown-preview/lib/markdown-preview.coffee +++ b/src/packages/markdown-preview/lib/markdown-preview.coffee @@ -18,7 +18,7 @@ module.exports = {previewPane, previewItem} = @getExistingPreview(editSession) if previewItem? previewPane.showItem(previewItem) - previewItem.fetchRenderedMarkdown() + previewItem.renderMarkdown() else if nextPane = activePane.getNextPane() nextPane.showItem(new MarkdownPreviewView(editSession.buffer)) else diff --git a/src/packages/markdown-preview/spec/markdown-preview-spec.coffee b/src/packages/markdown-preview/spec/markdown-preview-spec.coffee index 8e590a229..5c816d4fe 100644 --- a/src/packages/markdown-preview/spec/markdown-preview-spec.coffee +++ b/src/packages/markdown-preview/spec/markdown-preview-spec.coffee @@ -8,7 +8,7 @@ describe "MarkdownPreview package", -> project.setPath(project.resolve('markdown')) window.rootView = new RootView atom.activatePackage("markdown-preview", immediate: true) - spyOn(MarkdownPreviewView.prototype, 'fetchRenderedMarkdown') + spyOn(MarkdownPreviewView.prototype, 'renderMarkdown') describe "markdown-preview:show", -> beforeEach -> @@ -61,9 +61,9 @@ describe "MarkdownPreview package", -> [pane] = rootView.getPanes() pane.focus() - MarkdownPreviewView.prototype.fetchRenderedMarkdown.reset() + MarkdownPreviewView.prototype.renderMarkdown.reset() pane.activeItem.buffer.trigger 'saved' - expect(MarkdownPreviewView.prototype.fetchRenderedMarkdown).not.toHaveBeenCalled() + expect(MarkdownPreviewView.prototype.renderMarkdown).not.toHaveBeenCalled() describe "when a preview item has already been created for the edit session's uri", -> it "updates and shows the existing preview item if it isn't displayed", -> @@ -77,9 +77,9 @@ describe "MarkdownPreview package", -> expect(pane2.activeItem).not.toBe preview pane1.focus() - preview.fetchRenderedMarkdown.reset() + preview.renderMarkdown.reset() rootView.getActiveView().trigger 'markdown-preview:show' - expect(preview.fetchRenderedMarkdown).toHaveBeenCalled() + expect(preview.renderMarkdown).toHaveBeenCalled() expect(rootView.getPanes()).toHaveLength 2 expect(pane2.getItems()).toHaveLength 2 expect(pane2.activeItem).toBe preview @@ -95,9 +95,9 @@ describe "MarkdownPreview package", -> pane1.showItemAtIndex(0) preview = pane1.itemAtIndex(1) - preview.fetchRenderedMarkdown.reset() + preview.renderMarkdown.reset() pane1.activeItem.buffer.trigger 'saved' - expect(preview.fetchRenderedMarkdown).toHaveBeenCalled() + expect(preview.renderMarkdown).toHaveBeenCalled() expect(pane1.activeItem).not.toBe preview describe "when the preview is not in the same pane", -> @@ -109,7 +109,7 @@ describe "MarkdownPreview package", -> expect(pane2.activeItem).not.toBe preview pane1.focus() - preview.fetchRenderedMarkdown.reset() + preview.renderMarkdown.reset() pane1.activeItem.buffer.trigger 'saved' - expect(preview.fetchRenderedMarkdown).toHaveBeenCalled() + expect(preview.renderMarkdown).toHaveBeenCalled() expect(pane2.activeItem).toBe preview From ab23f624afde0184c46678e8910d313b41ca1a09 Mon Sep 17 00:00:00 2001 From: Garen Torikian & Nathan Sobo Date: Thu, 2 May 2013 16:52:59 -0700 Subject: [PATCH 10/12] Add pairs file for gjt --- .pairs | 2 +- themes/atom-dark-syntax.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pairs b/.pairs index ec4f2417c..2aafc939d 100644 --- a/.pairs +++ b/.pairs @@ -6,7 +6,7 @@ pairs: jc: Jerry Cheung; jerry bl: Brian Lopez; brian jp: Justin Palmer; justin + gt: Garen Torikian; garen email: domain: github.com #global: true - diff --git a/themes/atom-dark-syntax.css b/themes/atom-dark-syntax.css index a095d3018..357f0886e 100644 --- a/themes/atom-dark-syntax.css +++ b/themes/atom-dark-syntax.css @@ -1,4 +1,4 @@ -.editor, .editor .gutter { +.editor-colors { background-color: #1d1f21; color: #c5c8c6; } From f4d7586034b50e0ffdebbb1702fe5201e2e76d9a Mon Sep 17 00:00:00 2001 From: Garen Torikian & Nathan Sobo Date: Thu, 2 May 2013 16:55:08 -0700 Subject: [PATCH 11/12] Refactor syntax highlighting of Markdown pre blocks Extract out editor-colors class to apply background and default text color Remove pre background coloring from Markdown preview stylesheet --- src/app/editor.coffee | 105 ++++++++++++++---- .../lib/markdown-preview-view.coffee | 24 +--- .../stylesheets/markdown-preview.less | 1 - 3 files changed, 90 insertions(+), 40 deletions(-) diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 1b4f66761..f2e381bb0 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -43,7 +43,7 @@ class Editor extends View @div outlet: 'verticalScrollbarContent' @classes: ({mini} = {}) -> - classes = ['editor'] + classes = ['editor', 'editor-colors'] classes.push 'mini' if mini classes.join(' ') @@ -1317,35 +1317,25 @@ class Editor extends View buildLineElementsForScreenRows: (startRow, endRow) -> div = document.createElement('div') - div.innerHTML = @buildHtmlLines(startRow, endRow) + div.innerHTML = @htmlForScreenRows(startRow, endRow) new Array(div.children...) - buildHtmlLines: (startRow, endRow) -> + htmlForScreenRows: (startRow, endRow) -> lines = @activeEditSession.linesForScreenRows(startRow, endRow) htmlLines = [] screenRow = startRow for line in @activeEditSession.linesForScreenRows(startRow, endRow) - htmlLines.push(@buildHtmlFromLine(line, screenRow++)) + htmlLines.push(@htmlForScreenLine(line, screenRow++)) htmlLines.join('\n\n') - @buildEmptyLineHtml: (guideIndentation, showInvisibles, activeEditSession) -> + @buildHtmlEmptyLine: (guideIndentation, showInvisibles, activeEditSession) -> if guideIndentation > 0 - indentationHtml = "#{_.multiplyString(' ', @activeEditSession.getTabLength())}" - return _.multiplyString(indentationHtml, indentation) + indentationHtml = "#{_.multiplyString(' ', activeEditSession.getTabLength())}" + return _.multiplyString(indentationHtml, guideIndentation) else if not showInvisibles ' ' - buildEndOfLineInvisibles: (screenLine) -> - invisibles = [] - for invisible in @getEndOfLineInvisibles(screenLine) - invisibles.push("#{invisible}") - invisibles.join('') - - getEndOfLineInvisibles: (screenLine) -> - return [] unless @showInvisibles and @invisibles - return [] if @mini or screenLine.isSoftWrapped() - - buildLineHtml: (screenLine, screenRow) -> + buildHtmlFromLine: (screenLine, screenRow) -> { tokens, text, lineEnding, fold } = screenLine if fold attributes = { class: 'fold line', 'fold-id': fold.id } @@ -1368,7 +1358,10 @@ class Editor extends View break invisibles = @invisibles if @showInvisibles - Editor.buildLineHtmlHelper({tokens, text, lineEnding, fold, isSoftWrapped, invisibles, attributes, guideIndentation, @showInvisibles, @activeEditSession, @mini}) + Editor.buildHtmlHelper({tokens, text, lineEnding, fold, isSoftWrapped, invisibles, attributes, guideIndentation, @showInvisibles, @activeEditSession, @mini}) + + @buildHtmlLine: (tokens, text) -> + @buildHtmlHelper( {tokens, text} ) @buildHtmlHelper: ({tokens, text, lineEnding, fold, isSoftWrapped, invisibles, attributes, guideIndentation, showInvisibles, activeEditSession, mini}) => scopeStack = [] @@ -1399,8 +1392,11 @@ class Editor extends View attributePairs.push "#{attributeName}=\"#{value}\"" for attributeName, value of attributes line.push("
") + if showInvisibles + console.log invisibles + if text == '' - html = Editor.buildEmptyLineHtml(guideIndentation, showInvisibles, activeEditSession) + html = Editor.buildHtmlEmptyLine(guideIndentation, showInvisibles, activeEditSession) line.push(html) if html else firstNonWhitespacePosition = text.search(/\S/) @@ -1416,7 +1412,7 @@ class Editor extends View position += token.value.length popScope() while scopeStack.length > 0 - if invisibles and not @mini and not isSoftWrapped + if invisibles and not mini and not isSoftWrapped if invisibles.cr and lineEnding is '\r\n' line.push("#{invisibles.cr}") if invisibles.eol @@ -1568,6 +1564,73 @@ class Editor extends View logCursorScope: -> console.log @activeEditSession.getCursorScopes() + @buildLineHtml: ({tokens, text, lineEnding, fold, isSoftWrapped, invisibles, attributes, guideIndentation, showInvisibles, activeEditSession, mini}) => + scopeStack = [] + line = [] + + updateScopeStack = (desiredScopes) -> + excessScopes = scopeStack.length - desiredScopes.length + _.times(excessScopes, popScope) if excessScopes > 0 + + # pop until common prefix + for i in [scopeStack.length..0] + break if _.isEqual(scopeStack[0...i], desiredScopes[0...i]) + popScope() + + # push on top of common prefix until scopeStack == desiredScopes + for j in [i...desiredScopes.length] + pushScope(desiredScopes[j]) + + pushScope = (scope) -> + scopeStack.push(scope) + line.push("") + + popScope = -> + scopeStack.pop() + line.push("") + + attributePairs = [] + attributePairs.push "#{attributeName}=\"#{value}\"" for attributeName, value of attributes + line.push("
") + + if showInvisibles + console.log invisibles + + if text == '' + html = @buildEmptyLineHtml(guideIndentation, showInvisibles, activeEditSession) + line.push(html) if html + else + firstNonWhitespacePosition = text.search(/\S/) + firstTrailingWhitespacePosition = text.search(/\s*$/) + lineIsWhitespaceOnly = firstTrailingWhitespacePosition is 0 + position = 0 + for token in tokens + updateScopeStack(token.scopes) + hasLeadingWhitespace = position < firstNonWhitespacePosition + hasTrailingWhitespace = position + token.value.length > firstTrailingWhitespacePosition + hasIndentGuide = not @mini and guideIndentation? and (hasLeadingWhitespace or lineIsWhitespaceOnly) + line.push(token.getValueAsHtml({invisibles, hasLeadingWhitespace, hasTrailingWhitespace, hasIndentGuide})) + position += token.value.length + + popScope() while scopeStack.length > 0 + if invisibles and not mini and not isSoftWrapped + if invisibles.cr and lineEnding is '\r\n' + line.push("#{invisibles.cr}") + if invisibles.eol + line.push("#{invisibles.eol}") + + line.push("") if fold + + line.push('
') + line.join('') + + @buildEmptyLineHtml: (guideIndentation, showInvisibles, activeEditSession) -> + if guideIndentation > 0 + indentationHtml = "#{_.multiplyString(' ', activeEditSession.getTabLength())}" + return _.multiplyString(indentationHtml, guideIndentation) + else if not showInvisibles + ' ' + transact: (fn) -> @activeEditSession.transact(fn) commit: -> @activeEditSession.commit() abort: -> @activeEditSession.abort() diff --git a/src/packages/markdown-preview/lib/markdown-preview-view.coffee b/src/packages/markdown-preview/lib/markdown-preview-view.coffee index 6473a04f8..6f0c231e1 100644 --- a/src/packages/markdown-preview/lib/markdown-preview-view.coffee +++ b/src/packages/markdown-preview/lib/markdown-preview-view.coffee @@ -74,14 +74,10 @@ class MarkdownPreviewView extends ScrollView tokenizeCodeBlocks: (html) => html = $(html) preList = $(html.filter("pre")) - editorBackgroundColor = $('.editor').css("background-color") - rawEditorTextColor = $('.editor .gfm .raw').css("color") - for codeBlock in preList.toArray() - $(codeBlock).css("background-color", editorBackgroundColor) - codeBlock = $(codeBlock.firstChild) - # set the default raw color of unhiglighted pre tags - codeBlock.css("color", rawEditorTextColor) + for preElement in preList.toArray() + $(preElement).addClass("editor-colors") + codeBlock = $(preElement.firstChild) # go to next block unless this one has a class continue unless className = codeBlock.attr('class') @@ -90,22 +86,14 @@ class MarkdownPreviewView extends ScrollView # go to next block unless the class name is matches `lang` continue unless extension = fenceNameToExtension[fenceName] text = codeBlock.text() - syntax.selectGrammar("foo.#{extension}", text) # go to next block if this grammar is not mapped continue unless grammar = syntax.selectGrammar("foo.#{extension}", text) continue if grammar is syntax.nullGrammar - text = codeBlock.text() - tokens = grammar.tokenizeLines(text) - grouping = "" - for token in tokens - blockElem = $(Editor.buildHtmlLine(token, text)) - grouping += blockElem.addClass("editor")[0].outerHTML - - codeBlock.replaceWith(grouping) - # undo default coloring - codeBlock.css("color", "") + codeBlock.empty() + for tokens in grammar.tokenizeLines(text) + codeBlock.append(Editor.buildLineHtml({ tokens, text })) html diff --git a/src/packages/markdown-preview/stylesheets/markdown-preview.less b/src/packages/markdown-preview/stylesheets/markdown-preview.less index dd970afd8..4265e8cac 100644 --- a/src/packages/markdown-preview/stylesheets/markdown-preview.less +++ b/src/packages/markdown-preview/stylesheets/markdown-preview.less @@ -386,7 +386,6 @@ } .highlight pre, pre { - background-color: #f8f8f8; border: 1px solid #ccc; font-size: 13px; line-height: 19px; From 839f0b642c9308af34e49c207d59656b66cbd731 Mon Sep 17 00:00:00 2001 From: Garen Torikian & Nathan Sobo Date: Fri, 3 May 2013 16:23:41 -0700 Subject: [PATCH 12/12] Merge Kevin's fix for empty line invisibles with the reshuffling of buildLineHtml methods to be on the class level --- src/app/editor.coffee | 207 +++++++++++++++++------------------------- 1 file changed, 82 insertions(+), 125 deletions(-) diff --git a/src/app/editor.coffee b/src/app/editor.coffee index f2e381bb0..81e87c301 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -1328,101 +1328,47 @@ class Editor extends View htmlLines.push(@htmlForScreenLine(line, screenRow++)) htmlLines.join('\n\n') - @buildHtmlEmptyLine: (guideIndentation, showInvisibles, activeEditSession) -> - if guideIndentation > 0 - indentationHtml = "#{_.multiplyString(' ', activeEditSession.getTabLength())}" - return _.multiplyString(indentationHtml, guideIndentation) - else if not showInvisibles - ' ' - - buildHtmlFromLine: (screenLine, screenRow) -> - { tokens, text, lineEnding, fold } = screenLine + htmlForScreenLine: (screenLine, screenRow) -> + { tokens, text, lineEnding, fold, isSoftWrapped } = screenLine if fold attributes = { class: 'fold line', 'fold-id': fold.id } else attributes = { class: 'line' } + invisibles = @invisibles if @showInvisibles + eolInvisibles = @getEndOfLineInvisibles(screenLine) + htmlEolInvisibles = @buildHtmlEndOfLineInvisibles(screenLine) + + indentation = Editor.buildIndentation(screenRow, @activeEditSession) + + Editor.buildLineHtml({tokens, text, lineEnding, fold, isSoftWrapped, invisibles, eolInvisibles, htmlEolInvisibles, attributes, @showIndentGuide, indentation, @activeEditSession, @mini}) + + @buildIndentation: (screenRow, activeEditSession) -> + indentation = 0 + while --screenRow >= 0 + bufferRow = activeEditSession.bufferPositionForScreenPosition([screenRow]).row + bufferLine = activeEditSession.lineForBufferRow(bufferRow) + unless bufferLine is '' + indentation = Math.ceil(activeEditSession.indentLevelForLine(bufferLine)) + break + + indentation + + buildHtmlEndOfLineInvisibles: (screenLine) -> + invisibles = [] + for invisible in @getEndOfLineInvisibles(screenLine) + invisibles.push("#{invisible}") + invisibles.join('') + + getEndOfLineInvisibles: (screenLine) -> + return [] unless @showInvisibles and @invisibles + return [] if @mini or screenLine.isSoftWrapped() + invisibles = [] invisibles.push(@invisibles.cr) if @invisibles.cr and screenLine.lineEnding is '\r\n' invisibles.push(@invisibles.eol) if @invisibles.eol invisibles - buildEmptyLineHtml: (screenRow) -> - if not @mini and @showIndentGuide - guideIndentation = 0 - while --screenRow >= 0 - bufferRow = @activeEditSession.bufferPositionForScreenPosition([screenRow]).row - bufferLine = @activeEditSession.lineForBufferRow(bufferRow) - unless bufferLine is '' - guideIndentation = Math.ceil(@activeEditSession.indentLevelForLine(bufferLine)) - break - - invisibles = @invisibles if @showInvisibles - Editor.buildHtmlHelper({tokens, text, lineEnding, fold, isSoftWrapped, invisibles, attributes, guideIndentation, @showInvisibles, @activeEditSession, @mini}) - - @buildHtmlLine: (tokens, text) -> - @buildHtmlHelper( {tokens, text} ) - - @buildHtmlHelper: ({tokens, text, lineEnding, fold, isSoftWrapped, invisibles, attributes, guideIndentation, showInvisibles, activeEditSession, mini}) => - scopeStack = [] - line = [] - - updateScopeStack = (desiredScopes) -> - excessScopes = scopeStack.length - desiredScopes.length - _.times(excessScopes, popScope) if excessScopes > 0 - - # pop until common prefix - for i in [scopeStack.length..0] - break if _.isEqual(scopeStack[0...i], desiredScopes[0...i]) - popScope() - - # push on top of common prefix until scopeStack == desiredScopes - for j in [i...desiredScopes.length] - pushScope(desiredScopes[j]) - - pushScope = (scope) -> - scopeStack.push(scope) - line.push("") - - popScope = -> - scopeStack.pop() - line.push("") - - attributePairs = [] - attributePairs.push "#{attributeName}=\"#{value}\"" for attributeName, value of attributes - line.push("
") - - if showInvisibles - console.log invisibles - - if text == '' - html = Editor.buildHtmlEmptyLine(guideIndentation, showInvisibles, activeEditSession) - line.push(html) if html - else - firstNonWhitespacePosition = text.search(/\S/) - firstTrailingWhitespacePosition = text.search(/\s*$/) - lineIsWhitespaceOnly = firstTrailingWhitespacePosition is 0 - position = 0 - for token in tokens - updateScopeStack(token.scopes) - hasLeadingWhitespace = position < firstNonWhitespacePosition - hasTrailingWhitespace = position + token.value.length > firstTrailingWhitespacePosition - hasIndentGuide = not @mini and guideIndentation? and (hasLeadingWhitespace or lineIsWhitespaceOnly) - line.push(token.getValueAsHtml({invisibles, hasLeadingWhitespace, hasTrailingWhitespace, hasIndentGuide})) - position += token.value.length - - popScope() while scopeStack.length > 0 - if invisibles and not mini and not isSoftWrapped - if invisibles.cr and lineEnding is '\r\n' - line.push("#{invisibles.cr}") - if invisibles.eol - line.push("#{invisibles.eol}") - - line.push("") if fold - - line.push('
') - line.join('') - lineElementForScreenRow: (screenRow) -> @renderedLines.children(":eq(#{screenRow - @firstRenderedScreenRow})") @@ -1447,7 +1393,7 @@ class Editor extends View # # Returns an object with two values: `top` and `left`, representing the pixel positions. pixelPositionForScreenPosition: (position) -> - return { top: 0, left: 0 } unless @isOnDom() and @isVisible() + return { top: 0, left: 0 } unless @isOnDom() and @isVisible() {row, column} = Point.fromObject(position) actualRow = Math.floor(row) @@ -1542,29 +1488,7 @@ class Editor extends View ### Internal ### - bindToKeyedEvent: (key, event, callback) -> - binding = {} - binding[key] = event - window.keymap.bindKeys '.editor', binding - @on event, => - callback(this, event) - - replaceSelectedText: (replaceFn) -> - selection = @getSelection() - return false if selection.isEmpty() - - text = replaceFn(@getTextInRange(selection.getBufferRange())) - return false if text is null or text is undefined - - @insertText(text, select: true) - true - - consolidateSelections: (e) -> e.abortKeyBinding() unless @activeEditSession.consolidateSelections() - - logCursorScope: -> - console.log @activeEditSession.getCursorScopes() - - @buildLineHtml: ({tokens, text, lineEnding, fold, isSoftWrapped, invisibles, attributes, guideIndentation, showInvisibles, activeEditSession, mini}) => + @buildLineHtml: ({tokens, text, lineEnding, fold, isSoftWrapped, invisibles, eolInvisibles, htmlEolInvisibles, attributes, showIndentGuide, indentation, activeEditSession, mini}) -> scopeStack = [] line = [] @@ -1593,11 +1517,8 @@ class Editor extends View attributePairs.push "#{attributeName}=\"#{value}\"" for attributeName, value of attributes line.push("
") - if showInvisibles - console.log invisibles - if text == '' - html = @buildEmptyLineHtml(guideIndentation, showInvisibles, activeEditSession) + html = Editor.buildEmptyLineHtml(showIndentGuide, eolInvisibles, htmlEolInvisibles, indentation, activeEditSession, mini) line.push(html) if html else firstNonWhitespacePosition = text.search(/\S/) @@ -1608,29 +1529,65 @@ class Editor extends View updateScopeStack(token.scopes) hasLeadingWhitespace = position < firstNonWhitespacePosition hasTrailingWhitespace = position + token.value.length > firstTrailingWhitespacePosition - hasIndentGuide = not @mini and guideIndentation? and (hasLeadingWhitespace or lineIsWhitespaceOnly) + hasIndentGuide = not mini and showIndentGuide and (hasLeadingWhitespace or lineIsWhitespaceOnly) line.push(token.getValueAsHtml({invisibles, hasLeadingWhitespace, hasTrailingWhitespace, hasIndentGuide})) position += token.value.length popScope() while scopeStack.length > 0 - if invisibles and not mini and not isSoftWrapped - if invisibles.cr and lineEnding is '\r\n' - line.push("#{invisibles.cr}") - if invisibles.eol - line.push("#{invisibles.eol}") - + line.push(htmlEolInvisibles) unless text == '' line.push("") if fold line.push('
') line.join('') - @buildEmptyLineHtml: (guideIndentation, showInvisibles, activeEditSession) -> - if guideIndentation > 0 - indentationHtml = "#{_.multiplyString(' ', activeEditSession.getTabLength())}" - return _.multiplyString(indentationHtml, guideIndentation) - else if not showInvisibles + @buildEmptyLineHtml: (showIndentGuide, eolInvisibles, htmlEolInvisibles, indentation, activeEditSession, mini) -> + if not mini and showIndentGuide + if indentation > 0 + tabLength = activeEditSession.getTabLength() + indentGuideHtml = [] + for level in [0...indentation] + indentLevelHtml = [""] + for characterPosition in [0...tabLength] + if invisible = eolInvisibles.shift() + indentLevelHtml.push("#{invisible}") + else + indentLevelHtml.push(' ') + indentLevelHtml.push("") + indentGuideHtml.push(indentLevelHtml.join('')) + + for invisible in eolInvisibles + indentGuideHtml.push("#{invisible}") + + return indentGuideHtml.join('') + + invisibles = htmlEolInvisibles + if invisibles.length > 0 + invisibles + else ' ' + bindToKeyedEvent: (key, event, callback) -> + binding = {} + binding[key] = event + window.keymap.bindKeys '.editor', binding + @on event, => + callback(this, event) + + replaceSelectedText: (replaceFn) -> + selection = @getSelection() + return false if selection.isEmpty() + + text = replaceFn(@getTextInRange(selection.getBufferRange())) + return false if text is null or text is undefined + + @insertText(text, select: true) + true + + consolidateSelections: (e) -> e.abortKeyBinding() unless @activeEditSession.consolidateSelections() + + logCursorScope: -> + console.log @activeEditSession.getCursorScopes() + transact: (fn) -> @activeEditSession.transact(fn) commit: -> @activeEditSession.commit() abort: -> @activeEditSession.abort()