editor.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908
  1. /*
  2. * This file is part of HexoEditor.
  3. *
  4. * Copyright (c) 2018 zhuzhuyule <zhuzhuyule@gmail.com>
  5. *
  6. * HexoEditor is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * HexoEditor is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with HexoEditor. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. const FormatTables = require('./formatTables');
  20. module.exports = (() => {
  21. var keyMaps = {
  22. Esc: 'singleSelection',
  23. Enter: 'newlineAndIndentContinueMarkdownList',
  24. Home: 'goLineLeft',
  25. End: 'goLineRight',
  26. 'Tab':tabAdd,
  27. 'Shift-Tab': tabSubtract,
  28. 'Ctrl-B': toggleBlod,
  29. 'Ctrl-I': toggleItalic,
  30. 'Ctrl-D': toggleDelete,
  31. 'Ctrl-`': toggleComment,
  32. 'Ctrl-L': toggleUnOrderedList,
  33. 'Ctrl-Alt-L': toggleOrderedList,
  34. 'Ctrl-]': toggleHeader,
  35. 'Ctrl-[': toggleUnHeader,
  36. 'Ctrl-=': toggleBlockquote,
  37. 'Ctrl--': toggleUnBlockquote,
  38. 'Ctrl-U': drawLink,
  39. 'Ctrl-Alt-U': drawImageLink,
  40. 'Ctrl-T': drawTable,
  41. 'Ctrl-V': pasteOriginContent,
  42. 'Ctrl-Shift-V': pasteContent,
  43. 'Alt-F': formatTables
  44. }
  45. /**
  46. * Action for add space of "indentUnit" count.
  47. */
  48. function tabAdd(cm){
  49. let indentCount = parseInt(cm.getOption("indentUnit")) || 4;
  50. cm.indentSelection(indentCount);
  51. }
  52. /**
  53. * Action for subtract space of "indentUnit" count or delete start modifier.
  54. */
  55. function tabSubtract(cm) {
  56. const posStart = cm.getCursor('from');
  57. const posEnd = cm.getCursor('to');
  58. let needSubtract = true;
  59. if (posStart.line === posEnd.line) {
  60. let line = cm.getLine(posStart.line);
  61. line.replace(/^([*+-] |\d\. )(.*)$/, (all, part1, part2) => {
  62. posStart.ch -= part1.length;
  63. cm.replaceRange(part2, CodeMirror.Pos(posStart.line, 0), CodeMirror.Pos(posStart.line, all.length))
  64. cm.setCursor(posStart);
  65. cm.focus();
  66. needSubtract = false;
  67. })
  68. }
  69. if (needSubtract) {
  70. let indentCount = parseInt(cm.getOption("indentUnit")) || 4;
  71. cm.indentSelection(-indentCount);
  72. }
  73. }
  74. /**
  75. * Fix shortcut. Mac use Command, others use Ctrl.
  76. */
  77. function fixShortcut() {
  78. if (process.platform == 'drawin')
  79. Object.keys(keyMaps).forEach(item => {
  80. if (item.indexOf('Ctrl') > -1) {
  81. var val = keyMaps[item];
  82. delete keyMaps[item];
  83. keyMaps[item.replace('Ctrl', 'Cmd')] = val;
  84. }
  85. })
  86. }
  87. function toggleBlod() {
  88. modifiersChange('**',/(\*\*|__)+/g);
  89. }
  90. function toggleItalic() {
  91. modifiersChange('*',/(\*|_)/g);
  92. }
  93. function toggleDelete() {
  94. modifiersChange('~~',/(~~)+/g);
  95. }
  96. function toggleComment() {
  97. modifiersChange('`',/(`)/);
  98. }
  99. /**
  100. * The state of CodeMirror at the given position.
  101. */
  102. function getState (cm, pos) {
  103. pos = pos || cm.getCursor('start');
  104. var stat = cm.getTokenAt(pos);
  105. if (!stat.type)
  106. return {};
  107. var types = stat.type.split(' ');
  108. var ret = {}, data, text;
  109. for (var i = 0; i < types.length; i++) {
  110. data = types[i];
  111. if (data === 'strong') {
  112. ret.bold = true;
  113. } else if (data === 'variable-2') {
  114. text = cm.getLine(pos.line);
  115. if (/^\s*\d+\.\s/.test(text)) {
  116. ret['ordered-list'] = true;
  117. } else {
  118. ret['unordered-list'] = true;
  119. }
  120. } else if (data === 'atom') {
  121. ret.quote = true;
  122. } else if (data === 'em') {
  123. ret.italic = true;
  124. }
  125. }
  126. return ret;
  127. }
  128. function _replaceSelection (cm, active, start, end) {
  129. var text;
  130. var startPoint = cm.getCursor('start');
  131. var endPoint = cm.getCursor('end');
  132. if (active) {
  133. text = cm.getLine(startPoint.line);
  134. start = text.slice(0, startPoint.ch);
  135. end = text.slice(startPoint.ch);
  136. cm.replaceRange(start + end, CodeMirror.Pos(startPoint.line, 0), CodeMirror.Pos(startPoint.line, text.length));
  137. } else {
  138. text = cm.getSelection();
  139. cm.replaceSelection(start + text + end);
  140. startPoint.ch += start.length;
  141. endPoint.ch += start.length;
  142. }
  143. cm.setSelection(startPoint, endPoint);
  144. cm.focus();
  145. }
  146. /**
  147. * toggling line state
  148. * @param cm
  149. * @param name
  150. * @private
  151. */
  152. function _toggleContinueLine (cm, name) {
  153. var startPoint = cm.getCursor('start');
  154. var endPoint = cm.getCursor('end');
  155. var map = {
  156. 'header': '#',
  157. 'unquote': '>',
  158. 'unheader': '#',
  159. 'quote': '>'
  160. };
  161. //判断标识符是否可连续
  162. var isCancleAction = ['unheader', 'unquote'].indexOf(name) > -1 ;
  163. //操作每行的标识符
  164. for (var i = startPoint.line; i <= endPoint.line; i++) {
  165. (function (i) {
  166. var text = cm.getLine(i);
  167. var match = text.match(/^(\s*(?:[*+-] +|\d\. +|>+ +|#+ +)*(?:[*+-] |\d\. |>+ |#+ ))(.*)$/);
  168. var flages = ''; //前置标识 集合如:>>> * 1.
  169. var oldLength = 0,extend = map[name] + ' ';
  170. //将整行分割为 标识符区、文本区
  171. if (match !== null) {
  172. flages = match[1];
  173. text = match[2];
  174. }
  175. if( i === startPoint.line || i===endPoint.line)
  176. oldLength = flages.length;
  177. //存在 标识符区域时,做去重检测(去重只检验末尾标识符(最近的添加的)是否与需要添加的标识符相同)
  178. if (flages) {
  179. if (isCancleAction){
  180. extend = '';
  181. flages = flages.replace(new RegExp(map[name] + '(?=[^'+map[name]+']+$)'),'a').replace(/(^| )(a )/,'$1').replace('a','');
  182. } else {
  183. flages.replace(/^(.*(>|#))\s$/, (all, part1, part2) => {
  184. extend = '';
  185. //末尾标识符与 新添加标识符 相同
  186. if (part2 == map[name] ) {
  187. //相同标识符的状态下
  188. //可连续的标识符:将做连续添加操作
  189. flages = part1 + map[name] + ' ';
  190. } else {
  191. //不相同的标识符状态
  192. flages += map[name] + ' ';
  193. }
  194. });
  195. }
  196. flages += extend;
  197. text = flages + text;
  198. } else {
  199. //行首无标识符且当前操作不为取消操作时,添加新标识符到行首
  200. if (!isCancleAction)
  201. text = text.replace(/^(\s*)(.*)$/,(all,part1,part2)=>{
  202. flages = map[name] + ' ';
  203. return part1 + flages + part2;
  204. })
  205. }
  206. if( i === startPoint.line)
  207. startPoint.ch -= oldLength - flages.length;
  208. if((startPoint.line !== endPoint.line || startPoint.ch !== endPoint.ch)&& i === endPoint.line)
  209. endPoint.ch -= oldLength - flages.length;
  210. cm.replaceRange(text, CodeMirror.Pos(i, 0), CodeMirror.Pos(i, cm.getLine(i).length));
  211. })(i);
  212. }
  213. cm.setSelection(startPoint,endPoint);
  214. cm.focus();
  215. }
  216. function _toggleLine (cm, name) {
  217. var startPoint = cm.getCursor('start');
  218. var endPoint = cm.getCursor('end');
  219. var map = {
  220. 'unordered-list': '*',
  221. 'ordered-list': '1.'
  222. };
  223. //操作每行的标识符
  224. for (var i = startPoint.line; i <= endPoint.line; i++) {
  225. (function (i) {
  226. var text = cm.getLine(i);
  227. var match = text.match(/^(\s*(?:[*+-] +|\d\. +|>+ +|#+ +)*(?:[*+-] |\d\. |>+ |#+ ))(.*)$/);
  228. var modifiers = ''; //前置标识 集合如:>>> * 1.
  229. var oldLength = 0,extend = map[name] + ' ';
  230. //将整行分割为 标识符区、文本区
  231. if (match !== null) {
  232. modifiers = match[1];
  233. text = match[2];
  234. }
  235. if( i === startPoint.line || i===endPoint.line)
  236. oldLength = modifiers.length;
  237. //存在 标识符区域时,做去重检测(去重只检验末尾标识符(最近的添加的)是否与需要添加的标识符相同)
  238. if (modifiers) {
  239. modifiers.replace(/^(.*)([*+-]|\d\.)\s$/, (all, part1, part2) => {
  240. extend = '';
  241. if(part2.length != map[name].length){
  242. modifiers = part1 + map[name] + ' ';
  243. } else {
  244. modifiers = part1;
  245. }
  246. });
  247. modifiers += extend;
  248. text = modifiers + text;
  249. } else {
  250. //行首无标识符且当前操作不为取消操作时,添加新标识符到行首
  251. text = text.replace(/^(\s*)(.*)$/,(all,part1,part2)=>{
  252. modifiers = map[name] + ' ';
  253. return part1 + modifiers + part2;
  254. })
  255. }
  256. if( i === startPoint.line)
  257. startPoint.ch -= oldLength - modifiers.length;
  258. if((startPoint.line !== endPoint.line || startPoint.ch !== endPoint.ch)&& i === endPoint.line)
  259. endPoint.ch -= oldLength - modifiers.length;
  260. cm.replaceRange(text, CodeMirror.Pos(i, 0), CodeMirror.Pos(i, cm.getLine(i).length));
  261. })(i);
  262. }
  263. cm.setSelection(startPoint,endPoint);
  264. cm.focus();
  265. }
  266. /**
  267. * Action for toggling blockquote.
  268. */
  269. function toggleBlockquote (cm) {
  270. _toggleContinueLine(cm, 'quote');
  271. }
  272. /**
  273. * Action for toggling decrease blockquote.
  274. */
  275. function toggleUnBlockquote (cm) {
  276. _toggleContinueLine(cm, 'unquote');
  277. }
  278. /**
  279. * Action for toggling ul.
  280. */
  281. function toggleUnOrderedList (cm) {
  282. _toggleLine(cm, 'unordered-list');
  283. }
  284. /**
  285. * Action for toggling ol.
  286. */
  287. function toggleOrderedList (cm) {
  288. _toggleLine(cm, 'ordered-list');
  289. }
  290. /**
  291. * Action for toggling header.
  292. */
  293. function toggleHeader (cm) {
  294. _toggleContinueLine(cm, 'header');
  295. }
  296. /**
  297. * Action for toggling decrease header.
  298. */
  299. function toggleUnHeader (cm) {
  300. _toggleContinueLine(cm, 'unheader');
  301. }
  302. /**
  303. * Action for drawing a link.
  304. */
  305. function drawLink (cm) {
  306. var stat = getState(cm);
  307. _replaceSelection(cm, stat.link, '[', ']()');
  308. }
  309. /**
  310. * Action for drawing a image link.
  311. */
  312. function drawImageLink (cm) {
  313. var stat = getState(cm);
  314. _replaceSelection(cm, stat.link, '![', ']()');
  315. }
  316. /**
  317. * Action for drawing a table.
  318. */
  319. function drawTable (cm) {
  320. const posStart = cm.getCursor('from');
  321. const posEnd = cm.getCursor('to');
  322. const selectContent = cm.getRange(posStart,posEnd);
  323. var col = 3,row = 3;
  324. var match = selectContent.match(/(\d+)\D+?(\d+)/);
  325. if( match !== null){
  326. row = parseInt(match[1]);
  327. col = parseInt(match[2]);
  328. } else {
  329. match = cm.getLine(posStart.line).slice(0,posStart.ch).match(/(\d+)\D+?(\d+)/);
  330. if( match !== null){
  331. posStart.ch -= match[0].length;
  332. row = parseInt(match[1]);
  333. col = parseInt(match[2]);
  334. }
  335. }
  336. col = (col > 12 ? 12 : col);
  337. var table = '\n\n';
  338. if (/^\s*$/.test( cm.getLine(posStart.line).slice(0,posStart.ch))){
  339. table = '\n';
  340. }
  341. for(var r = 0,rlen = row + 2; r < rlen; r++){
  342. for (var c = 0; c < col; c++){
  343. if ( 1 === r){
  344. table += '| :--: '
  345. } else {
  346. table += '| ';
  347. }
  348. if ( c === col-1){
  349. table += '|\n';
  350. }
  351. }
  352. }
  353. cm.replaceRange(table,posStart,posEnd);
  354. cm.focus();
  355. }
  356. let praseHtml;
  357. /**
  358. * Action for paste date what is HTML or Image.
  359. */
  360. function pasteContent(cm) {
  361. let image = clipboard.readImage();
  362. if (!image.isEmpty()) {
  363. let imageTitle = cm.getSelection();
  364. cm.replaceSelection(`![${imageTitle}](${imgManager.getImageOfObj(image,imageTitle)})`);
  365. } else {
  366. if (!praseHtml)
  367. praseHtml = require('./tomarkdown');
  368. let content = praseHtml(clipboard.readHTML(),{
  369. converters: [
  370. {
  371. filter: 'img',
  372. replacement: function (innerHTML, node) {
  373. if (1 === node.attributes.length) {
  374. return "";
  375. }
  376. return "![](" + node.src + ")";
  377. }
  378. },{
  379. filter: 'a',
  380. replacement: function (innerHTML, node) {
  381. if (innerHTML.length > 0) {
  382. return "["+innerHTML+"](" + node.href + ")";
  383. }
  384. return "";
  385. }
  386. }
  387. ], gfm: true
  388. });
  389. content = content.trim();
  390. if (content === ''){
  391. content = clipboard.readText()
  392. } else {
  393. // code 中 <, > 进行转义
  394. var codes = content.split('```');
  395. if (codes.length > 1) {
  396. for (var i = 0, iMax = codes.length; i < iMax; i++) {
  397. if (i % 2 === 1) {
  398. codes[i] = codes[i].replace(/<\/span><span style="color:#\w{6};">/g, '').replace(/</g, '<').replace(/>/g, '>');
  399. }
  400. }
  401. }
  402. content = codes.join('```');
  403. // ascii 160 替换为 30
  404. content = $('<div>' + content + '</div>').text().replace(/\n{2,}/g, '\n\n').replace(/ /g, ' ');
  405. content = content.trim();
  406. }
  407. cm.replaceSelection(content)
  408. }
  409. }
  410. /**
  411. * Action for paste date what is HTML or Image.
  412. */
  413. function pasteOriginContent(cm) {
  414. let image = clipboard.readImage();
  415. if (!image.isEmpty()) {
  416. let imageTitle = cm.getSelection();
  417. cm.replaceSelection(`![${imageTitle}](${imgManager.getImageOfObj(image,imageTitle)})`);
  418. } else {
  419. cm.replaceSelection(clipboard.readText())
  420. }
  421. }
  422. /**
  423. * Action for format document tables.
  424. */
  425. function formatTables(cm) {
  426. FormatTables.formatTables(cm);
  427. }
  428. fixShortcut();
  429. CodeMirror.commands.pasteContent = pasteContent;
  430. CodeMirror.commands.pasteOriginContent = pasteOriginContent;
  431. var editor = CodeMirror.fromTextArea(document.querySelector('#editor textarea'), {
  432. lineNumbers: false,
  433. mode: 'yaml-frontmatter',
  434. matchBrackets: true,
  435. theme: moeApp.config.get('editor-theme'),
  436. lineWrapping: true,
  437. extraKeys: keyMaps,
  438. fixedGutter: false,
  439. // foldGutter: true,
  440. // gutters:["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
  441. // auto:"auto",
  442. // autoCloseBrackets: true,
  443. tabSize: moeApp.config.get('tab-size'),
  444. indentUnit: moeApp.config.get('tab-size'),
  445. viewportMargin: Infinity,
  446. styleActiveLine: true,
  447. showCursorWhenSelecting: true
  448. });
  449. editor.focus();
  450. const regStandardCharacterB = /^((-?\d+(,\d{3})*(\.\d+)?(x?\*?\d*\^\d+)?)|((\d+-)+\d+)|(_?[-\da-zA-Z\u4E00-\u9FFF\-]_?))+/;
  451. const regStandardCharacterE = /((-?\d+(,\d{3})*(\.\d+)?(x?\*?\d*\^\d+)?)|((\d+-)+\d+)|(_?[-\da-zA-Z\u4E00-\u9FFF\-]_?))+$/;
  452. /**
  453. * 字符置反
  454. * @param str
  455. */
  456. function reverseStr(str) {
  457. return str.split('').reverse().join('');
  458. }
  459. function getLine(line) {
  460. return editor.getLine(line);
  461. }
  462. function getSelectWords(){
  463. const posStart = editor.getCursor('from');
  464. const posEnd = editor.getCursor('to');
  465. const selectContent = editor.getRange(posStart,posEnd);
  466. const leftContent = getLine(posStart.line).slice(0,posStart.ch).replace(/^\s*([\*\-\+\>] |\d\. )+/,(item)=>{
  467. startPos = item.length;
  468. return item.replace(/./g,' ')
  469. });
  470. const rightContent = getLine(posEnd.line).slice(posEnd.ch);
  471. var match;
  472. match = leftContent.match(/(`|~~|\*\*?|__)+$/);
  473. var leftModifiers = (match === null) ? '': match[0];
  474. match = rightContent.match(/^(`|~~|\*\*?|__)+/);
  475. var rightModifiers = (match === null) ? '': match[0];
  476. match = (reverseStr(leftModifiers)+ 'a' + rightModifiers).match(/^((`|~~|\*\*?|__?)+).*?a.*?\1/);
  477. var modifiers = (match === null) ? '': reverseStr(match[1]);
  478. return {
  479. words: selectContent,
  480. range: {
  481. startWord: posStart,
  482. endWord: posEnd,
  483. start: CodeMirror.Pos(posStart.line,posStart.ch - modifiers.length,'after'),
  484. end: CodeMirror.Pos(posEnd.line,posEnd.ch + modifiers.length,'before')
  485. },
  486. modifiers: modifiers
  487. }
  488. }
  489. function getAutoWords(pos){
  490. const curPos = pos.ch;
  491. const line = getLine(pos.line);
  492. var lineLeft,lineRight;
  493. var match=null,curWord = '', lineNew='',modifiers = '';
  494. var startPos,start = curPos,end = curPos;
  495. //以光标位分割点,截取前后两部分文本
  496. //并左侧字符串删除 特殊字符 结尾部分
  497. //并右侧字符串删除 特殊字符 结尾部分
  498. lineLeft = line.slice(0,curPos);
  499. lineLeft = lineLeft.replace(/(`|~~|\*\*?|__)+$/,'');
  500. lineRight= line.slice(curPos,line.length);
  501. lineRight = lineRight.replace(/^(`|~~|\*\*?|__)+/,'');
  502. //当前光标前后是否有 特殊字符 (`、~~、**、__、*、_)
  503. start = lineLeft.length;
  504. end = lineLeft.length + line.length - lineLeft.length - lineRight.length;
  505. curWord = line.slice(start, end );
  506. //消除已有匹配
  507. lineLeft = lineLeft.replace(/^\s*([\*\-\+\>] |\d\. )+/,(item)=>{
  508. startPos = item.length;
  509. return item.replace(/./g,' ')
  510. });
  511. lineLeft = lineLeft.replace(/\\[_~`\*]/g,(item)=>{return item.replace(/./g,'a')});
  512. lineLeft = lineLeft.replace(/`.+?`/g,(item)=>{return item.replace(/./g,' ')});
  513. lineLeft = lineLeft.replace(/([~_\*]{2}|[_\*](?!\*|_|~|`)).+?\1/g,(item)=>{return item.replace(/./g,' ')});
  514. lineRight = reverseStr(lineRight);
  515. lineRight = lineRight.replace(/[_~`\*]\\/g,(item)=>{return item.replace(/./g,'a')});
  516. lineRight = lineRight.replace(/`.+?`/g,(item)=>{return item.replace(/./g,' ')});
  517. lineRight = lineRight.replace(/([~_\*]{2}|[_\*](?!\*|_|~|`)).+?\1/g,(item)=>{return item.replace(/./g,' ')});
  518. lineRight = reverseStr(lineRight);
  519. lineNew = lineLeft+curWord+lineRight;
  520. match = lineNew.match(/((?=(~|\*|_|`))\2*)((?=(~|\*|_|`))\4*)((?=(~|\*|_|`))\6*)((?=(~|\*|_|`))\8*)(.*?)(\7\5\3\1)/);
  521. if(match !== null) {
  522. modifiers = reverseStr(match[10]);
  523. start = match['index'] + modifiers.length;
  524. curWord = match[9]
  525. end = start + match[9].length;
  526. }else{
  527. lineLeft = line.slice(0,curPos);
  528. lineLeft = lineLeft.replace(regStandardCharacterE,'');
  529. lineRight= line.slice(curPos,line.length);
  530. lineRight = lineRight.replace(regStandardCharacterB,'');
  531. //其他操作
  532. start = lineLeft.length;
  533. end = lineLeft.length + line.length - lineLeft.length - lineRight.length;
  534. curWord = line.slice(start, end );
  535. }
  536. return {
  537. cursorEnd: curPos === end + modifiers.length,
  538. words: curWord,
  539. range: {
  540. startWord: CodeMirror.Pos(pos.line,start,'after'),
  541. endWord: CodeMirror.Pos(pos.line,end,'before'),
  542. start: CodeMirror.Pos(pos.line,start - modifiers.length,'after'),
  543. end: CodeMirror.Pos(pos.line,end + modifiers.length,'before')
  544. },
  545. modifiers: modifiers
  546. }
  547. }
  548. function changeModifiers(modifiers,item,reg){
  549. var newModifiers;
  550. if( item.length == 1){
  551. modifiers = modifiers.replace(/([~_\*])\1/g, (item)=>{
  552. switch (item){
  553. case '~~': return '11';
  554. case '__': return '22';
  555. case '**': return '33';
  556. default: return item;
  557. };
  558. });
  559. }
  560. if (modifiers.length > 0 && reg.test(modifiers)) {
  561. newModifiers = modifiers.replace(reg,'')
  562. } else {
  563. newModifiers = modifiers.replace(/^(`*)(.*)/,'$1'+item+'$2');
  564. }
  565. if( item.length == 1) {
  566. newModifiers = newModifiers.replace(/11|22|33/g, (item) => {
  567. switch (item) {
  568. case '11': return '~~';
  569. case '22': return '__';
  570. case '33': return '**';
  571. }
  572. ;
  573. });
  574. }
  575. return newModifiers;
  576. }
  577. function modifiersChangeSelect(item,reg) {
  578. const wordsInfo = getSelectWords();
  579. //生成新的 修饰符
  580. var newModifiers = changeModifiers(wordsInfo.modifiers, item, reg);
  581. //计算 修饰符 变化长度
  582. var changeLength = newModifiers.length - wordsInfo.modifiers.length;
  583. //修改选词位置信息 及 包含修饰符后的 位置信息
  584. wordsInfo.range.startWord.ch += changeLength;
  585. if( wordsInfo.range.endWord.line === wordsInfo.range.startWord.line){
  586. //同行情况下,末尾选中位置也 增加或者减少 变化的长度
  587. wordsInfo.range.endWord.ch += changeLength;
  588. // wordsInfo.range.end.ch += changeLength*2; //修饰符变化,尾部位置双倍长度变化
  589. }
  590. editor.replaceRange(newModifiers + wordsInfo.words + reverseStr(newModifiers), wordsInfo.range.start, wordsInfo.range.end);
  591. editor.setSelection(wordsInfo.range.startWord, wordsInfo.range.endWord);
  592. }
  593. function modifiersChangeAuto(item,reg) {
  594. const pos = editor.getCursor();
  595. const wordsInfo = getAutoWords(pos);
  596. //判断光标位置是否在选词 非末尾 并且 修饰符已存在,则移动光标到末尾
  597. if ( !wordsInfo.cursorEnd && (wordsInfo.modifiers.indexOf(item)>-1) ){
  598. editor.setCursor(wordsInfo.range.end);
  599. editor.focus();
  600. return ;
  601. }
  602. //生成新的 修饰符
  603. var newModifiers = changeModifiers(wordsInfo.modifiers, item, reg);
  604. //计算 修饰符 变化长度
  605. var changeLength = newModifiers.length - wordsInfo.modifiers.length;
  606. //修改选词位置信息 及 包含修饰符后的 位置信息
  607. wordsInfo.range.startWord.ch += changeLength;
  608. wordsInfo.range.endWord.ch += changeLength;
  609. // wordsInfo.range.end.ch += changeLength*2; //修饰符变化,尾部位置双倍长度变化
  610. editor.replaceRange(newModifiers + wordsInfo.words + reverseStr(newModifiers), wordsInfo.range.start, wordsInfo.range.end);
  611. if(wordsInfo.words == '')
  612. editor.setCursor(wordsInfo.range.endWord);
  613. else {
  614. if (wordsInfo.cursorEnd)
  615. pos.ch += changeLength * 2;
  616. else
  617. pos.ch += changeLength;
  618. editor.setCursor(pos);
  619. }
  620. }
  621. function modifiersChange(item,reg) {
  622. if (editor.somethingSelected()) {
  623. modifiersChangeSelect(item,reg)
  624. } else {
  625. modifiersChangeAuto(item,reg)
  626. }
  627. editor.focus();
  628. }
  629. return editor;
  630. })();
  631. /*以下是一些比较傻的方案*/
  632. /*
  633. /!**
  634. * 返回选中数据区以及前后修饰符
  635. * @param range
  636. * @returns {{offset: number, content: *[], before: *[], after: *[]}}
  637. *!/
  638. function checkSelection(range) {
  639. //获取选中 对象
  640. let textContent = getText(range);
  641. let textBefore = "", textAfter = "";
  642. //获取前置 对象
  643. range.anchor.sticky = 'before';
  644. let rangeBefore = getPosRange(range.anchor);
  645. if (range.head == rangeBefore.head) {
  646. _.extend(rangeBefore.head, rangeBefore.anchor);
  647. } else {
  648. _.extend(rangeBefore.head, range.anchor);
  649. textBefore = getText(rangeBefore);
  650. }
  651. //获取后置 对象
  652. range.head.sticky = 'after';
  653. let rangeAfter = getPosRange(range.head);
  654. if (range.head == rangeAfter.head) {
  655. _.extend(rangeAfter.anchor, rangeAfter.head);
  656. } else {
  657. _.extend(rangeAfter.anchor, range.head);
  658. textAfter = getText(rangeAfter);
  659. }
  660. let strShort, strLong;
  661. if (textBefore.length > textAfter.length)
  662. strLong = textBefore, strShort = textAfter;
  663. else
  664. strShort = textBefore, strLong = textAfter;
  665. if (/^[\*~_`]+$/.test(strShort) && strShort.replace(/^ *!/).startsWith(reverseStr(strLong).replace(/^ *!/))) {
  666. if (textBefore.length > textAfter.length) {
  667. rangeBefore.anchor.ch = rangeBefore.head.ch - strShort.length;
  668. textBefore = textBefore.slice(textBefore.length - strShort.length, textBefore.length);
  669. }
  670. else if (textBefore.length < textAfter.length) {
  671. rangeAfter.head.ch = rangeAfter.anchor.ch + strShort.length;
  672. textAfter = textAfter.slice(0, strShort.length);
  673. }
  674. } else {
  675. _.extend(rangeBefore.anchor, rangeBefore.head);
  676. textBefore = '';
  677. _.extend(rangeAfter.head, rangeAfter.anchor);
  678. textAfter = '';
  679. }
  680. range.anchor.sticky = null;
  681. range.head.sticky = null;
  682. return {
  683. offset: 0,
  684. content: ['' == textContent, textContent, range],
  685. before: ['' == textBefore, textBefore, rangeBefore],
  686. after: ['' == textAfter, textAfter, rangeAfter]
  687. }
  688. }
  689. /!**
  690. * 获取指定范围的前一个或者后一个范围
  691. * @param range
  692. * @param sticky
  693. * @returns {*}
  694. *!/
  695. function getRange(range, sticky) {
  696. var pos = range.anchor;
  697. sticky = sticky || 'before';
  698. if ('after' == sticky)
  699. pos = range.head;
  700. pos.sticky = sticky;
  701. return getPosRange(pos)
  702. }
  703. /!**
  704. * 获取文本内容
  705. * @param range
  706. * @returns {*}
  707. *!/
  708. function getText(range) {
  709. return editor.getRange(range.anchor, range.head);
  710. }
  711. function getCheckStart(param) {
  712. let lineStart = null;
  713. if (param.anchor && param.anchor.line) {
  714. lineStart = editor.getLine(param.anchor.line).match(/^( |> )*\* /);
  715. } else if (typeof lineStart == 'string') {
  716. lineStart = param.match(/^( |> )*\* /);
  717. }
  718. return (null == lineStart) ? 0 : lineStart[0].length;
  719. ;
  720. }
  721. function checkRange(range, content) {
  722. var checkContent, findWord = content.slice(0, range.head.ch).match(regStandardCharacterE);
  723. if (findWord != null) {
  724. checkContent = findWord[0];
  725. range.anchor.ch = range.head.ch - checkContent.length;
  726. }
  727. findWord = content.slice(range.anchor.ch, content.length).match(regStandardCharacterB);
  728. if (findWord != null) {
  729. checkContent = findWord[0];
  730. range.head.ch = range.anchor.ch + checkContent.length;
  731. }
  732. return checkContent
  733. }
  734. /!**
  735. * 获取强调内容真实范围 前(before)/后(after) 范围集合
  736. * @param range 指定范围
  737. * @param sticky 前/后查询
  738. * @returns {*} 内容真实范围(包含@range范围)
  739. *!/
  740. function serachContentRange(range, sticky) {
  741. sticky = sticky || 'before';
  742. let matchText = getText(range);
  743. let reg = new RegExp(reverseStr(matchText).replace(/\*!/g, '\\*') + '$');
  744. let rangeHelper, rangeNext = range;
  745. let strNext;
  746. let success = true;
  747. let safeCount = -1;
  748. //查找开头
  749. let checkStart = getCheckStart(range);
  750. //循环找出 与 matchText 对称 textHelper 如:**abcd** ~abc~
  751. do {
  752. rangeHelper = rangeNext;
  753. rangeNext = getRange(rangeHelper, sticky);
  754. strNext = getText(rangeNext);
  755. if (checkStart > rangeHelper.anchor.ch || rangeHelper.anchor.ch == rangeNext.anchor.ch || safeCount > 1000) {
  756. success = false;
  757. break;
  758. }
  759. safeCount++;
  760. } while (!reg.test(strNext))
  761. if (success) {
  762. if (sticky == 'before') {
  763. _.extend(rangeHelper.head, range.anchor);
  764. } else {
  765. _.extend(rangeHelper.anchor, range.head);
  766. }
  767. } else {
  768. _.extend(rangeHelper.head, rangeHelper.anchor);
  769. }
  770. return rangeHelper;
  771. }
  772. function getSelection() {
  773. if (editor.state.completionActive) {
  774. return;
  775. }
  776. // 自动获取有效文本 结构如下:
  777. // 标识符+内容+标识符 (前 中 后 结构)
  778. let rangeContent;
  779. let textContent = '';
  780. //选中状态下
  781. if (editor.somethingSelected()) {
  782. var start = editor.getCursor('from');
  783. var end = editor.getCursor('to');
  784. //获取选中 对象
  785. rangeContent = getPosRange(end);
  786. rangeContent.anchor.ch = start.ch;
  787. rangeContent.anchor.line = start.line;
  788. rangeContent.head.ch = end.ch;
  789. rangeContent.head.line = end.line;
  790. return checkSelection(rangeContent);
  791. } else {
  792. /!**
  793. * 未选中状态:
  794. * 1.光标在空格之后
  795. * 2.光标在普通字符中
  796. * 3.光标在语法标志中
  797. * 4.光标在标记之中
  798. *!/
  799. var curPos = editor.getCursor();
  800. curPos.sticky = 'before';
  801. var rangeHelper = rangeContent = getPosRange(curPos);
  802. let lineContent = getLine(rangeContent);
  803. let checkStart = getCheckStart(lineContent);
  804. if (checkStart > curPos.ch) {
  805. _.extend(rangeContent.anchor, rangeContent.head);
  806. return checkSelection(rangeContent);
  807. }
  808. textContent = getText(rangeContent);
  809. //光标前面是空格
  810. if (/^ +$/.test(textContent)) {
  811. //空格+光标
  812. curPos.sticky = 'after';
  813. rangeContent = getPosRange(curPos);
  814. textContent = getText(rangeContent);
  815. if (/^ +$/.test(textContent)) {
  816. //空格+光标+空格或结尾 (前后都是空格,直接插入内容) |
  817. _.extend(rangeContent.anchor, rangeContent.head);
  818. return checkSelection(rangeContent);
  819. } else if (/^[\*~_`]+$/.test(textContent)) {
  820. //空格+光标+标记 |**
  821. //需要找到更后一位单词检查
  822. rangeContent.head.sticky = 'after';
  823. rangeContent = getPosRange(rangeContent.head);
  824. checkRange(rangeContent, lineContent);
  825. return checkSelection(rangeContent);
  826. } else {
  827. //空格+光标+内容 |abc
  828. checkRange(rangeContent, lineContent);
  829. return checkSelection(rangeContent);
  830. }
  831. } else if (/^[\*~`]+/.test(textContent)) {
  832. //标记+光标
  833. //1.标记+光标+内容
  834. //2.内容+标记+光标
  835. //需要向前查找单词
  836. if (0 != rangeHelper.anchor.ch) {
  837. //1.优先开始前找 标记+光标
  838. rangeContent = serachContentRange(rangeHelper, 'before');
  839. if (rangeContent.empty()) {
  840. rangeContent = serachContentRange(rangeHelper, 'after');
  841. }
  842. } else {
  843. //2.向后查找
  844. rangeContent = serachContentRange(rangeHelper, 'after');
  845. if (rangeContent.empty()) {
  846. rangeContent = serachContentRange(rangeHelper, 'before');
  847. }
  848. }
  849. return checkSelection(rangeContent);
  850. } else {
  851. //空格+光标+标记内容
  852. checkRange(rangeContent, lineContent);
  853. return checkSelection(rangeContent);
  854. }
  855. }
  856. }*/