Skip to content

Instantly share code, notes, and snippets.

@thecere
Created March 10, 2019 21:06

Revisions

  1. thecere created this gist Mar 10, 2019.
    172 changes: 172 additions & 0 deletions cm_diff.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,172 @@
    // derived from https://github.com/scniro/react-codemirror2/blob/master/src/index.tsx
    import React from 'react'

    const SERVER_RENDERED = process.env.UIX_ENV == 'express'

    let CodeMirror
    if (!SERVER_RENDERED) {
    CodeMirror = require('codemirror')
    }

    export class DiffView extends React.Component {
    constructor(props) {
    super(props)

    this.ref = null

    this.mergeview = null
    this.leftEditor = null
    this.editor = null
    this.rightEditor = null

    this.mounted = false
    }

    componentWillMount() {
    if (SERVER_RENDERED) return

    if (this.props.editorWillMount) {
    this.props.editorWillMount()
    }
    }

    createMergeView(options) {
    // this kills the old ones
    this.ref.innerHTML = ''

    // ok, we play some games with text initializiation
    // if we initialize with the real text contents, we get glitches in the "blue diff indicators"
    // so we init with empty strings and use a additional setValue call on the subeditors later
    let {value, origLeft, origRight, ...nonTextOptions} = options
    if (origLeft !== undefined) {
    nonTextOptions.origLeft = ''
    }
    if (origRight !== undefined) {
    nonTextOptions.origRight = ''
    }
    this.mergeview = CodeMirror.MergeView(this.ref, {...nonTextOptions, value: ''})

    // optional left or right
    this.leftEditor = this.mergeview.leftOriginal()
    this.rightEditor = this.mergeview.rightOriginal()

    // center editor (can be writable)
    this.editor = this.mergeview.editor()
    this.editor.on('change', (cm, data) => {
    if (!this.mounted) {
    return
    }
    if (this.props.onChange) {
    this.props.onChange(this.editor, data, this.editor.getValue())
    }
    })
    this.hackCSS(this.props.height)

    // ok, apply the init hack
    if (value) {
    this.editor.setValue(value)
    }
    if (this.leftEditor && origLeft) {
    this.leftEditor.setValue(origLeft)
    }
    if (this.rightEditor && origRight) {
    this.rightEditor.setValue(origRight)
    }

    if (this.props.editorDidMount) {
    this.props.editorDidMount(this.editor, this.leftEditor, this.rightEditor)
    }

    this.mergeview.setShowDifferences(options.highlightDifferences)
    }

    componentDidMount() {
    if (SERVER_RENDERED) return

    this.createMergeView(this.props.options)

    this.mounted = true
    }

    componentWillReceiveProps(nextProps) {
    if (SERVER_RENDERED) return

    if (this.props.height != nextProps.height || this.props.width != nextProps.width) {
    this.hackCSS(nextProps.height)
    }
    let prevOpts = this.props.options
    let nextOpts = nextProps.options

    let resetOptions = ['collapseIdentical', 'lineWrapping', 'connect', 'ignoreWhitespace']
    let reset = resetOptions.some(name => prevOpts[name] !== nextOpts[name])
    if (reset) {
    // full reset
    this.createMergeView(nextOpts)
    } else {
    if (prevOpts.highlightDifferences != nextOpts.highlightDifferences) {
    this.mergeview.setShowDifferences(nextOpts.highlightDifferences)
    }

    // XXX: handle all option changes with a reset?
    if (prevOpts.origLeft != nextOpts.origLeft) {
    this.leftEditor.setValue(nextOpts.origLeft)
    //~ this.createMergeView(nextOpts)
    }
    if (prevOpts.value != nextOpts.value) {
    this.editor.setValue(nextOpts.value)
    //~ this.createMergeView(nextProps.options)
    }

    //~ let self = this.mergeview
    //~ this.editor.operation(function() {
    //~ merge.collapseIdenticalStretches(self, true);
    //~ });

    //~ this.mergeview.setOption('collapseIdentical', true)
    }
    }

    componentWillUnmount() {
    if (SERVER_RENDERED) return

    this.mergeview = null
    this.leftEditor = null
    this.rightEditor = null
    }

    shouldComponentUpdate(nextProps, nextState) {
    if (SERVER_RENDERED) return false
    return true
    }

    hackCSS = height => {
    if (!this.ref || SERVER_RENDERED) return
    for (let e of this.ref.querySelectorAll('.codemirror-merge .CodeMirror')) {
    e.style.height = `${height}px`
    }

    // http://dwwebs.com/notes-common/codemirror/doc/manual.html#refresh
    for (let e of [this.editor, this.leftEditor, this.rightEditor]) {
    if (e) {
    e.refresh()
    }
    }
    }

    render() {
    if (SERVER_RENDERED) return null
    let {width, height} = this.props

    let className = this.props.className
    ? `codemirror-merge ${this.props.className}`
    : 'codemirror-merge'

    return (
    <div
    className={className}
    ref={self => (this.ref = self)}
    style={{width, height, overflow: 'hidden'}}
    />
    )
    }
    }