<commit_before>{CompositeDisposable} = require 'atom'

_ = require 'lodash'


Config = require './config'
KernelManager = require './kernel-manager'
ResultView = require './result-view'
SignalListView = require './signal-list-view'
WatchSidebar = require './watch-sidebar'
KernelPicker = require './kernel-picker'
AutocompleteProvider = require './autocomplete-provider'
Inspector = require './inspector'
CellManager = require './cell-manager'

module.exports = Hydrogen =
    config: Config.schema

    subscriptions: null

    statusBarElement: null
    statusBarTile: null

    editor: null

    markerBubbleMap: {}

    activate: (state) ->
        @subscriptions = new CompositeDisposable

        @subscriptions.add atom.commands.add 'atom-text-editor',
            'hydrogen:run': => @run()
            'hydrogen:run-all': => @runAll()
            'hydrogen:run-all-above': => @runAllAbove()
            'hydrogen:run-and-move-down': => @runAndMoveDown()
            'hydrogen:run-cell': => @runCell()
            'hydrogen:run-cell-and-move-down': => @runCellAndMoveDown()
            'hydrogen:toggle-watches': => @toggleWatchSidebar()
            'hydrogen:select-watch-kernel': => @showWatchKernelPicker()
            'hydrogen:select-kernel': => @showKernelPicker()
            'hydrogen:add-watch': => @watchSidebar.addWatchFromEditor()
            'hydrogen:remove-watch': => @watchSidebar.removeWatch()
            'hydrogen:update-kernels': -> KernelManager.updateKernelSpecs()
            'hydrogen:inspect': -> Inspector.inspect()
            'hydrogen:interrupt-kernel': =>
                @handleKernelCommand command: 'interrupt-kernel'
            'hydrogen:restart-kernel': =>
                @handleKernelCommand command: 'restart-kernel'

        @subscriptions.add atom.commands.add 'atom-workspace',
            'hydrogen:clear-results': => @clearResultBubbles()
            'hydrogen:toggle-inspector-size': -> Inspector.toggleInspectorSize()
            'hydrogen:close-inspector': -> Inspector.closeInspector()

        @statusBarElement = document.createElement('div')
        @statusBarElement.classList.add('hydrogen')
        @statusBarElement.classList.add('status-container')
        @statusBarElement.onclick = @showKernelCommands.bind @

        @editor = atom.workspace.getActiveTextEditor()

        @subscriptions.add atom.workspace.observeActivePaneItem (item) =>
            if item
                @editor = item
                @setStatusBarElement()

        KernelManager.updateKernelSpecs()


    deactivate: ->
        @subscriptions.dispose()
        KernelManager.destroy()
        @statusBarTile.destroy()


    consumeStatusBar: (statusBar) ->
        @statusBarTile = statusBar.addLeftTile
            item: @statusBarElement, priority: 100


    provide: ->
        if atom.config.get('Hydrogen.autocomplete') is true
            return AutocompleteProvider


    showKernelCommands: ->
        unless @signalListView?
            @signalListView = new SignalListView()
            @signalListView.onConfirmed = (kernelCommand) =>
                @handleKernelCommand kernelCommand
        @signalListView.toggle()


    handleKernelCommand: ({kernel, command, grammar, language, kernelSpec}) ->
        console.log 'handleKernelCommand:', arguments

        unless grammar
            grammar = @editor.getGrammar()
        unless language
            language = KernelManager.getGrammarLanguageFor grammar
        unless kernel
            kernel = KernelManager.getRunningKernelFor language

        if command is 'interrupt-kernel'
            kernel.interrupt()

        else if command is 'restart-kernel'
            KernelManager.destroyRunningKernel kernel
            @clearResultBubbles()
            KernelManager.startKernelFor grammar, =>
                @setStatusBarElement()

        else if command is 'switch-kernel'
            kernel = KernelManager.getRunningKernelFor language
            if kernel
                KernelManager.destroyRunningKernel kernel
            @clearResultBubbles()
            KernelManager.setKernelMapping kernelSpec, grammar
            KernelManager.startKernel kernelSpec, grammar, =>
                @setStatusBarElement()


    getCurrentKernel: ->
        grammar = @editor.getGrammar()
        language = KernelManager.getGrammarLanguageFor grammar
        kernel = KernelManager.getRunningKernelFor language

        return {grammar, language, kernel}


    createResultBubble: (code, row) ->
        {kernel, grammar} = @getCurrentKernel()

        if kernel
            @_createResultBubble kernel, code, row
            return

        KernelManager.startKernelFor grammar, (kernel) =>
            @setStatusBarElement()
            @_createResultBubble kernel, code, row


    _createResultBubble: (kernel, code, row) ->
        unless @watchSidebar?
            @setWatchSidebar kernel.watchSidebar
        else if @watchSidebar.element.contains document.activeElement
            @watchSidebar.run()
            return

        @clearBubblesOnRow row
        view = @insertResultBubble row
        kernel.execute code, (result) ->
            view.spin false
            view.addResult result


    insertResultBubble: (row) ->
        buffer = @editor.getBuffer()
        lineLength = buffer.lineLengthForRow(row)

        marker = @editor.markBufferPosition
            row: row
            column: lineLength
        ,
            invalidate: 'touch'

        view = new ResultView(marker)
        view.spin(true)
        element = view.getElement()

        lineHeight = @editor.getLineHeightInPixels()
        view.spinner.setAttribute('style',
                "width: #{lineHeight + 2}px; height: #{lineHeight - 4}px;")
        view.statusContainer.setAttribute('style', "height: #{lineHeight}px")
        element.setAttribute('style',
                "margin-left: #{lineLength + 1}ch;
                margin-top: -#{lineHeight}px")

        @editor.decorateMarker marker,
            type: 'block'
            item: element
            position: 'after'

        @markerBubbleMap[marker.id] = view
        marker.onDidChange (event) =>
            console.log 'marker.onDidChange:', marker
            if not event.isValid
                view.destroy()
                marker.destroy()
                delete @markerBubbleMap[marker.id]
            else
                if not element.classList.contains('multiline')
                    lineLength = marker.getStartBufferPosition()['column']
                    element.setAttribute('style',
                            "margin-left: #{lineLength + 1}ch;
                            margin-top: -#{lineHeight}px")

        return view


    clearResultBubbles: ->
        _.forEach @markerBubbleMap, (bubble) -> bubble.destroy()
        @markerBubbleMap = {}


    clearBubblesOnRow: (row) ->
        buffer = @editor.getBuffer()
        _.forEach buffer.findMarkers({endRow: row}), (marker) =>
            if @markerBubbleMap[marker.id]?
                @markerBubbleMap[marker.id].destroy()
                delete @markerBubbleMap[marker.id]


    moveDown: (row) ->
        lastRow = @editor.getLastBufferRow()

        if row >= lastRow
            @editor.moveToBottom()
            @editor.insertNewline()
            return

        while row < lastRow
            row++
            break if not @blank(row)

        @editor.setCursorBufferPosition
            row: row
            column: 0


    run: () ->
        codeBlock = @findCodeBlock()
        unless codeBlock?
            return

        [code, row] = codeBlock
        if code? and row?
            @createResultBubble code, row


    runAll: () ->
        code = @editor.getText()
        row = @editor.getLastBufferRow()
        if row > 0
            for i in [0 .. row - 1] when @blank(row)
                row -= 1
        @createResultBubble code, row


    runAllAbove: () ->
        codeBlock = @findCodeBlock(true)
        unless codeBlock?
            return

        [code, row] = codeBlock
        if code? and row?
            @createResultBubble code, row


    runAndMoveDown: () ->
        codeBlock = @findCodeBlock()
        unless codeBlock?
            return

        [code, row] = codeBlock
        if code? and row?
            @moveDown row
            @createResultBubble code, row

    runCell: () ->
        [startRow, endRow] = CellManager.getCurrentCell()
        if endRow > startRow
            for i in [startRow .. endRow - 1] when @blank(endRow)
                endRow -= 1
        code = @getRows(startRow, endRow)
        if code?
            @createResultBubble code, endRow

    runCellAndMoveDown: () ->
        [startRow, endRow] = CellManager.getCurrentCell()
        if endRow > startRow
            for i in [startRow .. endRow - 1] when @blank(endRow)
                endRow -= 1
        code = @getRows(startRow, endRow)
        if code?
            @moveDown endRow
            @createResultBubble code, endRow

    removeStatusBarElement: ->
        unless @statusBarElement?
            console.error 'removeStatusBarElement: there is no status bar'
            return

        while @statusBarElement.hasChildNodes()
            @statusBarElement.removeChild @statusBarElement.lastChild

    setStatusBarElement: ->
        unless @statusBarElement?
            console.error 'setStatusBarElement: there is no status bar'
            return

        @removeStatusBarElement()

        {kernel} = @getCurrentKernel()

        if kernel?
            @statusBarElement.appendChild kernel.statusView.getElement()

    hideWatchSidebar: ->
        unless @watchSidebar?
            console.log 'hideWatchSidebar: there is no sidebar'
            return

        @watchSidebar.hide()

    showWatchSidebar: ->
        unless @watchSidebar?
            console.log 'showWatchSidebar: there is no sidebar'
            return

        @watchSidebar.show()

    toggleWatchSidebar: ->
        if @watchSidebar?.visible
            console.log 'toggleWatchSidebar: hiding sidebar'
            @watchSidebar.hide()
        else
            console.log 'toggleWatchSidebar: showing sidebar'
            @watchSidebar.show()

    setWatchSidebar: (sidebar) ->
        console.log 'setting watch sidebar'
        if @watchSidebar isnt sidebar and @watchSidebar?.visible
            @watchSidebar.hide()
            @watchSidebar = sidebar
            @watchSidebar.show()
        else
            @watchSidebar = sidebar

    showKernelPicker: ->
        unless @kernelPicker?
            @kernelPicker = new KernelPicker =>
                grammar = @editor.getGrammar()
                language = KernelManager.getGrammarLanguageFor grammar
                return KernelManager.getAllKernelSpecsFor language
            @kernelPicker.onConfirmed = ({kernelSpec}) =>
                @handleKernelCommand
                    command: 'switch-kernel'
                    kernelSpec: kernelSpec

        @kernelPicker.toggle()

    showWatchKernelPicker: ->
        unless @watchKernelPicker?
            @watchKernelPicker = new KernelPicker ->
                KernelManager.getAllRunningKernels()
            @watchKernelPicker.onConfirmed = (command) =>
                @setWatchSidebar command.kernel.watchSidebar
        @watchKernelPicker.toggle()

    findCodeBlock: (runAllAbove = false) ->
        buffer = @editor.getBuffer()
        selectedText = @editor.getSelectedText()

        if selectedText
            selectedRange = @editor.getSelectedBufferRange()
            endRow = selectedRange.end.row
            if selectedRange.end.column is 0
                endRow = endRow - 1
            while @blank(endRow) and endRow > selectedRange.start.row
                endRow = endRow - 1
            return [selectedText, endRow]

        cursor = @editor.getLastCursor()

        row = cursor.getBufferRow()
        console.log 'findCodeBlock:', row

        if runAllAbove
            if row > 0
                for i in [0 .. row - 1] when @blank(row)
                    row -= 1
            return [@getRows(0, row), row]

        indentLevel = cursor.getIndentLevel()

        foldable = @editor.isFoldableAtBufferRow(row)
        foldRange = @editor.languageMode.rowRangeForCodeFoldAtBufferRow(row)
        if not foldRange? or not foldRange[0]? or not foldRange[1]?
            foldable = false

        if foldable
            return @getFoldContents(row)
        else if @blank(row)
            return @findPrecedingBlock(row, indentLevel)
        else if @getRow(row).trim() is 'end'
            return @findPrecedingBlock(row, indentLevel)
        else
            return [@getRow(row), row]

    findPrecedingBlock: (row, indentLevel) ->
        buffer = @editor.getBuffer()
        previousRow = row - 1
        while previousRow >= 0
            previousIndentLevel = @editor.indentationForBufferRow previousRow
            sameIndent = previousIndentLevel <= indentLevel
            blank = @blank(previousRow)
            isEnd = @getRow(previousRow).trim() is 'end'

            if @blank(row)
                row = previousRow
            if sameIndent and not blank and not isEnd
                return [@getRows(previousRow, row), row]
            previousRow--
        return null

    blank: (row) ->
        return @editor.getBuffer().isRowBlank(row) or
               @editor.languageMode.isLineCommentedAtBufferRow(row)

    getRow: (row) ->
        buffer = @editor.getBuffer()
        return buffer.getTextInRange
            start:
                row: row
                column: 0
            end:
                row: row
                column: 9999999

    getRows: (startRow, endRow) ->
        buffer = @editor.getBuffer()
        return buffer.getTextInRange
            start:
                row: startRow
                column: 0
            end:
                row: endRow
                column: 9999999

    getFoldRange: (editor, row) ->
        range = editor.languageMode.rowRangeForCodeFoldAtBufferRow(row)
        if @getRow(range[1] + 1).trim() is 'end'
            range[1] = range[1] + 1
        console.log 'getFoldRange:', range
        return range

    getFoldContents: (row) ->
        buffer = @editor.getBuffer()
        range = @getFoldRange(@editor, row)
        return [
                @getRows(range[0], range[1]),
                range[1]
            ]
<commit_msg>Fix statusbar interfering with settings view<commit_after>{CompositeDisposable} = require 'atom'

_ = require 'lodash'


Config = require './config'
KernelManager = require './kernel-manager'
ResultView = require './result-view'
SignalListView = require './signal-list-view'
WatchSidebar = require './watch-sidebar'
KernelPicker = require './kernel-picker'
AutocompleteProvider = require './autocomplete-provider'
Inspector = require './inspector'
CellManager = require './cell-manager'

module.exports = Hydrogen =
    config: Config.schema

    subscriptions: null

    statusBarElement: null
    statusBarTile: null

    editor: null

    markerBubbleMap: {}

    activate: (state) ->
        @subscriptions = new CompositeDisposable

        @subscriptions.add atom.commands.add 'atom-text-editor',
            'hydrogen:run': => @run()
            'hydrogen:run-all': => @runAll()
            'hydrogen:run-all-above': => @runAllAbove()
            'hydrogen:run-and-move-down': => @runAndMoveDown()
            'hydrogen:run-cell': => @runCell()
            'hydrogen:run-cell-and-move-down': => @runCellAndMoveDown()
            'hydrogen:toggle-watches': => @toggleWatchSidebar()
            'hydrogen:select-watch-kernel': => @showWatchKernelPicker()
            'hydrogen:select-kernel': => @showKernelPicker()
            'hydrogen:add-watch': => @watchSidebar.addWatchFromEditor()
            'hydrogen:remove-watch': => @watchSidebar.removeWatch()
            'hydrogen:update-kernels': -> KernelManager.updateKernelSpecs()
            'hydrogen:inspect': -> Inspector.inspect()
            'hydrogen:interrupt-kernel': =>
                @handleKernelCommand command: 'interrupt-kernel'
            'hydrogen:restart-kernel': =>
                @handleKernelCommand command: 'restart-kernel'

        @subscriptions.add atom.commands.add 'atom-workspace',
            'hydrogen:clear-results': => @clearResultBubbles()
            'hydrogen:toggle-inspector-size': -> Inspector.toggleInspectorSize()
            'hydrogen:close-inspector': -> Inspector.closeInspector()

        @statusBarElement = document.createElement('div')
        @statusBarElement.classList.add('hydrogen')
        @statusBarElement.classList.add('status-container')
        @statusBarElement.onclick = @showKernelCommands.bind @

        @editor = atom.workspace.getActiveTextEditor()

        @subscriptions.add atom.workspace.observeActivePaneItem (item) =>
            if item and item is atom.workspace.getActiveTextEditor()
                @editor = item
                @setStatusBarElement()

        KernelManager.updateKernelSpecs()


    deactivate: ->
        @subscriptions.dispose()
        KernelManager.destroy()
        @statusBarTile.destroy()


    consumeStatusBar: (statusBar) ->
        @statusBarTile = statusBar.addLeftTile
            item: @statusBarElement, priority: 100


    provide: ->
        if atom.config.get('Hydrogen.autocomplete') is true
            return AutocompleteProvider


    showKernelCommands: ->
        unless @signalListView?
            @signalListView = new SignalListView()
            @signalListView.onConfirmed = (kernelCommand) =>
                @handleKernelCommand kernelCommand
        @signalListView.toggle()


    handleKernelCommand: ({kernel, command, grammar, language, kernelSpec}) ->
        console.log 'handleKernelCommand:', arguments

        unless grammar
            grammar = @editor.getGrammar()
        unless language
            language = KernelManager.getGrammarLanguageFor grammar
        unless kernel
            kernel = KernelManager.getRunningKernelFor language

        if command is 'interrupt-kernel'
            kernel.interrupt()

        else if command is 'restart-kernel'
            KernelManager.destroyRunningKernel kernel
            @clearResultBubbles()
            KernelManager.startKernelFor grammar, =>
                @setStatusBarElement()

        else if command is 'switch-kernel'
            kernel = KernelManager.getRunningKernelFor language
            if kernel
                KernelManager.destroyRunningKernel kernel
            @clearResultBubbles()
            KernelManager.setKernelMapping kernelSpec, grammar
            KernelManager.startKernel kernelSpec, grammar, =>
                @setStatusBarElement()


    getCurrentKernel: ->
        grammar = @editor.getGrammar()
        language = KernelManager.getGrammarLanguageFor grammar
        kernel = KernelManager.getRunningKernelFor language

        return {grammar, language, kernel}


    createResultBubble: (code, row) ->
        {kernel, grammar} = @getCurrentKernel()

        if kernel
            @_createResultBubble kernel, code, row
            return

        KernelManager.startKernelFor grammar, (kernel) =>
            @setStatusBarElement()
            @_createResultBubble kernel, code, row


    _createResultBubble: (kernel, code, row) ->
        unless @watchSidebar?
            @setWatchSidebar kernel.watchSidebar
        else if @watchSidebar.element.contains document.activeElement
            @watchSidebar.run()
            return

        @clearBubblesOnRow row
        view = @insertResultBubble row
        kernel.execute code, (result) ->
            view.spin false
            view.addResult result


    insertResultBubble: (row) ->
        buffer = @editor.getBuffer()
        lineLength = buffer.lineLengthForRow(row)

        marker = @editor.markBufferPosition
            row: row
            column: lineLength
        ,
            invalidate: 'touch'

        view = new ResultView(marker)
        view.spin(true)
        element = view.getElement()

        lineHeight = @editor.getLineHeightInPixels()
        view.spinner.setAttribute('style',
                "width: #{lineHeight + 2}px; height: #{lineHeight - 4}px;")
        view.statusContainer.setAttribute('style', "height: #{lineHeight}px")
        element.setAttribute('style',
                "margin-left: #{lineLength + 1}ch;
                margin-top: -#{lineHeight}px")

        @editor.decorateMarker marker,
            type: 'block'
            item: element
            position: 'after'

        @markerBubbleMap[marker.id] = view
        marker.onDidChange (event) =>
            console.log 'marker.onDidChange:', marker
            if not event.isValid
                view.destroy()
                marker.destroy()
                delete @markerBubbleMap[marker.id]
            else
                if not element.classList.contains('multiline')
                    lineLength = marker.getStartBufferPosition()['column']
                    element.setAttribute('style',
                            "margin-left: #{lineLength + 1}ch;
                            margin-top: -#{lineHeight}px")

        return view


    clearResultBubbles: ->
        _.forEach @markerBubbleMap, (bubble) -> bubble.destroy()
        @markerBubbleMap = {}


    clearBubblesOnRow: (row) ->
        buffer = @editor.getBuffer()
        _.forEach buffer.findMarkers({endRow: row}), (marker) =>
            if @markerBubbleMap[marker.id]?
                @markerBubbleMap[marker.id].destroy()
                delete @markerBubbleMap[marker.id]


    moveDown: (row) ->
        lastRow = @editor.getLastBufferRow()

        if row >= lastRow
            @editor.moveToBottom()
            @editor.insertNewline()
            return

        while row < lastRow
            row++
            break if not @blank(row)

        @editor.setCursorBufferPosition
            row: row
            column: 0


    run: () ->
        codeBlock = @findCodeBlock()
        unless codeBlock?
            return

        [code, row] = codeBlock
        if code? and row?
            @createResultBubble code, row


    runAll: () ->
        code = @editor.getText()
        row = @editor.getLastBufferRow()
        if row > 0
            for i in [0 .. row - 1] when @blank(row)
                row -= 1
        @createResultBubble code, row


    runAllAbove: () ->
        codeBlock = @findCodeBlock(true)
        unless codeBlock?
            return

        [code, row] = codeBlock
        if code? and row?
            @createResultBubble code, row


    runAndMoveDown: () ->
        codeBlock = @findCodeBlock()
        unless codeBlock?
            return

        [code, row] = codeBlock
        if code? and row?
            @moveDown row
            @createResultBubble code, row

    runCell: () ->
        [startRow, endRow] = CellManager.getCurrentCell()
        if endRow > startRow
            for i in [startRow .. endRow - 1] when @blank(endRow)
                endRow -= 1
        code = @getRows(startRow, endRow)
        if code?
            @createResultBubble code, endRow

    runCellAndMoveDown: () ->
        [startRow, endRow] = CellManager.getCurrentCell()
        if endRow > startRow
            for i in [startRow .. endRow - 1] when @blank(endRow)
                endRow -= 1
        code = @getRows(startRow, endRow)
        if code?
            @moveDown endRow
            @createResultBubble code, endRow

    removeStatusBarElement: ->
        unless @statusBarElement?
            console.error 'removeStatusBarElement: there is no status bar'
            return

        while @statusBarElement.hasChildNodes()
            @statusBarElement.removeChild @statusBarElement.lastChild

    setStatusBarElement: ->
        unless @statusBarElement?
            console.error 'setStatusBarElement: there is no status bar'
            return

        @removeStatusBarElement()

        {kernel} = @getCurrentKernel()

        if kernel?
            @statusBarElement.appendChild kernel.statusView.getElement()

    hideWatchSidebar: ->
        unless @watchSidebar?
            console.log 'hideWatchSidebar: there is no sidebar'
            return

        @watchSidebar.hide()

    showWatchSidebar: ->
        unless @watchSidebar?
            console.log 'showWatchSidebar: there is no sidebar'
            return

        @watchSidebar.show()

    toggleWatchSidebar: ->
        if @watchSidebar?.visible
            console.log 'toggleWatchSidebar: hiding sidebar'
            @watchSidebar.hide()
        else
            console.log 'toggleWatchSidebar: showing sidebar'
            @watchSidebar.show()

    setWatchSidebar: (sidebar) ->
        console.log 'setting watch sidebar'
        if @watchSidebar isnt sidebar and @watchSidebar?.visible
            @watchSidebar.hide()
            @watchSidebar = sidebar
            @watchSidebar.show()
        else
            @watchSidebar = sidebar

    showKernelPicker: ->
        unless @kernelPicker?
            @kernelPicker = new KernelPicker =>
                grammar = @editor.getGrammar()
                language = KernelManager.getGrammarLanguageFor grammar
                return KernelManager.getAllKernelSpecsFor language
            @kernelPicker.onConfirmed = ({kernelSpec}) =>
                @handleKernelCommand
                    command: 'switch-kernel'
                    kernelSpec: kernelSpec

        @kernelPicker.toggle()

    showWatchKernelPicker: ->
        unless @watchKernelPicker?
            @watchKernelPicker = new KernelPicker ->
                KernelManager.getAllRunningKernels()
            @watchKernelPicker.onConfirmed = (command) =>
                @setWatchSidebar command.kernel.watchSidebar
        @watchKernelPicker.toggle()

    findCodeBlock: (runAllAbove = false) ->
        buffer = @editor.getBuffer()
        selectedText = @editor.getSelectedText()

        if selectedText
            selectedRange = @editor.getSelectedBufferRange()
            endRow = selectedRange.end.row
            if selectedRange.end.column is 0
                endRow = endRow - 1
            while @blank(endRow) and endRow > selectedRange.start.row
                endRow = endRow - 1
            return [selectedText, endRow]

        cursor = @editor.getLastCursor()

        row = cursor.getBufferRow()
        console.log 'findCodeBlock:', row

        if runAllAbove
            if row > 0
                for i in [0 .. row - 1] when @blank(row)
                    row -= 1
            return [@getRows(0, row), row]

        indentLevel = cursor.getIndentLevel()

        foldable = @editor.isFoldableAtBufferRow(row)
        foldRange = @editor.languageMode.rowRangeForCodeFoldAtBufferRow(row)
        if not foldRange? or not foldRange[0]? or not foldRange[1]?
            foldable = false

        if foldable
            return @getFoldContents(row)
        else if @blank(row)
            return @findPrecedingBlock(row, indentLevel)
        else if @getRow(row).trim() is 'end'
            return @findPrecedingBlock(row, indentLevel)
        else
            return [@getRow(row), row]

    findPrecedingBlock: (row, indentLevel) ->
        buffer = @editor.getBuffer()
        previousRow = row - 1
        while previousRow >= 0
            previousIndentLevel = @editor.indentationForBufferRow previousRow
            sameIndent = previousIndentLevel <= indentLevel
            blank = @blank(previousRow)
            isEnd = @getRow(previousRow).trim() is 'end'

            if @blank(row)
                row = previousRow
            if sameIndent and not blank and not isEnd
                return [@getRows(previousRow, row), row]
            previousRow--
        return null

    blank: (row) ->
        return @editor.getBuffer().isRowBlank(row) or
               @editor.languageMode.isLineCommentedAtBufferRow(row)

    getRow: (row) ->
        buffer = @editor.getBuffer()
        return buffer.getTextInRange
            start:
                row: row
                column: 0
            end:
                row: row
                column: 9999999

    getRows: (startRow, endRow) ->
        buffer = @editor.getBuffer()
        return buffer.getTextInRange
            start:
                row: startRow
                column: 0
            end:
                row: endRow
                column: 9999999

    getFoldRange: (editor, row) ->
        range = editor.languageMode.rowRangeForCodeFoldAtBufferRow(row)
        if @getRow(range[1] + 1).trim() is 'end'
            range[1] = range[1] + 1
        console.log 'getFoldRange:', range
        return range

    getFoldContents: (row) ->
        buffer = @editor.getBuffer()
        range = @getFoldRange(@editor, row)
        return [
                @getRows(range[0], range[1]),
                range[1]
            ]

{'fraction_non_alphanumeric': 0.09637054960248877, 'fraction_numerical': 0.003594884203249222, 'mean_word_length': 1.9365611043442956, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 147, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 4, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3548991', 'n_tokens_mistral': 7948, 'n_tokens_neox': 7427, 'n_words': 1667}