You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
educoder/public/react/src/modules/page/component/TPICodeMirror.js

523 lines
15 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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 (
<React.Fragment>
<Drawer
anchor="right"
className="settingDrawer"
width={260}
open={settingDrawerOpen}
onClose={() => showSettingDrawer( false )}
>
<TPICodeSetting {...this.props} {...this.state}
onFontSizeChange={this.onFontSizeChange}
onAutoCompleteSwitchChange={this.onAutoCompleteSwitchChange}
></TPICodeSetting>
</Drawer>
<div className="cmWrapper" style={{fontSize: `${cmFontSize}px`}}>
<textarea className = "" id="extend-challenge-file-edit" name="content">{repositoryCode}</textarea>
</div>
</React.Fragment>
);
}
}
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)