import React, { Component } from 'react'; import Dialog, { DialogActions, DialogContent, DialogContentText, DialogTitle, } from 'material-ui/Dialog'; import _ from 'lodash' import Drawer from 'material-ui/Drawer'; import './TPICodeMirror.css' import TPICodeSetting from './TPICodeSetting' import { fromStore, toStore } from 'educoder' require('codemirror/lib/codemirror.css'); // require('codemirror/mode/javascript/javascript'); // require('codemirror/mode/xml/xml'); // require('codemirror/mode/markdown/markdown'); function getModeByMirrorName(mirror_name) { let mode = 'javascript' if (mirror_name && mirror_name.length) { for (let i = 0; i < mirror_name.length; i++) { let modeVal = mirrorNameModeMap[mirror_name[i]]; if (modeVal) { mode = modeVal; break; } } } return mode; } const _extraKeys = {"Alt-/": "autocomplete"}; function createCMOptions(mirror_name) { let mode = getModeByMirrorName(mirror_name) let cmOptions = { lineNumbers: true, mode: mode, theme: "railscasts", indentUnit:4, matchBrackets: true, autoRefresh: true, smartIndent: true,//智能换行 extraKeys: _extraKeys, autofocus: true, styleActiveLine: true, lint: true, gutters: ["CodeMirror-linenumbers", "breakpoints", "CodeMirror-lint-markers"] }; return cmOptions; } const mirrorNameModeMap = { 'JFinal': 'text/x-java', 'Java': 'text/x-java', 'Kotlin': 'text/x-kotlin', 'C/C++' : 'text/x-c++src', 'MachineLearning': { name: "python", version: 3, singleLineStringErrors: false }, 'Python2.7': { name: "python", version: 3, singleLineStringErrors: false }, 'Python3.6': { name: "python", version: 3, singleLineStringErrors: false }, } let extend_editor = null; let notCallCodeMirrorOnChangeFlag = false; const $ = window.$; /* lint的实现,目前只支持javascript\html\css\coffeescript\json的lint,支持的语言版本有待考量 底层gutter mark实现使用的还是setGutterMarker接口(参见lint.js的189行) 红色波浪线实现方式: _cm.markText({line:4, ch:32}, {line:4, ch:40}, { className: "CodeMirror-lint-mark-error", __annotation: {message: "Expected an identifier and instead saw ';'.", severity: "error"} }) */ class TPICodeMirror extends Component { constructor(props) { super(props) this.state = { cmFontSize: fromStore('cmFontSize', 16), autoCompleteSwitch: fromStore('autoCompleteSwitch', true), } } onAutoCompleteSwitchChange = () => { extend_editor.setOption({ extraKeys: this.state.autoCompleteSwitch ? _extraKeys : {"Ctrl-Alt-/": "autocomplete"} }) toStore('autoCompleteSwitch', !this.state.autoCompleteSwitch) this.setState({ autoCompleteSwitch: !this.state.autoCompleteSwitch }) } componentDidUpdate(prevProps, prevState, snapshot) { const { game, mirror_name } = this.props if (extend_editor && !_.isEqual(prevProps.mirror_name, mirror_name)) { extend_editor.setOption("mode", getModeByMirrorName(mirror_name)); } } componentDidMount() { let cmOptions = createCMOptions(this.props.mirror_name) extend_editor = window.CodeMirror.fromTextArea(window.$('#extend-challenge-file-edit')[0] , cmOptions); extend_editor.on('beforeChange', (cm,change) => { // if ( ~readOnlyLines.indexOf(change.from.line) ) { // change.cancel(); // } if (change.origin === "setValue") { return; } if (!this.props.isEditablePath) { change.cancel(); } }); extend_editor.on('change', (cMirror) => { // get value right from instance // $('#extend-challenge-file-edit').val(cMirror.getValue()); if (notCallCodeMirrorOnChangeFlag === true) { // 避免死循环 onRepositoryCodeUpdate 和 componentWillReceiveProps notCallCodeMirrorOnChangeFlag = false; return; } this.props.onRepositoryCodeUpdate(cMirror.getValue()) }); extend_editor.refresh(); // wtf 加了这句后,禁用快捷键唤起autocomplete就生效了。。。 extend_editor.setOption('extraKeys', {"Ctrl-Alt-/": "autocomplete"}) // 拖拽也需要用 : window.editor_CodeMirror.refresh() window.editor_CodeMirror = extend_editor; // tpi_html_show需要用到 this.initHint() this.props.codemirrorDidMount(); } initHint() { window.CodeMirror.showHint && extend_editor.on('keyup', (editor_arg, event) => { if (event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'ArrowLeft' || event.key === 'ArrowRight' || event.key === 'Enter' || event.key === 'Space' // 32 空格 || event.key === 'Escape' || event.keyCode === 32 ) { // 避免使用键盘选择hint的时候再次触发 // TODO 增加的键 :ctrl+v ctrl+z return; } var cursor = extend_editor.getCursor(); var lineText = extend_editor.getLine(cursor.line); const lastCharInput = lineText.charAt(cursor.ch - 1).trim() if (lineText && /^[a-zA-Z0-9_]+$/.test(lastCharInput) === true) { this.state.autoCompleteSwitch && extend_editor.showHint(editor_arg); } }); let languageHints; if (this.props.challenge.isHtml === true) { languageHints = allCssPropValueArray } window.CodeMirror.on(extend_editor, "hinting", (words) => { // extend_editor.state.needToClearJSHint = true; // 每次调用完成后,needToClearJSHint会被置为false var result = window.CodeMirror.hint.anyword(extend_editor) // 获取当前editor里的单词 extend_editor.state.myhints = languageHints || [] var myhints = extend_editor.state.myhints; result.list.forEach(function(item) { if (myhints.indexOf(item) === -1) myhints.push(item) }) }) window.document.onkeydown = (e) => { e=window.event||e; if(e.keyCode== 83 && e.ctrlKey){ /*延迟,兼容FF浏览器 */ // setTimeout(function(){ // alert('ctrl+s'); // },1); this.props.doFileUpdateRequestOnCodeMirrorBlur(); return false; } }; window.CodeMirror.registerHelper( "hintWords", "javascript", ( // string "charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " + "toUpperCase toLowerCase split concat match replace search " + // array "length concat join splice push pop shift unshift slice reverse sort indexOf " + "lastIndexOf every some filter forEach map reduce reduceRight " + // Math "sin cos tan abs ceil floor exp max min pow PI random " + "console log " + "prototype apply call bind " + "double float int long short null true false enum super this void auto for register static const friend mutable explicit virtual template typename " + "break continue return do while if else for instanceof switch case default try catch finally throw throws assert import byte char delete " + "export operator with " + "print exec raise lambda private protected public abstract class extends final implements interface native new static " + "String vector Boolean function").split(" ")); } componentWillReceiveProps(newProps) { if (this.props.codeLoading === true && newProps.codeLoading === false && newProps.repositoryCode != extend_editor.getValue()) { // newProps.repositoryCode !== this.props.repositoryCode && notCallCodeMirrorOnChangeFlag = true; // 重要:setState(因获取代码、重置代码等接口引起的调用)调用引起的变化才需要setValue extend_editor.setValue(newProps.repositoryCode) // Clears the editor's undo history. extend_editor.clearHistory() } } onFontSizeChange = (value) => { toStore('cmFontSize', value), this.setState({ cmFontSize: value }); } render() { const { repositoryCode, showSettingDrawer, settingDrawerOpen } = this.props; const { cmFontSize } = this.state; return ( showSettingDrawer( false )} >
); } } export default ( TPICodeMirror ) ; // prop http://css-infos.net/ const allCssPropertiesArray = `alignment-adjust alignment-baseline appearance azimuth background background-attachment background-clip background-color background-image background-origin background-position background-repeat background-size baseline-shift bookmark-label bookmark-level bookmark-target border border-bottom border-bottom-color border-bottom-left-radius border-bottom-right-radius border-bottom-style border-bottom-width border-clip border-collapse border-color border-image border-left border-left-color border-left-style border-left-width border-radius border-right border-right-color border-right-style border-right-width border-spacing border-style border-top border-top-color border-top-left-radius border-top-right-radius border-top-style border-top-width border-width bottom box-decoration-break box-shadow caption-side clear clip color column-count column-fill column-gap column-rule column-rule-color column-rule-style column-rule-width column-span column-width columns content counter-increment counter-reset crop cue cue-after cue-before cursor direction display dominant-baseline drop-initial-after-adjust drop-initial-after-align drop-initial-before-adjust drop-initial-before-align drop-initial-size drop-initial-value elevation empty-cells fit fit-position float float-offset font font-family font-size font-size-adjust font-stretch font-style font-variant font-weight grid-columns grid-rows hanging-punctuation height hyphenate-after hyphenate-before hyphenate-character hyphenate-lines hyphenate-resource hyphens icon image-orientation image-resolution inline-box-align left letter-spacing line-height line-stacking line-stacking-ruby line-stacking-shift line-stacking-strategy list-style list-style-image list-style-position list-style-type margin margin-bottom margin-left margin-right margin-top mark mark-after mark-before marker-offset marks marquee-direction marquee-loop marquee-speed marquee-style max-height max-width min-height min-width move-to nav-down nav-index nav-left nav-right nav-up opacity orphans outline outline-color outline-offset outline-style outline-width overflow overflow-style overflow-x overflow-y padding padding-bottom padding-left padding-right padding-top page page-break-after page-break-before page-break-inside page-policy pause pause-after pause-before phonemes pitch pitch-range play-during pointer-events position presentation-level punctuation-trim quotes rendering-intent resize rest rest-after rest-before richness right rotation rotation-point ruby-align ruby-overhang ruby-position ruby-span size speak speak-header speak-numeral speak-punctuation speech-rate stress string-set tab-side table-layout target target-name target-new target-position text-align text-align-last text-decoration text-emphasis text-height text-indent text-justify text-outline text-overflow text-shadow text-transform text-wrap top unicode-bidi vertical-align visibility voice-balance voice-duration voice-family voice-pitch voice-pitch-range voice-rate voice-stress voice-volume volume white-space white-space-collapse widows width word-break word-spacing word-wrap z-index`.split('\n') // value http://www.siliconbaytraining.com/pages/csspv.html /* var array =[]; $('.test tr td:nth-child(2) font').each((index, item) => array = array.concat($(item).text().split(', '))) var mySet = new Set(array) array = Array.from(mySet) var array2=array.filter(item=> {return item.indexOf('(') == -1 && item.length < 18 && item != 'Values'}) array2 = array2.map((item) => item.trim().replace('↵','')) */ const allCssValueArray = "none,inherit,normal,wider,narrower,ultra-condensed,semi-condensed,semi-expanded,expanded,extra-expanded,ultra-expanded,italic,oblique,small-caps,bold,bolder,lighter,xx-small,x-small,small,medium,large,x-large,xx-large,larger,smaller,1em,left,right,center,justify,underline,overline,line-through,blink,capitalize,uppercase,lowercase,scroll,fixed,transparent,top,bottom,repeat,repeat-x,repeat-y,no-repeat,auto,thin,thick,dotted,dashed,solid,double,groove,ridge,inset,outset,disc,circle,square,decimal,lower-roman,upper-roman,lower-alpha,upper-alpha,upper-latin,hebrew,armenian,georgian,cjk-ideographic,hiragana,katakana,hiragana-iroha,katakana-iroh,outside,inside,pre,nowrap,crosshair,default,pointer,move,e-resize,ne-resize,nw-resize,n-resize,se-resize,sw-resize,s-resize,w-resize,text,wait,help,invert,visible,hidden,open-quote,close-quote,no-open-quote,no-close-quote,inherit,none,both,ltr,rtl,inline,block,list-item,run-in,compact,marker,table,inline-table,table-row-group,table-row,table-caption,static,absolute,relative,embed,bidi-override,baseline,sub,super,text-top,middle,text-bottom,collapse,separate,show,hide,once,always,number,percentage,silent,x-soft,soft,loud,x-loud,spell-out,time,percentage,uri,mix,angle,left-side,far-left,center-left,center-right,far-right,right-side,behind,leftwards,rightwards,below,level,above,higher,lower,x-slow,slow,fast,x-fast,faster,slower,frequency,x-low,low,high,x-high,code,digits,continuous".split(',') const allCssPropValueArray = allCssPropertiesArray.concat(allCssValueArray)