import { call , put , select , takeEvery } from 'redux-saga/effects' ; import { compressRange , Document , documentFromString , emptyDocument , expandRange , Selection } from "./document" ; import 'brace' ; import 'brace/mode/c_cpp' ; import 'brace/mode/python' ; import 'brace/snippets/html' ; import 'brace/ext/language_tools' ; import 'brace/theme/github' ; import 'brace/worker/javascript' ; import { ActionTypes } from "./actionTypes" ; import { ActionTypes as AppActionTypes } from "../actionTypes" ; import { getBufferModel } from "./selectors" ; import { current , immerable } from "immer" ; import { AppStore } from "../store" ; import { ReplayContext } from "../player/sagas" ; import { PlayerInstant } from "../player" ; import { Bundle } from "../linker" ; import { App } from "../index" ; import { getCurrentStepperState } from "../stepper/selectors" ; import { getNodeRange } from "../stepper" ; const AceThemes = [ 'github' , ] ; export class DocumentModel { [ immerable ] = true ; constructor ( public document = emptyDocument , public selection = new Selection ( ) , public firstVisibleRow = 0 ) { } } export class BufferState { [ immerable ] = true ; constructor ( public model = new DocumentModel ( ) ) { } editor = null ; } export const documentModelFromString = function ( text ) : DocumentModel { const doc = documentFromString ( text ) ; return new DocumentModel ( doc ) ; } ; function initBufferIfNeeded ( state , buffer ) { if ( ! ( buffer in state . buffers ) ) { state . buffers [ buffer ] = new BufferState ( ) ; } } export default function ( bundle ) { bundle . addReducer ( AppActionTypes . AppInit , ( state , { payload : { options : { source , input } } } ) => { state . buffers = { } ; if ( source ) { bufferLoadReducer ( state , { buffer : 'source' , text : source || '' } ) ; } if ( input ) { bufferLoadReducer ( state , { buffer : 'input' , text : input || '' } ) ; } } ) ; bundle . defineAction ( ActionTypes . BufferInit ) ; bundle . addReducer ( ActionTypes . BufferInit , ( state , action ) => { const { buffer , editor } = action ; initBufferIfNeeded ( state , buffer ) ; state . buffers [ buffer ] . editor = editor ; } ) ; bundle . defineAction ( ActionTypes . BufferLoad ) ; bundle . addReducer ( ActionTypes . BufferLoad , bufferLoadReducer ) ; bundle . defineAction ( ActionTypes . BufferReset ) ; bundle . addReducer ( ActionTypes . BufferReset , bufferResetReducer ) ; bundle . defineAction ( ActionTypes . BufferEdit ) ; bundle . addReducer ( ActionTypes . BufferEdit , bufferEditReducer ) ; bundle . defineAction ( ActionTypes . BufferSelect ) ; bundle . addReducer ( ActionTypes . BufferSelect , bufferSelectReducer ) ; bundle . defineAction ( ActionTypes . BufferScroll ) ; bundle . addReducer ( ActionTypes . BufferScroll , bufferScrollReducer ) ; bundle . defineAction ( ActionTypes . BufferModelEdit ) ; bundle . defineAction ( ActionTypes . BufferModelSelect ) ; bundle . defineAction ( ActionTypes . BufferModelScroll ) ; bundle . defineAction ( ActionTypes . BufferHighlight ) ; bundle . addSaga ( buffersSaga ) ; bundle . defer ( addRecordHooks ) ; bundle . defer ( addReplayHooks ) ; } ; function bufferLoadReducer ( state , action ) { const { buffer , text } = action ; initBufferIfNeeded ( state , buffer ) ; state . buffers [ buffer ] . model = new DocumentModel ( documentFromString ( text ) ) ; } function bufferResetReducer ( state , action ) { const { buffer , model } = action ; initBufferIfNeeded ( state , buffer ) ; state . buffers [ buffer ] . model = model ; } function bufferEditReducer ( state , action ) { const { buffer , delta } = action ; const oldDoc = state . buffers [ buffer ] . model . document ; state . buffers [ buffer ] . model . document = oldDoc . applyDelta ( delta ) ; } function bufferSelectReducer ( state , action ) { const { buffer , selection } = action ; state . buffers [ buffer ] . model . selection = selection ; } function bufferScrollReducer ( state , action ) { const { buffer , firstVisibleRow } = action ; state . buffers [ buffer ] . model . firstVisibleRow = firstVisibleRow ; } function loadBufferModel ( dump ) { return new DocumentModel ( documentFromString ( dump . document ) , expandRange ( dump . selection ) , dump . firstVisibleRow || 0 ) ; } function getBufferEditor ( state , buffer ) { return state . buffers [ buffer ] . editor ; } function * buffersSaga ( ) { yield takeEvery ( ActionTypes . BufferInit , function * ( action ) { const state = yield select ( ) ; const { buffer , editor } = action ; if ( editor ) { const model = getBufferModel ( state , buffer ) ; resetEditor ( editor , model ) ; } } ) ; yield takeEvery ( ActionTypes . BufferReset , function * ( action ) { const state = yield select ( ) ; const { buffer , model , quiet , goToEnd } = action ; if ( ! quiet ) { const editor = getBufferEditor ( state , buffer ) ; if ( editor ) { resetEditor ( editor , model , ! ! goToEnd ) ; } } } ) ; yield takeEvery ( ActionTypes . BufferModelSelect , function * ( action ) { const state = yield select ( ) ; const { buffer , selection } = action ; const editor = getBufferEditor ( state , buffer ) ; if ( editor ) { editor . setSelection ( selection ) ; } } ) ; yield takeEvery ( ActionTypes . BufferModelEdit , function * ( action ) { const state = yield select ( ) ; const { buffer , delta , deltas } = action ; const editor = getBufferEditor ( state , buffer ) ; if ( editor ) { editor . applyDeltas ( deltas || [ delta ] ) ; } } ) ; yield takeEvery ( ActionTypes . BufferModelScroll , function * ( action ) { const state = yield select ( ) ; const { buffer , firstVisibleRow } = action ; const editor = getBufferEditor ( state , buffer ) ; if ( editor ) { editor . scrollToLine ( firstVisibleRow ) ; } } ) ; yield takeEvery ( ActionTypes . BufferHighlight , function * ( action ) { const state = yield select ( ) ; const { buffer , range } = action ; const editor = getBufferEditor ( state , buffer ) ; if ( editor ) { editor . highlight ( range ) ; } } ) ; yield takeEvery ( ActionTypes . BufferResize , function * ( action ) { const state = yield select ( ) ; const { buffer } = action ; const editor = getBufferEditor ( state , buffer ) ; if ( editor ) { editor . resize ( ) ; } } ) ; } function resetEditor ( editor , model , goToEnd ? ) { try { const text = model . document . toString ( ) ; editor . reset ( text , model . selection , model . firstVisibleRow ) ; if ( goToEnd ) { editor . goToEnd ( ) ; } } catch ( error ) { console . log ( 'failed to update editor view with model' , error ) ; } } function addRecordHooks ( { recordApi } ) { recordApi . onStart ( function * ( init ) { const state = yield select ( ) ; init . buffers = { } ; for ( let bufferName of Object . keys ( state . buffers ) ) { const bufferModel = getBufferModel ( state , bufferName ) ; init . buffers [ bufferName ] = { document : bufferModel . document . toString ( ) , selection : compressRange ( bufferModel . selection ) , firstVisibleRow : bufferModel . firstVisibleRow } ; } } ) ; recordApi . on ( ActionTypes . BufferSelect , function * ( addEvent , action ) { const { buffer , selection } = action ; yield call ( addEvent , 'buffer.select' , buffer , compressRange ( selection ) ) ; } ) ; recordApi . on ( ActionTypes . BufferEdit , function * ( addEvent , action ) { const state = yield select ( ) ; const { buffer , delta } = action ; const { start , end } = delta ; const range = { start , end } ; const { platform } = state . options ; if ( buffer === 'output' && platform === 'python' ) { return ; } if ( delta . action === 'insert' ) { yield call ( addEvent , 'buffer.insert' , buffer , compressRange ( range ) , delta . lines ) ; } else { yield call ( addEvent , 'buffer.delete' , buffer , compressRange ( range ) ) ; } } ) ; recordApi . on ( ActionTypes . BufferScroll , function * ( addEvent , action ) { const { buffer , firstVisibleRow } = action ; yield call ( addEvent , 'buffer.scroll' , buffer , firstVisibleRow ) ; } ) ; } function addReplayHooks ( { replayApi } ) { console . log ( 'ADD REPLAY HOOKS' ) ; replayApi . on ( 'start' , function * ( replayContext , event ) { const { buffers } = event [ 2 ] ; for ( let bufferName of Object . keys ( buffers ) ) { const text = buffers [ bufferName ] . document ; yield put ( { type : ActionTypes . BufferLoad , buffer : bufferName , text } ) ; } } ) ; replayApi . on ( 'buffer.select' , function * ( replayContext , event ) { const buffer = event [ 2 ] ; const selection = expandRange ( event [ 3 ] ) ; yield put ( { type : ActionTypes . BufferSelect , buffer , selection } ) ; replayContext . addSaga ( function * ( ) { yield put ( { type : ActionTypes . BufferModelSelect , buffer , selection } ) ; } ) ; } ) ; replayApi . on ( [ 'buffer.insert' , 'buffer.delete' ] , function * ( replayContext , event ) { const buffer = event [ 2 ] ; const range = expandRange ( event [ 3 ] ) ; let delta ; if ( event [ 1 ] . endsWith ( 'insert' ) ) { delta = { action : 'insert' , start : range . start , end : range . end , lines : event [ 4 ] } ; } else if ( event [ 1 ] . endsWith ( 'delete' ) ) { delta = { action : 'remove' , start : range . start , end : range . end } ; } if ( delta ) { yield put ( { type : ActionTypes . BufferEdit , buffer , delta } ) ; yield call ( replayApi . applyEvent , 'buffer.edit' , replayContext , [ buffer ] ) ; replayContext . addSaga ( function * ( ) { yield put ( { type : ActionTypes . BufferModelEdit , buffer , delta } ) ; } ) ; } } ) ; replayApi . on ( 'buffer.scroll' , function * ( replayContext , event ) { const buffer = event [ 2 ] ; const firstVisibleRow = event [ 3 ] ; yield put ( { type : ActionTypes . BufferScroll , buffer , firstVisibleRow } ) ; replayContext . addSaga ( function * ( ) { yield put ( { type : ActionTypes . BufferModelScroll , buffer , firstVisibleRow } ) ; } ) ; } ) ; replayApi . onReset ( function * ( { state } , quick ) { console . log ( 'BUFFER RESET' , state ) ; for ( let buffer of Object . keys ( state . buffers ) ) { const model = state . buffers [ buffer ] . model ; yield put ( { type : ActionTypes . BufferReset , buffer , model , quiet : quick } ) ; } const range = getNodeRange ( getCurrentStepperState ( state ) ) ; yield put ( { type : ActionTypes . BufferHighlight , buffer : 'source' , range } ) ; } ) ; }