1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/lunarsf-Lunar-Markdown-Editor

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Это зеркальный репозиторий, синхронизируется ежедневно с исходного репозитория.
Клонировать/Скачать
MarkDownEditorBase.cs 190 КБ
Копировать Редактировать Исходные данные Просмотреть построчно История
LunarSF Отправлено 8 лет назад 26b6083
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555
using System;
using System.Collections.Generic;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Highlighting;
using System.IO;
using System.Xml;
using ICSharpCode.AvalonEdit.CodeCompletion;
using ICSharpCode.AvalonEdit.Folding;
using System.Windows.Input;
using System.Windows.Threading;
using ICSharpCode.AvalonEdit.Search;
using System.Windows;
using System.Text;
using System.Windows.Data;
using System.Windows.Media;
using ICSharpCode.AvalonEdit.Document;
using System.Text.RegularExpressions;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
namespace LunarSF.SHomeWorkshop.LunarMarkdownEditor
{
/// <summary>
/// 创建者:杨震宇
///
/// 说明:Markdown编辑器类。
/// </summary>
public class MarkDownEditorBase : TextEditor
{
/// <summary>
/// 自定义高亮定义,完整定义。
/// 但一千多行的帮助文档就明显延迟。
/// </summary>
private static IHighlightingDefinition advanceMarkdownHighlighting;
/// <summary>
/// 仅仅只提供基本的六级标题的高亮显示。
/// </summary>
private static IHighlightingDefinition onlyHeadersMarkdownHightlighting;
/// <summary>
/// [静态构造方法]载入高亮定义文件。
/// </summary>
static MarkDownEditorBase()
{
// 载入代码高亮的定义文件
//using (Stream s = typeof(MainWindow).Assembly.GetManifestResourceStream("LunarSF.SHomeWorkshop.LunarMarkdownEditor.CustomHighlighting.xshd"))
Stream s = typeof(MainWindow).Assembly.GetManifestResourceStream("LunarSF.SHomeWorkshop.LunarMarkdownEditor.Markdown.SyntaxHightlightRulesets.xshd");
if (s != null)
{
using (XmlReader reader = new XmlTextReader(s))
{
advanceMarkdownHighlighting = ICSharpCode.AvalonEdit.Highlighting.Xshd.
HighlightingLoader.Load(reader, HighlightingManager.Instance);
}
}
Stream s2 = typeof(MainWindow).Assembly.GetManifestResourceStream("LunarSF.SHomeWorkshop.LunarMarkdownEditor.MarkdownHeader.SyntaxHightlightRulesets.xshd");
if (s2 != null)
{
using (XmlReader reader = new XmlTextReader(s2))
{
onlyHeadersMarkdownHightlighting = ICSharpCode.AvalonEdit.Highlighting.Xshd.
HighlightingLoader.Load(reader, HighlightingManager.Instance);
}
}
}
/// <summary>
/// 高亮显示方案。
/// </summary>
public enum HighlightingType
{
None, //禁用高亮
OnlyHeaders, //只对六级标题高亮显示
Advance, //全部显示
}
private MarkdownEditor masterEditor;
/// <summary>
/// [只读]父封装。
/// </summary>
public MarkdownEditor MasterEditor { get { return masterEditor; } }
/// <summary>
/// [构造方法]编辑器内核。
/// </summary>
public MarkDownEditorBase(MarkdownEditor masterEditor)
{
this.masterEditor = masterEditor;
//这个不能写死,因为会导致“字体选择”功能无效化。
//this.FontFamily = new FontFamily("SimSun");
this.SnapsToDevicePixels = true;//避免折叠线模糊
this.Background = Brushes.WhiteSmoke;
this.WordWrap = true;
this.ShowLineNumbers = true;
this.Options.ShowColumnRuler = true;
this.Options.ShowSpaces = false;
//AvalonEdit本来使用间隔号代替空格,但间隔号是全角字符,要占据两个半角的宽度;
//我把它改为使用加重号,这样就只需要占据一个半角宽度了。
this.Options.ShowTabs = false;
this.Options.AutoAlignTabs = false;//将Tab显示为“--+”,并禁止Tab自动伸缩尺寸。
//this.Options.WordWrapIndentation = Globals.MainWindow.mainTabControl.FontSize * 2;
//直接演示Markdown文档本身时,这个效果在同段换行时非常糟糕,
//容易出现对不齐的现象——尤其使用的字体不是宋体时更易出问题。
//TODO: 暂时去除它。
this.Options.ShowBoxForControlCharacters = true;
this.Options.ShowEndOfLine = false;
this.Options.AllowScrollBelowDocument = true;
this.Options.HighlightCurrentLine = true;
this.PreviewTextInput += MarkdownEditorBase_PreviewTextInput;
this.TextChanged += MarkDownEditorBase_TextChanged;
this.TextArea.SelectionChanged += TextArea_SelectionChanged;
#region 代码高亮处理
RefreshHighlightingSetting();
#endregion
this.TextArea.IndentationStrategy = new ICSharpCode.AvalonEdit.Indentation.DefaultIndentationStrategy();
#region 代码折叠处理Folding
foldingStrategy = new CustomFoldingStrategy(this);
foldingManager = CustomFoldingManager.Install(this.TextArea);
foldingStrategy.UpdateFoldings(FoldingManager, this.Document);
foldingUpdateTimer.Interval = TimeSpan.FromSeconds(0.05);//50毫秒。
foldingUpdateTimer.Tick += foldingUpdateTimer_Tick;
foldingUpdateTimer.Start();
#endregion
#region 自动完成
this.TextArea.TextEntered += TextArea_TextEntered;
this.TextArea.TextEntering += TextArea_TextEntering;
#endregion
#region 搜索框
if (searchPanel == null)
{
//searchPanel = new SearchPanel() { FontSize = Globals.MainWindow.FontSize, };
//searchPanel.Attach(this.TextArea);
searchPanel = SearchPanel.Install(this.TextArea);
}
#endregion
this.TextChanged += MarkdownEditorBase_TextChanged;
//this.Document.UndoStack.MarkAsOriginalFile();
//加上这句后,无法撤销已保存的文件。
this.GotFocus += MarkDownEditorBase_GotFocus;
this.LostFocus += MarkDownEditorBase_LostFocus;
this.PreviewKeyDown += MarkDownEditorBase_PreviewKeyDown;
//处理拖动
this.AllowDrop = true;
//处理字号缩放
this.PreviewMouseWheel += MarkDownEditorBase_PreviewMouseWheel;
this.PreviewMouseLeftButtonUp += MarkDownEditorBase_PreviewMouseLeftButtonUp;
this.PreviewMouseDown += MarkDownEditorBase_PreviewMouseDown;
//添加快捷工具条
popupToolBar = new LunarMarkdownEditor.EditorPopupToolBar(this) { IsOpen = false, };
this.MouseMove += MarkDownEditorBase_MouseMove;
}
private void MarkDownEditorBase_MouseMove(object sender, MouseEventArgs e)
{
if (this.popupToolBar.IsOpen == false || this.popupToolBar.Entered == false) return;
var topLeft = this.popupToolBar.VPanel.TranslatePoint(new Point(0, 0), this);
var bottomRight = new Point(topLeft.X + this.popupToolBar.MainBorder.ActualWidth, topLeft.Y + this.popupToolBar.MainBorder.ActualHeight);
//var topRight = new Point(bottomRight.X, topLeft.Y);
//var bottomLeft = new Point(topLeft.X, bottomRight.Y);
var rect1 = new Rect(topLeft, bottomRight);
var rect2 = new Rect(new Point(topLeft.X - 25, topLeft.Y - 25), new Point(bottomRight.X + 25, bottomRight.Y + 25));
var rect3 = new Rect(new Point(topLeft.X - 50, topLeft.Y - 50), new Point(bottomRight.X + 50, bottomRight.Y + 50));
var rect4 = new Rect(new Point(topLeft.X - 75, topLeft.Y - 75), new Point(bottomRight.X + 75, bottomRight.Y + 75));
var mpt = e.GetPosition(this);
if (rect1.Contains(mpt))
{
this.popupToolBar.MainBorder.Opacity = 1;
}
else if (rect2.Contains(mpt))
{
this.popupToolBar.MainBorder.Opacity = 0.75;
}
else if (rect3.Contains(mpt))
{
this.popupToolBar.MainBorder.Opacity = 0.50;
}
else if (rect4.Contains(mpt))
{
this.popupToolBar.MainBorder.Opacity = 0.25;
}
else
{
this.popupToolBar.IsOpen = false;
this.popupToolBar.MainBorder.Opacity = 1;
this.popupToolBar.Entered = false;
}
//this.popupToolBar.TbInfo.Text = topLeft.ToString() + "|" + bottomRight.ToString() + "|" + mpt.ToString();
}
private void MarkDownEditorBase_LostFocus(object sender, RoutedEventArgs e)
{
this.PopupToolBar.IsOpen = false;
}
private void MarkDownEditorBase_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
this.PopupToolBar.IsOpen = false;
}
private void MarkDownEditorBase_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (masterEditor == null || masterEditor.IsCompareAreaEditor) return;
if (PopupToolBar.IsOpen == true)
{
PopupToolBar.IsOpen = false;
}
if (this.SelectionLength <= 0 || Globals.MainWindow.IsPopupContextToolbarEnabled == false) return;
PopupToolBar.IsOpen = true;
}
private EditorPopupToolBar popupToolBar;
/// <summary>
/// 上下文快捷工具栏。
/// </summary>
internal EditorPopupToolBar PopupToolBar
{
get
{
return popupToolBar;
}
}
private void TextArea_SelectionChanged(object sender, EventArgs e)
{
if (Globals.MainWindow != null)
{
Globals.MainWindow.RefreshTextInfos();
}
}
private void MarkDownEditorBase_TextChanged(object sender, EventArgs e)
{
if (Globals.MainWindow != null)
{
Globals.MainWindow.RefreshTextInfos();
}
}
private void RefreshHighlightingSetting()
{
//this.SyntaxHighlighting.MainRuleSet.Rules.Clear();
//上面这行会导致错误
switch (highlightingSetting)
{
case HighlightingType.None:
{
this.SyntaxHighlighting = null;
break;
}
case HighlightingType.OnlyHeaders:
{
HighlightingManager.Instance.RegisterHighlighting("Markdown Header Highlighting", new string[] { ".md" }, onlyHeadersMarkdownHightlighting);
this.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension(".md");
break;
}
default:
{
HighlightingManager.Instance.RegisterHighlighting("Markdown Highlighting", new string[] { ".md" }, advanceMarkdownHighlighting);
this.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension(".md");
break;
}
}
//TODO: 载入用户自定义高亮方案。
//this.SyntaxHighlighting.MainRuleSet.Rules.Add(new HighlightingRule() { })
}
/// <summary>
/// 按住 Ctrl 键 + 鼠标滚轮 缩放字号。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MarkDownEditorBase_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if ((Keyboard.GetKeyStates(Key.LeftCtrl) & KeyStates.Down) <= 0 && (Keyboard.GetKeyStates(Key.RightCtrl) & KeyStates.Down) <= 0) return;
if (e.Delta > 0)
{
Globals.MainWindow.FontSizeUp();
}
else if (e.Delta < 0)
{
Globals.MainWindow.FontSizeDown();
}
e.Handled = true;
}
/// <summary>
/// 是否支持试题编辑。需要开启“IsAutoCompletionEnabled”。
/// </summary>
public bool IsExamEnabled { get; set; }
private bool isAutoCompletionEnabled = false;
/// <summary>
/// 是否支持自动完成。
/// </summary>
public bool IsAutoCompletionEnabled
{
get
{
return isAutoCompletionEnabled;
}
set
{
isAutoCompletionEnabled = value;
if (value == false)
{
if (this.completionWindow != null &&
this.completionWindow.Visibility == Visibility.Visible)
{
this.completionWindow.Close();
this.completionWindow = null;
}
}
}
}
/// <summary>
/// 是否显示空格。
/// </summary>
public bool IsShowSpaces
{
get { return this.Options.ShowSpaces; }
set { this.Options.ShowSpaces = value; }
}
/// <summary>
/// 是否显示段落标记。
/// </summary>
public bool IsShowEndOfLine
{
get { return this.Options.ShowEndOfLine; }
set { this.Options.ShowEndOfLine = value; }
}
/// <summary>
/// 是否显示Tab符。
/// </summary>
public bool IsShowTabs
{
get { return this.Options.ShowTabs; }
set { this.Options.ShowTabs = value; }
}
/// <summary>
/// 是否支持英译中词典的自动提示功能。需要开启“IsAutoCompletionEnabled”。
/// </summary>
public bool IsEnToChineseDictEnabled { get; set; } = false;
private MarkDownEditorBase.HighlightingType highlightingSetting = MarkDownEditorBase.HighlightingType.Advance;
/// <summary>
/// 采用何种高亮显示方案。
/// </summary>
public MarkDownEditorBase.HighlightingType HighlightingSetting
{
get { return highlightingSetting; }
set
{
highlightingSetting = value;
RefreshHighlightingSetting();
}
}
/// <summary>
/// [覆盖方法]只有用这个办法才能使Alt+Up/Down生效。
/// </summary>
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyboardDevice.Modifiers == ModifierKeys.Alt
&& (e.KeyboardDevice.IsKeyDown(Key.RightAlt) || e.KeyboardDevice.IsKeyDown(Key.LeftAlt)))
{
if ((e.Key == Key.Up || e.SystemKey == Key.Up))
{
SwapWithPreviewLine();
e.Handled = true;
}
else if ((e.Key == Key.Down || e.SystemKey == Key.Down))
{
SwapWithNextLine();
e.Handled = true;
}
}
base.OnKeyDown(e);
}
/// <summary>
/// 处理常用快捷键。
/// </summary>
void MarkDownEditorBase_PreviewKeyDown(object sender, KeyEventArgs e)
{
this.PopupToolBar.IsOpen = false;
bool isCtrl = false ||
(Keyboard.GetKeyStates(Key.RightCtrl) & KeyStates.Down) > 0 ||
(Keyboard.GetKeyStates(Key.LeftCtrl) & KeyStates.Down) > 0;
bool isShift = false ||
(Keyboard.GetKeyStates(Key.RightShift) & KeyStates.Down) > 0 ||
(Keyboard.GetKeyStates(Key.LeftShift) & KeyStates.Down) > 0;
bool isAlt = false ||
(Keyboard.GetKeyStates(Key.RightAlt) & KeyStates.Down) > 0 ||
(Keyboard.GetKeyStates(Key.LeftAlt) & KeyStates.Down) > 0;
try
{
switch (e.Key)
{
case Key.OemSemicolon://分号
{
int startOffset = this.SelectionStart;
int endOffset = this.SelectionStart + this.SelectionLength;
var startLine = this.Document.GetLineByOffset(startOffset);
var endLine = this.Document.GetLineByOffset(endOffset);
if (isCtrl)
{
if (isShift)
{
//取消这些行的备注(如果行首存在分号,取消之)
for (int i = endLine.LineNumber; i >= startLine.LineNumber; i--)
{
var line = this.Document.GetLineByNumber(i);
var text = this.Document.GetText(line.Offset, 1);
if (text == ";" || text == ";")
{
this.Document.Remove(line.Offset, 1);
}
}
}
else
{
//将这些行变成备注(在头部添加分号)
for (int i = endLine.LineNumber; i >= startLine.LineNumber; i--)
{
var line = this.Document.GetLineByNumber(i);
this.Document.Insert(line.Offset, ";");
}
}
e.Handled = true;
}
break;
}
case Key.OemOpenBrackets://左花括号
{
if (isCtrl && !isAlt)
{
e.Handled = true;
SwitchTaskListItemOrDateTimeLineState(false, isShift);
}
break;
}
case Key.OemCloseBrackets://右花括号
{
if (isCtrl && !isAlt)
{
e.Handled = true;
SwitchTaskListItemOrDateTimeLineState(true, isShift);
}
break;
}
//另有用处:case Key.OemMinus: //Markdown语法:前后各一个“*”包围的文本加粗。例如:这是一段*斜体*的文本。
case Key.I://为什么在这里处理?因为avalonEdit已经绑死Alt+Up,使用Alt+Up不起任何作用。
{
if (isCtrl)//移动到上一行的上方。
{
if (isShift)
{
if (!isAlt) SwapWithPreviewLine();
}
else
{
//斜体
SwitchItalic();//Markdown语法:前后各一个“_”包围的文本加粗。例如:这是一段_斜体_的文本。
}
e.Handled = true;
}
break;
}
case Key.K://为什么用这个?因为avalonEdit已经绑死Alt+Up,使用Alt+Up不起任何作用。
{
if (isCtrl && isShift && !isAlt)//移动到下一行的下方。
{
SwapWithNextLine();
e.Handled = true;
}
break;
}
//另有用处:case Key.Oem8: //Markdown语法:前后两组“**”包围的文本加粗。例如:这是一段**加粗**的文本。
case Key.B: //Markdown语法:前后两组“__”包围的文本加粗。例如:这是一段__加粗__的文本。
{
if (isCtrl)
{
//加粗
SwitchBold();
}
break;
}
case Key.Back://BackSpace,退格键
{
if (isShift && !isCtrl && !isAlt)
{
//删除到行首
var textArea = this.TextArea;
if (textArea != null && textArea.IsKeyboardFocused)
{
var curLine = this.Document.GetLineByOffset(this.SelectionStart);
var curLineText = this.Document.GetText(curLine.Offset, curLine.Length);
var fstSelectedLine = this.Document.GetLineByOffset(this.SelectionStart);
this.Select(fstSelectedLine.Offset, textArea.Selection.Length + (this.SelectionStart - fstSelectedLine.Offset));
textArea.PerformTextInput("");
if (CustomMarkdownSupport.IsTreeListTextLine(curLineText))
{
FormatTextTable();
}
e.Handled = true;
}
}
break;
}
case Key.Enter://回车键
{
if (isCtrl)
{
if (!isShift)
{
//在当前行下方插入一个新行,并移动光标。
AppendANewLine();
JumpToNextCell();
e.Handled = true;
}
}
else
{
if (isShift)
{
//在当前行上方插入一个新行,并移动光标。
InsertANewLine();
JumpToNextCell();
e.Handled = true;
}
else
{
//AvalonEdit有个默认行为:若一行文本以空格开头,无论在第一个非空格字符前按下回车,还是在这些空格前按下回车,
//新行开头都不会保留空格!!!
if (IsCompleting == false)
{
#region 处理树型列表
var curLine = CurrentDocumentLine();
var curLineText = this.Document.GetText(curLine.Offset, curLine.Length);
string header, tail; int level;
if (curLine != null && CustomMarkdownSupport.IsImageLinkLine(curLineText) == false &&
IsTreeListLine(curLineText, out header, out tail, out level))
{
e.Handled = true;
InsertNewLineAndFormat(isShift);
break;
}
#endregion
var textArea = this.TextArea;
if (textArea != null && textArea.IsKeyboardFocused)
{
var fstSelectedLine = this.Document.GetLineByOffset(this.SelectionStart);
//回车换行时保留的前导字串:
//^[\t  ☆○◎◇□△§※★●◆■▲>〉》]{ 1,}[ \t]*
//处理用户自定义的前导字符。
//顺序是:先A类,再B类,最后内置前导字符。基本原则:用户定义优先。
var leaderTextRegs = Globals.MainWindow.LeaderTextRegsA;
if (string.IsNullOrEmpty(leaderTextRegs) == false)
{
leaderTextRegs = ConvertToRegexTexts(leaderTextRegs);
if (ReplaceLeaderText(leaderTextRegs, false))
{
e.Handled = true; return;
}
}
leaderTextRegs = Globals.MainWindow.LeaderTextRegsB;
if (string.IsNullOrWhiteSpace(leaderTextRegs) == false)
{
leaderTextRegs = ConvertToRegexTexts(leaderTextRegs);
if (ReplaceLeaderText(leaderTextRegs, true))
{
e.Handled = true; return;
}
}
leaderTextRegs = @"^[\t  ☆○◎◇□△§※★●◆■▲>〉》]{1,}[  \t]*";
if (ReplaceLeaderText(leaderTextRegs, false))
{
e.Handled = true; return;
}
leaderTextRegs = @"^[" + Globals.MainWindow.ReplaceLeaderChar + "] {0,}";
//默认情况下,以替换式自动添加的前导字串总是“$+空格串”样式的,这是为了便于连续输入“$+空格串”的新行。
//但是用户可以指定另一字符。
if (ReplaceLeaderText(leaderTextRegs, false))
{
e.Handled = true; return;
}
}
}
}
//这个方法会造成无法自动滚动的问题。
}
break;
}
case Key.Tab://Tab键
{
if (!isCtrl && !isAlt)
{
if (IsCompleting == false)
{
if (JumpToNextExamElement(isShift))
{
e.Handled = true;
break;
}
}
if (isShift)
{
if (JumpOverMarks(ref e, isShift)) return;
//2017年1月11日,调整到这里是为了让树型文字表中的括号对啥的也支持使用Tab键来越过去。
#region 处理树型列表的层级调整
if (MoveTreeListTableLevel(e, isShift)) return;
#endregion
#region 将光标移动到上一单元格
var line = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
if (line == null) break;
var text = this.Document.GetText(line.Offset, line.Length);
if (text.Contains("|") == false && text.Contains("|") == false) break;
if (this.SelectionStart == line.Offset && this.SelectionLength == 0)
{
if (this.SelectionStart - 2 >= 0)
{
this.Select(this.SelectionStart - 2, 0);//2是\r\n的长度。
}
e.Handled = true;
break;
}
//先在text中查找前一个分割线
int preIndex = -1;
for (int i = this.SelectionStart - 1; i >= 0; i--)
{
char c = this.Text[i];
if (c == '|' || c == '|' || c == '\n' || c == '\r')
{
preIndex = i;
break;
}
}
//再找后一个分割线位置
int nextIndex = -1;
for (int i = this.SelectionStart; i < this.Text.Length; i++)
{
char c = this.Text[i];
if (c == '|' || c == '|' || c == '\r' || c == '\n')
{
nextIndex = i;
break;
}
}
if (nextIndex >= 0)
{
if (preIndex >= 0 && nextIndex > preIndex)
{
if (Globals.MainWindow.SelectCellFirst &&
(this.SelectionStart != preIndex + 1 || this.SelectionLength != nextIndex - preIndex - 1))
{
this.Select(preIndex + 1, nextIndex - preIndex - 1);
e.Handled = true;
}
else
{
//如果已选中当前单元格所有文本,继续找上一个单元格并选定。
var preIndex2 = -1;
for (int i = preIndex - 1; i >= 0; i--)
{
char c = this.Text[i];
if (c == '|' || c == '|' || c == '\r' || c == '\n')
{
preIndex2 = i;
break;
}
}
if (preIndex2 < 0 && preIndex > 0)
{
this.Select(preIndex - 1, 0);
e.Handled = true;
}
if (preIndex2 < preIndex)
{
this.Select(preIndex2 + 1, preIndex - preIndex2 - 1);
e.Handled = true;
}
}
}
else
{
this.Select(0, 0);//整个文档开头
e.Handled = true;
}
}
else
{
//如果就在整个文档的最后一个位置,此时肯定找不到nextIndex
//如果已选中当前单元格所有文本,继续找上一个单元格并选定。
var preIndex2 = -1;
for (int i = preIndex - 1; i >= 0; i--)
{
char c = this.Text[i];
if (c == '|' || c == '|' || c == '\r' || c == '\n')
{
preIndex2 = i;
break;
}
}
if (preIndex2 < 0 && preIndex > 0)
{
this.Select(preIndex - 1, 0);
e.Handled = true;
}
if (preIndex2 < preIndex)
{
this.Select(preIndex2 + 1, preIndex - preIndex2 - 1);
e.Handled = true;
}
}
#endregion
}
else
{
if (IsCompleting == false)
{
if (JumpToNextCell())
{
e.Handled = true;
}
else
{
//跳过反引号,用在:按Ctrl+`输入一对反引号并将插入点置于两个反引号之间,输入一些文本后直接跳过右反引号继续输入其它文本。
//这比按两个组合方向按键跳过去或者按方向键跳过去更方便。
//*号对与_对情形类似。
if (JumpOverMarks(ref e, isShift)) return;
#region 旧版的跳出反引号、星号对的代码。
//var index = this.SelectionStart + this.SelectionLength;
//if (index < this.Document.TextLength)
//{
// var nextChar = this.Document.Text[index];
// if (nextChar == '`')
// {
// this.Select(index + 1, 0);
// e.Handled = true;
// }
// else if (nextChar == '*')
// {
// if (index < this.Document.TextLength - 1)
// {
// var nextNextChar = this.Document.Text[index + 1];
// if (nextNextChar == '*')
// {
// this.Select(index + 2, 0);
// e.Handled = true;
// }
// else
// {
// this.Select(index + 1, 0);
// e.Handled = true;
// }
// }
// else
// {
// this.Select(index + 1, 0);
// e.Handled = true;
// }
// }
// else if (nextChar == '_')
// {
// if (index < this.Document.TextLength - 1)
// {
// var nextNextChar = this.Document.Text[index + 1];
// if (nextNextChar == '_')
// {
// this.Select(index + 2, 0);
// e.Handled = true;
// }
// else
// {
// this.Select(index + 1, 0);
// e.Handled = true;
// }
// }
// else
// {
// this.Select(index + 1, 0);
// e.Handled = true;
// }
// }
//}
#endregion
//2017年1月11日,调整到这里是为了让树型文字表中的括号对啥的也支持使用Tab键来越过去。
#region 处理树型列表的层级调整
if (MoveTreeListTableLevel(e, isShift)) return;
#endregion
}
}
}
}
break;
}
case Key.Z:
{
if (isAlt)
{
if (!isShift && isCtrl)
{
this.FoldSelectedBlock();
}
}
else
{
if (!isShift && isCtrl)
{
if (this.CanUndo) this.Undo();
e.Handled = true;
}
}
break;
}
case Key.Y:
{
if (!isAlt && !isShift && isCtrl)
{
if (this.CanRedo) this.Redo();
e.Handled = true;
}
break;
}
case Key.OemPeriod://句点键
{
if (isShift && isCtrl && !isAlt)
{
var fstSelLine = this.Document.GetLineByOffset(this.SelectionStart);
var lastSelLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
this.BeginChange();
for (int lineNumber = fstSelLine.LineNumber; lineNumber <= lastSelLine.LineNumber; lineNumber++)
{
var line = this.Document.GetLineByNumber(lineNumber);
var lineText = this.Document.GetText(line);
lineText = "> " + lineText;
this.Document.Replace(line.Offset, line.Length, lineText);
}
this.EndChange();
}
break;
}
case Key.OemComma://逗号健
{
if (isShift && isCtrl && !isAlt)
{
var fstSelLine = this.Document.GetLineByOffset(this.SelectionStart);
var lastSelLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
Regex reg = new Regex(@"^[  ]{0,3}[>〉》][  ]{0,}");
this.BeginChange();
for (int lineNumber = fstSelLine.LineNumber; lineNumber <= lastSelLine.LineNumber; lineNumber++)
{
var line = this.Document.GetLineByNumber(lineNumber);
var lineText = this.Document.GetText(line);
var match = reg.Match(lineText);
if (match.Success)
{
lineText = lineText.Substring(match.Length);
this.Document.Replace(line.Offset, line.Length, lineText);
}
}
this.EndChange();
}
break;
}
}
}
catch (Exception ex)
{
LMessageBox.Show("发生意外错误,无法执行操作。建议保存文件并重启程序。\r\n" +
ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
/// <summary>
/// 保存的正则表达式是多行的,每行一个规则,这个方法是用来将这些规则合并为一个规则字串的。
/// 合并后的样式类似:(规则一)|(规则二)|(规则三)
/// </summary>
/// <param name="leaderTextRegs"></param>
/// <returns></returns>
private static string ConvertToRegexTexts(string leaderTextRegs)
{
var spans = leaderTextRegs.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
if (spans.Length > 0)
{
StringBuilder sb = new StringBuilder();
sb.Append("(");
foreach (var span in spans)
{
sb.Append(span);
sb.Append(")|(");
}
leaderTextRegs = sb.ToString();
if (leaderTextRegs.EndsWith("|("))
leaderTextRegs = leaderTextRegs.Substring(0, leaderTextRegs.Length - 2);
}
return leaderTextRegs;
}
/// <summary>
/// 按Enter时在新行头部替换添加“前导字符串”。
/// </summary>
/// <param name="regexText">当前行头部前导字符的正则表达式规则。</param>
/// <param name="replaceMode">是否替换为“$+空格串”的样式。为false时原文输出。</param>
/// <returns></returns>
private bool ReplaceLeaderText(string regexText, bool replaceMode)
{
if (string.IsNullOrEmpty(regexText)) return false;
var textArea = this.TextArea;
var fstSelectedLine = this.Document.GetLineByOffset(this.SelectionStart);
var lineText = textArea.Document.GetText(fstSelectedLine.Offset, fstSelectedLine.Length);
try
{
var regex = new Regex(regexText);
var match = regex.Match(lineText);
if (match.Success == false) return false;
if (match.Value == lineText)//如果只添加了标记,却没有其它文本内容,直接取消标记。
{
textArea.TextEditor.BeginChange();
textArea.TextEditor.Select(fstSelectedLine.Offset, this.SelectionStart - fstSelectedLine.Offset + this.SelectionLength);
textArea.TextEditor.SelectedText = "\r\n";
textArea.TextEditor.Select(fstSelectedLine.Offset + 2, 0);
textArea.TextEditor.EndChange();
textArea.BringIntoView();
}
else
{
if (replaceMode)
{
//将匹配的文本转换为半角字符组成的等字符宽度字串
//一个全角字符被视为两个半角空格的字符宽度
//这里的“字符宽度”只考虑全、半角,不考虑字体带来的宽度变化
string spaceText;
var length = GetTextWidth(match.Value);
if (length > 1)
{
spaceText = Globals.MainWindow.ReplaceLeaderChar + new string(' ', length - 1);
}
else { spaceText = Globals.MainWindow.ReplaceLeaderChar; }
textArea.PerformTextInput($"\r\n{spaceText}");
}
else
{
//当前行符合规则的前导字串原文添加到新行头部。
textArea.PerformTextInput($"\r\n{match.Value}");
}
textArea.BringIntoView();
}
return true;
}
catch (Exception ex)
{
LMessageBox.Show("要保留的前导字符串正则表达式有错误,请重写正则表达式。\r\n\r\n" + ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
return false;
}
}
/// <summary>
/// 改变树型文字表行的层级。
/// </summary>
/// <param name="isShift">为真时(按Shift键+Tab键)层级减小一级(向左);
/// 为假时(只按Tab键)层级增加一级(向右)。</param>
private bool MoveTreeListTableLevel(KeyEventArgs e, bool isShift)
{
var curLine = CurrentDocumentLine();
string header, tail; int level;
if (curLine != null && IsTreeListLine(this.Document.GetText(curLine.Offset, curLine.Length), out header, out tail, out level))
{
e.Handled = true;
if (isShift) MoveTreeListLineLevelLeft(curLine, header, tail, level);
else MoveTreeListLineLevelRight(curLine, header, tail, level);
return true;
}
return false;
}
#region 处理树型列表
/// <summary>
/// 是否树型列表文本行。
/// </summary>
/// <param name="line">要判断的文本行。</param>
/// <param name="header">用于返回此文本行头部的格式字符串。</param>
/// <param name="tail">用于返回此文本行尾部的正文字符串。</param>
/// <param name="level">用于返回此文本行的层级,从1开始。当此层级为1时,文本行形如:!XXX。</param>
public static bool IsTreeListLine(string line, out string header, out string tail, out int level)
{
if (string.IsNullOrEmpty(line))
{
header = "";
tail = "";
level = 0;//0表示不是树型列表文本行。
return false;
}
Regex rgx = new Regex(@"^[!!][│ ]{0,}[├└]{0,}(?!([  \t]*\{))");
var match = rgx.Match(line);
if (match.Success)
{
header = match.Value;
tail = line.Substring(match.Length);
level = header.Length;
return true;
}
else
{
header = tail = "";
level = 0;//0表示不是树型列表文本行。
return false;
}
}
private DocumentLine CurrentDocumentLine()
{
return this.Document.GetLineByOffset(this.SelectionStart);
}
private string CurrentLine()
{
int preReturnIndex = this.Text.LastIndexOf("\r\n", this.SelectionStart);
int nextReturnIndex = this.Text.IndexOf("\r\n", this.SelectionStart);
if (preReturnIndex < 0) preReturnIndex = -2;//后面会加2(\r\n's Length)
if (nextReturnIndex < 0) nextReturnIndex = this.Text.Length;
var length = nextReturnIndex - preReturnIndex - 2;
if (length < 0) length = 0;
return this.Text.Substring(preReturnIndex + 2, length);
}
private DocumentLine PreviewDocumentLine()
{
var curLine = CurrentDocumentLine();
if (curLine == null || curLine.Offset <= 0) return null;
return this.Document.GetLineByOffset(curLine.Offset - 2);//-\r\n's Length
}
private string PreviewLine()
{
int preReturnIndex = this.Text.LastIndexOf("\r\n", this.SelectionStart);
if (preReturnIndex <= 0) return "";
int prepreReturnIndex = this.Text.LastIndexOf("\r\n", preReturnIndex - 1);
if (prepreReturnIndex < 0) return this.Text.Substring(0, preReturnIndex);
return this.Text.Substring(prepreReturnIndex + 2, preReturnIndex - prepreReturnIndex - 2);
}
private DocumentLine NextDocumentLine()
{
var curLine = CurrentDocumentLine();
//当插入点在最后一个字符时,TextLength==lastLine.EndOffset
if (curLine == null || curLine.EndOffset >= this.Document.TextLength) return null;
return this.Document.GetLineByOffset(curLine.EndOffset + 2);//+\r\n's Length
}
private string NextLine()
{
int nextReturnIndex = this.Text.IndexOf("\r\n", this.SelectionStart);
if (nextReturnIndex < 0) return "";
int nextnextReturnIndex = this.Text.IndexOf("\r\n", nextReturnIndex + 2);
if (nextnextReturnIndex < 0) return this.Text.Substring(nextReturnIndex + 2);
return this.Text.Substring(nextReturnIndex + 2, nextnextReturnIndex - nextReturnIndex - 2);
}
/// <summary>
/// 根据当前行的上一行决定可以移动的层级,并进行整体格式化。
/// 左移可连续多次移动,不需要考虑上一行的层级,但需要考虑上一行的格式化。
/// 左移前跟随的所有下级均须同步左移。
/// </summary>
private void MoveTreeListLineLevelLeft(DocumentLine curLine, string header, string tail, int level)
{
if (level <= 1) return;//1级标题不能再左移了。
//左移时,所有级别高于当前行的、跟随在当前行之后的都顺序左移一级。
//基本思路:本行左移时,先自动生成本行的基准头部。
// 由于本行左移时,可能对前面所有行产生影响,所以要对前面的行重新计算格式字符;
// 本行左移时,可能对后面所有行产生影响,所以还需要对后面的行重新计算格式字符。
// →所以不如寻找头、尾,然后依次重新写格式字符。
//层级标准:只有一个!号的算1级,[!│ ├]这四个字符(其中一个是全角空格)每有一个加1级。
List<TreeListTextLine> previewLines = new List<TreeListTextLine>();
var preLine = curLine.PreviousLine;
while (preLine != null)
{
var preLineText = this.Document.GetText(preLine.Offset, preLine.Length);
string preHeader, preTail; int preLevel;
if (IsTreeListLine(preLineText, out preHeader, out preTail, out preLevel))
{
previewLines.Add(new TreeListTextLine()
{
Line = preLine,
OldText = preLineText,
NewText = "",
//HeaderTextArray = preHeader.ToCharArray(),
TailText = preTail,
OldLevel = preLevel,
NewLevel = preLevel, //之前的行层级不变
});
preLine = preLine.PreviousLine;
}
else break;
}
List<TreeListTextLine> allLines = new List<TreeListTextLine>();
for (int i = previewLines.Count - 1; i >= 0; i--)
{
allLines.Add(previewLines[i]);
}
List<TreeListTextLine> moveLines = new List<TreeListTextLine>();
moveLines.Add(new TreeListTextLine()
{
Line = curLine,
//HeaderTextArray = header.ToCharArray(),
NewText = "",
OldText = this.Document.GetText(curLine.Offset, curLine.Length),
TailText = tail,
OldLevel = level,
NewLevel = level - 1, //当前行层级降一级
});
allLines.Add(moveLines[0]);
var nextLine = curLine.NextLine;
while (nextLine != null)
{
string oldNextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
string nextHeader, nextTail; int nextLevel;
if (IsTreeListLine(oldNextLineText, out nextHeader, out nextTail, out nextLevel))
{
if (nextLevel > level)
{
var nextTreeListTextLine = new TreeListTextLine()
{
Line = nextLine,
//HeaderTextArray = nextHeader.ToCharArray(),
NewText = "",
OldText = oldNextLineText,
TailText = nextTail,
OldLevel = nextLevel,
NewLevel = nextLevel - 1, //层级高于当前行的也降一级
};
moveLines.Add(nextTreeListTextLine);
allLines.Add(nextTreeListTextLine); //无论层级都应加入此列表
}
else break;
}
else break;
nextLine = nextLine.NextLine;
}
while (nextLine != null)
{
string oldNextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
string nextHeader, nextTail; int nextLevel;
if (IsTreeListLine(oldNextLineText, out nextHeader, out nextTail, out nextLevel))
{
var nextTreeListTextLine = new TreeListTextLine()
{
Line = nextLine,
//HeaderTextArray = nextHeader.ToCharArray(),
NewText = "",
OldText = oldNextLineText,
TailText = nextTail,
OldLevel = nextLevel,
NewLevel = nextLevel, //当碰到一个层级与当前行移位前层级相同的行后,之后所有行都不再降级。
};
allLines.Add(nextTreeListTextLine);
}
else break;
nextLine = nextLine.NextLine;
}
//重新格式化。
FormatAllTreeLines(allLines, true);
}
/// <summary>
/// 根据当前行的上一行决定可以移动的层级,并进行整体格式化。
/// 右移最多移动到上一行的下一级。
/// 右移前跟随的所有下级同步右移一级。
/// </summary>
private void MoveTreeListLineLevelRight(DocumentLine curLine, string header, string tail, int level)
{
if (curLine == null) return;
//右移时最多移动到上一行的下一级。
//如果上一行不是树型列表项,禁止;如果上一行是树型列表项,最多移动到上一行的更高1级。
var preLine = curLine.PreviousLine;
if (preLine == null) return;
var pText = this.Document.GetText(preLine.Offset, preLine.Length);
string pHeader, pTail; int pLevel;
if (IsTreeListLine(pText, out pHeader, out pTail, out pLevel))
{
if (level > pLevel) return;
}
else return;
List<TreeListTextLine> allLines = new List<TreeListTextLine>();
List<TreeListTextLine> previewLines = new List<TreeListTextLine>();
while (preLine != null)
{
var preLineText = this.Document.GetText(preLine.Offset, preLine.Length);
string preHeader, preTail; int preLevel;
if (IsTreeListLine(preLineText, out preHeader, out preTail, out preLevel))
{
previewLines.Add(new TreeListTextLine()
{
Line = preLine,
OldText = preLineText,
NewText = "",
//HeaderTextArray = preHeader.ToCharArray(),
TailText = preTail,
OldLevel = preLevel,
NewLevel = preLevel, //之前的行层级不变
});
preLine = preLine.PreviousLine;
}
else break;
}
for (int i = previewLines.Count - 1; i >= 0; i--)
{
allLines.Add(previewLines[i]);
}
List<TreeListTextLine> moveLines = new List<TreeListTextLine>();
moveLines.Add(new TreeListTextLine()
{
Line = curLine,
//HeaderTextArray = header.ToCharArray(),
NewText = "",
OldText = this.Document.GetText(curLine.Offset, curLine.Length),
TailText = tail,
OldLevel = level,
NewLevel = level + 1, //当前行层级降一级
});
allLines.Add(moveLines[0]);
var nextLine = curLine.NextLine;
while (nextLine != null)
{
string oldNextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
string nextHeader, nextTail; int nextLevel;
if (IsTreeListLine(oldNextLineText, out nextHeader, out nextTail, out nextLevel))
{
if (nextLevel > level)
{
var nextTreeListTextLine = new TreeListTextLine()
{
Line = nextLine,
//HeaderTextArray = nextHeader.ToCharArray(),
NewText = "",
OldText = oldNextLineText,
TailText = nextTail,
OldLevel = nextLevel,
NewLevel = nextLevel + 1, //层级高于当前行的也升一级
};
moveLines.Add(nextTreeListTextLine);
allLines.Add(nextTreeListTextLine); //无论层级都应加入此列表
}
else break;
}
else break;
nextLine = nextLine.NextLine;
}
while (nextLine != null)
{
string oldNextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
string nextHeader, nextTail; int nextLevel;
if (IsTreeListLine(oldNextLineText, out nextHeader, out nextTail, out nextLevel))
{
var nextTreeListTextLine = new TreeListTextLine()
{
Line = nextLine,
//HeaderTextArray = nextHeader.ToCharArray(),
NewText = "",
OldText = oldNextLineText,
TailText = nextTail,
OldLevel = nextLevel,
NewLevel = nextLevel, //当碰到一个层级与当前行移位前层级相同的行后,之后所有行都不再升级。
};
allLines.Add(nextTreeListTextLine);
}
else break;
nextLine = nextLine.NextLine;
}
//重新格式化。
FormatAllTreeLines(allLines);
}
private void FormatAllTreeLines(List<TreeListTextLine> allLines, bool? selectLeft = false)
{
if (allLines == null || allLines.Count <= 0) return;
foreach (var tLine in allLines)
{
tLine.HeaderTextArray = BuildTreeLineBaseHeader(tLine.NewLevel).ToCharArray();
}
int basePreLevel = 0;
for (int i = 0; i < allLines.Count; i++)
{
var tLine = allLines[i];
if (tLine.OldLevel > basePreLevel + 1)
{
tLine.NewLevel = basePreLevel + 1;
}
basePreLevel = tLine.NewLevel;
}
for (int i = 0; i < allLines.Count; i++)
{
var tLine = allLines[i];
//格式化冒号开头的注释
if (tLine.TailText.StartsWith(":"))
{
tLine.TailText = ":" + tLine.TailText.Substring(1);
}
var newHeaderText = BuildTreeLineBaseHeader(tLine.NewLevel);
tLine.NewText = newHeaderText + tLine.TailText;
tLine.HeaderTextArray = newHeaderText.ToCharArray();
//根据情况刷新竖线。
if (tLine.NewLevel <= 1) continue;
for (int j = i - 1; j >= 0; j--)
{
var aPreLine = allLines[j];
if (aPreLine.NewLevel < tLine.NewLevel) break;
if (aPreLine.NewLevel == tLine.NewLevel)
{
aPreLine.HeaderTextArray[tLine.NewLevel - 1] = '├';
break;
}
if (aPreLine.NewLevel > tLine.NewLevel)
{
aPreLine.HeaderTextArray[tLine.NewLevel - 1] = '│';
}
}
}
StringBuilder sb = new StringBuilder();
foreach (var line in allLines)
{
sb.Append(new string(line.HeaderTextArray) + line.TailText);
sb.Append("\r\n");
}
this.BeginChange();
var oldSelectionStart = this.SelectionStart;
var oldSelectionLength = this.SelectionLength;
var newText = sb.ToString();
if (newText.EndsWith("\r\n"))
{
newText = newText.Substring(0, newText.Length - 2);
}
this.Document.Replace(allLines[0].Line.Offset, allLines[allLines.Count - 1].Line.EndOffset - allLines[0].Line.Offset, newText);
if (selectLeft.HasValue)
{
if (selectLeft.Value)
{
if (oldSelectionStart > 0)
{
this.Select(oldSelectionStart - 1, oldSelectionLength);
}
else { this.Select(0, 0); }
}
else
{
if (oldSelectionLength < this.Document.TextLength - 1)
{
this.Select(oldSelectionStart + 1, oldSelectionLength);
}
}
}
this.EndChange();
}
private string BuildTreeLineBaseHeader(int level)
{
if (level <= 0) return "";
if (level == 1) return "!";
if (level == 2) return "!└";
return $"!{new string(' ', level - 2)}└";
}
private void InsertNewLineAndFormat(bool insertAtPreview = false)
{
var curLine = CurrentDocumentLine();
var preLine = PreviewDocumentLine();
var nextLine = NextDocumentLine();
var curLineText = curLine == null ? "" : this.Document.GetText(curLine.Offset, curLine.Length);
var preLineText = preLine == null ? "" : this.Document.GetText(preLine.Offset, preLine.Length);
var nextLineText = nextLine == null ? "" : this.Document.GetText(nextLine.Offset, nextLine.Length);
//MessageBox.Show($"P行:{preLineText}\r\nC行:{curLineText}\r\nN行:{nextLineText}");
string headerText, tailText;
int level;
if (IsTreeListLine(curLineText, out headerText, out tailText, out level))
{
if (this.SelectionStart == curLine.Offset && this.SelectionLength == 0)
{
this.BeginChange();
this.Document.Insert(curLine.Offset, "\r\n");
this.EndChange();
this.Select(curLine.Offset + 2, 0);
return;
}
this.BeginChange();
int startReplaceOffset = this.SelectionStart;
int tailStartOffset = curLine.Offset + headerText.Length;
int replaceLength = curLine.EndOffset - this.SelectionStart;
if (startReplaceOffset < tailStartOffset)
{
startReplaceOffset = tailStartOffset;
replaceLength = curLine.EndOffset - startReplaceOffset;
}
var curTail = this.Document.GetText(startReplaceOffset, replaceLength);
this.Select(this.SelectionStart, 0);
var mark = "└";
string nextHeaderText, nextTailText; int nextLevel;
if (IsTreeListLine(nextLineText, out nextHeaderText, out nextTailText, out nextLevel) && nextLevel == level)
{
mark = "├";
}
if (NextBrotherLineExist(curLine, level))
{
mark = "├";
}
else
{
mark = "└";
}
if (level == 1) mark = "!";
if (curLine.TotalLength == curLine.Length)//没有定界符(\r\n)最后一行
{
this.Document.Insert(curLine.EndOffset, "\r\n" + headerText.Substring(0, headerText.Length - 1) + $"{mark}{curTail}\r\n");
}
else
{
this.Document.Insert(curLine.EndOffset + 2, headerText.Substring(0, headerText.Length - 1) + $"{mark}{curTail}\r\n");
}
this.Select(curLine.EndOffset + 2 + headerText.Length, 0);
if (replaceLength > 0)//这个替换必须放到后面进行
{
this.Document.Replace(startReplaceOffset, replaceLength, "");
}
FormatPreviewBrotherLines(this.Document.GetLineByNumber(curLine.LineNumber + 1), level);
this.EndChange();
}
}
private bool NextBrotherLineExist(DocumentLine curLine, int level)
{
if (level <= 0) return false;
if (curLine == null || curLine.LineNumber >= this.Document.LineCount) return false;
var nextLine = this.Document.GetLineByNumber(curLine.LineNumber + 1);
while (nextLine != null)
{
string nextHeader, nextTail; int nextLevel;
if (IsTreeListLine(this.Document.GetText(nextLine.Offset, nextLine.Length), out nextHeader, out nextTail, out nextLevel))
{
if (nextLevel < level) return false;//截断
if (nextLevel == level) return true;
else
{
if (nextLine.LineNumber == this.Document.LineCount) return false;
nextLine = this.Document.GetLineByNumber(nextLine.LineNumber + 1);
continue;
}
}
else
{
if (nextLine.LineNumber == this.Document.LineCount) return false;
nextLine = this.Document.GetLineByNumber(nextLine.LineNumber + 1);
continue;
}
}
return false;
}
private void FormatPreviewBrotherLines(DocumentLine curLine, int level)
{
if (level == 1) return;//level==1时,只有惊叹号开头,其它不论。
if (curLine == null || curLine.LineNumber <= 1) return;
DocumentLine preLine = null;
preLine = this.Document.GetLineByNumber(curLine.LineNumber - 1);
while (preLine != null)
{
var preLineText = this.Document.GetText(preLine.Offset, preLine.Length);
string headerText, tailText; int preLevel;
if (IsTreeListLine(preLineText, out headerText, out tailText, out preLevel) == false) return;
if (preLevel != level) break;
if (preLineText[level - 1] != '├')
{
this.Document.Replace(preLine.Offset + preLevel - 1, 1, "├");
}
if (preLine.LineNumber <= 1) break;
preLine = this.Document.GetLineByNumber(preLine.LineNumber - 1);
}
}
#endregion
/// <summary>
/// 跳转到下一个输入区域。这些输入区域包括:成对的小括弧之间,成对的大括弧之间,成对的中括弧之间等等。
/// </summary>
/// <param name="e"></param>
private bool JumpOverMarks(ref KeyEventArgs e, bool isShift)
{
if (SelectionLength != 0) return false;
//只有当插入点正好在某些特殊符号(及符号组合)前面时才起作用,用来跳过这些个特殊符号组合。
try
{
var regexText = "(</?[a-zA-Z]{1,}( [^><]*)?>)|(\\*\\*)|(\\[\\=)|(\\=\\])|(__)|([_\\*“”‘’'\"`<>《》〈〉\\(\\)\\[\\]\\{\\}()〔〕〈〉「」『』〖〗【】[]{}"'°])";
Regex regex = GetRegEx(regexText, isShift);
int start = regex.Options.HasFlag(RegexOptions.RightToLeft) ? SelectionStart : SelectionStart + SelectionLength;
Match match = regex.Match(Text, start);
if (!match.Success) // start again from beginning or end
{
if (regex.Options.HasFlag(RegexOptions.RightToLeft))
match = regex.Match(Text, Text.Length);
else
match = regex.Match(Text, 0);
}
if (match.Success)
{
if (isShift)
{
if (match.Index + match.Length != SelectionStart) return false;
var destSel = match.Index;
if (destSel >= 0)
{
Select(destSel, 0);
TextLocation loc = Document.GetLocation(match.Index);
ScrollTo(loc.Line, loc.Column);
e.Handled = true;
return true;
}
}
else
{
if (match.Index != SelectionStart + SelectionLength) return false;
var destSel = match.Index + match.Length;
if (destSel <= Document.TextLength)
{
Select(destSel, 0);
TextLocation loc = Document.GetLocation(match.Index);
ScrollTo(loc.Line, loc.Column);
e.Handled = true;
return true;
}
}
return false;
}
return false;
}
catch (Exception ex)
{
LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
return false;
}
}
/// <summary>
/// 自动完成窗口目前是否可见,是否正在进行提示。
/// </summary>
private bool IsCompleting
{
get
{
if (this.CompletionWindow != null && this.CompletionWindow.Visibility == Visibility.Visible)
return true;
else return false;
}
}
/// <summary>
/// 切换目标文本的任务列表状态标记。
/// </summary>
/// <param name="backOrder">决定正序还是逆序。</param>
/// <param name="isShift">决定是作用于本行还是作用于本行所在的任务列表项。false时仅作用于本行文本。</param>
public void SwitchTaskListItemOrDateTimeLineState(bool backOrder, bool isShift)
{
int startOffset = this.SelectionStart;
int selectionLength = this.SelectionLength;
int endOffset = this.SelectionStart + this.SelectionLength;
var startLine = this.Document.GetLineByOffset(startOffset);
var endLine = this.Document.GetLineByOffset(endOffset);
var startLineText = this.Document.GetText(startLine.Offset, startLine.Length);
if (startLineText.StartsWith(" ") || startLineText.StartsWith("\t"))
{
LMessageBox.Show("插入点所在的这行文本属于一个代码块,不能执行此命令!", Globals.AppName,
MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
if (isShift)
{
if (CustomMarkdownSupport.IsTaskLine(startLineText) == false && startLine.LineNumber > 1)
{
//如果当前行不是任务列表,则向文件头部倒找。
DocumentLine destLine = null;
destLine = this.Document.GetLineByNumber(startLine.LineNumber - 1);
while (destLine != null)
{
var destLineText = this.Document.GetText(destLine.Offset, destLine.Length);
if (CustomMarkdownSupport.IsTaskLine(destLineText))
{
break;
}
destLine = this.Document.GetLineByNumber(destLine.LineNumber - 1);
}
if (destLine != null)
{
this.Select(destLine.Offset, destLine.Length);
SwitchTaskListItemOrDateTimeLineState(backOrder, false);
this.Select(startLine.Offset, selectionLength);
return;
}
}//如果本行就是任务列表,则直接改动本行即可,继续。
}
CustomMarkdownSupport.TaskListItemState state = GetTaskListItemState(startLineText);
//只改第一行,避免无谓的错误,也可降低开发难度。
//CustomMarkdownSupport.TaskListItemState destState;
string newHeader;
string oldHeader;
if (backOrder)
{
switch (state)
{
case CustomMarkdownSupport.TaskListItemState.Finished:
{
oldHeader = "[+]";
newHeader = "[%] ";
break;
}
case CustomMarkdownSupport.TaskListItemState.Precessing:
{
oldHeader = "[%]";
//destState = CustomMarkdownSupport.TaskListItemState.Finished;
newHeader = "[-] ";
break;
}
case CustomMarkdownSupport.TaskListItemState.UnStart:
{
oldHeader = "[-]";
//destState = CustomMarkdownSupport.TaskListItemState.Precessing;
newHeader = "[#] ";
break;
}
case CustomMarkdownSupport.TaskListItemState.Aborted:
{
oldHeader = "[#]";
//destState = CustomMarkdownSupport.NotTaskListItem
newHeader = "[+] ";
break;
}
default:
{
oldHeader = "";
//destState = CustomMarkdownSupport.TaskListItemState.UnStart;
newHeader = "[#] ";
break;
}
}
}
else
{
switch (state)
{
case CustomMarkdownSupport.TaskListItemState.Finished:
{
oldHeader = "[+]";
//destState = CustomMarkdownSupport.TodoListItemState.Abort;
newHeader = "[#] ";
break;
}
case CustomMarkdownSupport.TaskListItemState.Precessing:
{
oldHeader = "[%]";
//destState = CustomMarkdownSupport.TodoListItemState.Finished;
newHeader = "[+] ";
break;
}
case CustomMarkdownSupport.TaskListItemState.UnStart:
{
oldHeader = "[-]";
//destState = CustomMarkdownSupport.TodoListItemState.Precessing;
newHeader = "[%] ";
break;
}
case CustomMarkdownSupport.TaskListItemState.Aborted:
{
oldHeader = "[#]";
//destState = CustomMarkdownSupport.NotTodoListItem
newHeader = "[-] ";
break;
}
default:
{
oldHeader = "";
//destState = CustomMarkdownSupport.TodoListItemState.UnStart;
newHeader = "[-] ";
break;
}
}
}
if (CustomMarkdownSupport.IsDateLine(startLineText))
{
if (oldHeader == "")
{
var rightIndex = startLineText.IndexOf("]");
if (rightIndex >= 0)
{
startLineText = startLineText.Substring(0, rightIndex + 1) + newHeader + " " + startLineText.Substring(rightIndex + 1).TrimStart(new char[] { ' ' });
this.Select(startLine.Offset, startLine.Length);
this.SelectedText = startLineText;
var indexTmp = startLineText.IndexOf("]");
if (indexTmp >= 0)
{
this.Select(startLine.Offset + indexTmp + newHeader.Length + 1, 0);
}
else
{
this.Select(startLine.Offset, 0);
}
return;
}
}
else
{
var indexOfOldHeader = startLineText.IndexOf(oldHeader);
if (indexOfOldHeader >= 0)
{
startLineText = startLineText.Substring(0, indexOfOldHeader) + newHeader + startLineText.Substring(indexOfOldHeader + oldHeader.Length).TrimStart(new char[] { ' ' });
this.Select(startLine.Offset, startLine.Length);
this.SelectedText = startLineText;
var indexTmp = startLineText.IndexOf("]");
if (indexTmp >= 0)
{
this.Select(startLine.Offset + indexTmp + newHeader.Length + 1, 0);
}
else
{
this.Select(startLine.Offset, 0);
}
return;
}
}
}
int index = startLineText.Length;
for (int i = 0; i < startLineText.Length - 1; i++)
{
char c = startLineText[i];
if (c != ' ' && c != ' ' && c != '\t' && c != '[' && c != ']' && c != '+' && c != '-' && c != '%' && c != '#')
{
index = i;
break;
}
}
var tail = startLineText.Substring(index);
this.Select(startLine.Offset, startLine.Length);
this.SelectedText = newHeader + tail;
this.Select(startLine.Offset + newHeader.Length, 0);
}
private CustomMarkdownSupport.TaskListItemState GetTaskListItemState(string lineText)
{
if (string.IsNullOrWhiteSpace(lineText)) return CustomMarkdownSupport.TaskListItemState.NotTaskListItem;
var tmp = lineText.Replace(" ", "").Replace("\t", "").Replace(" ", "");
if (tmp.StartsWith("[-]")) return CustomMarkdownSupport.TaskListItemState.UnStart;
if (tmp.StartsWith("[%]")) return CustomMarkdownSupport.TaskListItemState.Precessing;
if (tmp.StartsWith("[+]")) return CustomMarkdownSupport.TaskListItemState.Finished;
if (tmp.StartsWith("[#]")) return CustomMarkdownSupport.TaskListItemState.Aborted;
if (CustomMarkdownSupport.IsDateLine(lineText))
{
if (tmp.IndexOf("[-]") >= 0) return CustomMarkdownSupport.TaskListItemState.UnStart;
if (tmp.IndexOf("[%]") >= 0) return CustomMarkdownSupport.TaskListItemState.Precessing;
if (tmp.IndexOf("[+]") >= 0) return CustomMarkdownSupport.TaskListItemState.Finished;
if (tmp.IndexOf("[#]") >= 0) return CustomMarkdownSupport.TaskListItemState.Aborted;
}
return CustomMarkdownSupport.TaskListItemState.NotTaskListItem;
}
/// <summary>
/// 在当前选定文本两侧加上倾斜标记文本。
/// </summary>
public void SwitchItalic()
{
if (this.SelectionLength == 0)
{
//this.Document.Insert(this.SelectionStart, "_");
this.SelectedText = "__";
this.Select(this.SelectionStart + 1, 0);
}
else
{
var lineEnd = this.Document.GetLineByOffset(this.SelectionStart).EndOffset;
if (this.SelectionStart + this.SelectionLength > lineEnd) return;//不支持跨行
if ((this.SelectedText.StartsWith("_") && this.SelectedText.EndsWith("_")) ||
(this.SelectedText.StartsWith("*") && this.SelectedText.EndsWith("*")))
{
this.SelectedText = this.SelectedText.Substring(1, this.SelectedText.Length - 2);
}
else
{
this.SelectedText = "*" + this.SelectedText + "*";
}
}
}
/// <summary>
/// 在当前选定文本两侧加上或取消代码片段标记文本(``,即一对反引号)。
/// </summary>
public void SwitchCodeSnippet()
{
if (this.SelectionLength == 0)
{
this.SelectedText = "``";
this.Select(this.SelectionStart + 1, 0);
}
else
{
var lineEnd = this.Document.GetLineByOffset(this.SelectionStart).EndOffset;
if (this.SelectionStart + this.SelectionLength > lineEnd) return;//不支持跨行
if (this.SelectedText.StartsWith("`") && this.SelectedText.EndsWith("`"))
{
this.SelectedText = this.SelectedText.Substring(1, this.SelectedText.Length - 2);
}
else
{
this.SelectedText = "`" + this.SelectedText + "`";
}
}
}
/// <summary>
/// 在当前选定文本两侧加上加粗标记文本。
/// </summary>
public void SwitchBold()
{
//原本使用****语法,但易造成冲突,在引用块中导致高亮显示不正常
if (this.SelectionLength == 0)
{
//this.Document.Insert(this.SelectionStart, "_");
this.SelectedText = "____";
this.Select(this.SelectionStart + 2, 0);
}
else
{
var lineEnd = this.Document.GetLineByOffset(this.SelectionStart).EndOffset;
if (this.SelectionStart + this.SelectionLength > lineEnd) return;//不支持跨行
if ((this.SelectedText.StartsWith("__") && this.SelectedText.EndsWith("__")) ||
(this.SelectedText.StartsWith("**") && this.SelectedText.EndsWith("**")))
{
this.SelectedText = this.SelectedText.Substring(2, this.SelectedText.Length - 4);
}
else
{
this.SelectedText = "__" + this.SelectedText + "__";
}
}
}
/// <summary>
/// 折叠插入点所在的可折叠区域。
/// </summary>
public void FoldSelectedBlock()
{
ICSharpCode.AvalonEdit.Folding.FoldingSection folding = null;
foreach (var i in this.FoldingManager.AllFoldings)
{
if (i.StartOffset <= this.SelectionStart &&
i.EndOffset >= this.SelectionStart + this.SelectionLength)
{
if (folding == null)
{
folding = i;
continue;
}
else
{
if (folding.StartOffset < i.StartOffset)
{
folding = i;
continue;
}
}
}
}
if (folding != null)
{
folding.IsFolded = !folding.IsFolded;
}
}
/// <summary>
/// 跳转到下一个单元格。
/// </summary>
public bool JumpToNextCell()
{
var line = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
if (line == null) return false;
var text = this.Document.GetText(line.Offset, line.Length);
if (text.Contains("|") == false && text.Contains("|") == false) return false;
if (this.SelectionStart == line.EndOffset && this.SelectionLength == 0)
{
if (this.SelectionStart + 2 < this.Text.Length)
{
this.Select(this.SelectionStart + 2, 0);//2是\r\n的长度。
}
return true;
}
//先在text中查找前一个分割线
int preIndex = -1;
for (int i = this.SelectionStart + this.SelectionLength - 1; i >= 0; i--)
{
char c = this.Text[i];
if (c == '|' || c == '|' || c == '\n' || c == '\r')
{
preIndex = i;
break;
}
}
//再找后一个分割线位置
int nextIndex = -1;
for (int i = this.SelectionStart + this.SelectionLength; i < this.Text.Length; i++)
{
char c = this.Text[i];
if (c == '|' || c == '|' || c == '\r' || c == '\n')
{
nextIndex = i;
break;
}
}
if (nextIndex > preIndex)
{
//如果在首行
if (line.LineNumber == 1 && preIndex < 0)
{
if (this.SelectionStart != 0 || this.SelectionLength != nextIndex)
{
this.Select(0, nextIndex);
}
else
{
//如果已选中当前单元格所有文本,继续找下一个单元格并选定。
var nextIndex2 = -1;
for (int i = nextIndex + 1; i < this.Text.Length; i++)
{
char c = this.Text[i];
if (c == '|' || c == '|' || c == '\r' || c == '\n')
{
nextIndex2 = i;
break;
}
}
if (nextIndex2 < 0)
{
this.Select(nextIndex + 1, 0);
}
else if (nextIndex2 > nextIndex)
{
this.Select(nextIndex + 1, nextIndex2 - nextIndex - 1);
}
}
return true;
}
if (Globals.MainWindow.SelectCellFirst &&
(this.SelectionStart != preIndex + 1 || this.SelectionLength != nextIndex - preIndex - 1))
{
this.Select(preIndex + 1, nextIndex - preIndex - 1);
}
else
{
//如果已选中当前单元格所有文本,继续找下一个单元格并选定。
var nextIndex2 = -1;
for (int i = nextIndex + 1; i < this.Text.Length; i++)
{
char c = this.Text[i];
if (c == '|' || c == '|' || c == '\r' || c == '\n')
{
nextIndex2 = i;
break;
}
}
if (nextIndex2 < 0)
{
this.Select(nextIndex + 1, 0);
}
else if (nextIndex2 > nextIndex)
{
this.Select(nextIndex + 1, nextIndex2 - nextIndex - 1);
}
}
return true;
}
return false;
}
/// <summary>
/// 跳转到下一个试题元素。
/// </summary>
/// <param name="isShift">Shift 键是否处于被按下状态。</param>
private bool JumpToNextExamElement(bool isShift)
{
if (Globals.MainWindow.IsExamEnabled == false) return false;
string textToFind = ">>[^>]";
Regex regex = GetRegEx(textToFind, isShift);
int start;
if (isShift)
{
var line = this.Document.GetLineByOffset(this.SelectionStart);
if (line != null)
{
var lineText = this.Document.GetText(line.Offset, line.Length);
//找一下,前一个【】在哪里
//if (lineText.StartsWith("  试题>>"))
//{
var startIndex = this.SelectionStart + this.SelectionLength - line.Offset - 1;
if (startIndex >= 0)
{
var rightIndex = lineText.LastIndexOf('】', startIndex);
if (rightIndex >= 0)
{
var leftIndex = lineText.LastIndexOf('【', rightIndex);
if (rightIndex > leftIndex)
{
this.Select(line.Offset + leftIndex + 1, rightIndex - leftIndex - 1);
TextLocation loc = this.Document.GetLocation(line.Offset + leftIndex + 1);
this.ScrollTo(loc.Line, loc.Column);
return true;
}
}
}
//}
if (lineText.Contains(">>"))
{
start = line.Offset;//跳过这行的开头向文件头部逆向查找,否则只能停留在本行(这样没用处)
}
else
{
start = regex.Options.HasFlag(RegexOptions.RightToLeft) ? this.SelectionStart : this.SelectionStart + this.SelectionLength;
}
}
else return false;
}
else
{
var line = this.Document.GetLineByOffset(this.SelectionStart);
if (line != null)
{
//正向时,不需要越过标记,所以这里不需要判断本行有无“>>”。
//找一下,下一个【】在哪里
var lineText = this.Document.GetText(line.Offset, line.Length);
var startIndex = this.SelectionStart - line.Offset;
if (startIndex >= 0)
{
var leftIndex = lineText.IndexOf('【', startIndex);
if (leftIndex >= 0)
{
var rightIndex = lineText.IndexOf('】', leftIndex);
if (rightIndex > leftIndex)
{
this.Select(line.Offset + leftIndex + 1, rightIndex - leftIndex - 1);
TextLocation loc = this.Document.GetLocation(line.Offset + leftIndex + 1);
this.ScrollTo(loc.Line, loc.Column);
return true;
}
}
}
start = line.EndOffset;
}
else return false;
}
Match match = regex.Match(this.Text, start);
if (!match.Success) //重头开始或结束
{
if (regex.Options.HasFlag(RegexOptions.RightToLeft))
match = regex.Match(this.Text, this.Text.Length);
else
match = regex.Match(this.Text, 0);
}
if (match.Success)
{
var destLine = this.Document.GetLineByOffset(match.Index + 2);
int length;
var destLineText = this.Document.GetText(destLine.Offset, destLine.Length);
var firstIndex = match.Index - destLine.Offset + 2;
var lastIndex = destLineText.LastIndexOf("<<");
if (lastIndex >= firstIndex)
{
length = lastIndex - firstIndex;
}
else
{
length = destLine.EndOffset - match.Index - 2;
}
this.Select(match.Index + 2, length);
TextLocation loc = this.Document.GetLocation(match.Index);
this.ScrollTo(loc.Line, loc.Column);
}
return match.Success;
}
/// <summary>
/// 根据选项构建正则表达式。
/// </summary>
/// <param name="textToFind">要查找的文本</param>
/// <param name="leftToRight">正序还是逆序。</param>
private Regex GetRegEx(string textToFind, bool leftToRight)
{
RegexOptions options = RegexOptions.None;
if (leftToRight)
{
options |= RegexOptions.RightToLeft;
}
options |= RegexOptions.IgnoreCase;
string pattern = textToFind; // Regex.Escape(textToFind);
return new Regex(pattern, options);
}
/// <summary>
/// 插入新行。
/// </summary>
private void InsertANewLine()
{
var curLine = this.Document.GetLineByOffset(this.SelectionStart);
var text = this.Document.GetText(curLine.Offset, curLine.Length);
if (text.Contains("|") == false && text.Contains("|") == false)
{
this.Document.Insert(curLine.Offset, "\r\n");
this.Select(curLine.Offset, 0);
}
else
{
//如果本行是表格行,仿制一行
StringBuilder sb = new StringBuilder();
for (int i = 0; i < text.Length; i++)
{
char c = text[i];
if (c == '|')
{
sb.Append("|");
}
else if (c == '|')
{
sb.Append("|");
}
else if ((int)c <= 255)
{
sb.Append(" ");
}
else
{
sb.Append(" ");
}
}
this.Document.Insert(curLine.Offset, sb.ToString() + "\r\n");
this.Select(curLine.Offset, 0);
}
}
/// <summary>
/// 附加新行。
/// </summary>
private void AppendANewLine()
{
var curLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
var text = this.Document.GetText(curLine.Offset, curLine.Length);
if (text.Contains("|") == false && text.Contains("|") == false)
{
this.Document.Insert(curLine.EndOffset, "\r\n");
this.Select(curLine.EndOffset + 2, 0);
}
else
{
//如果本行是表格行,仿制一行
StringBuilder sb = new StringBuilder();
for (int i = 0; i < text.Length; i++)
{
char c = text[i];
if (c == '|')
{
sb.Append("|");
}
else if (c == '|')
{
sb.Append("|");
}
else if ((int)c <= 255)
{
sb.Append(" ");
}
else
{
sb.Append(" ");
}
}
this.Document.Insert(curLine.EndOffset, "\r\n" + sb.ToString());
this.Select(curLine.EndOffset + 2, 0);
}
}
/// <summary>
/// 与后一行交换位置。
/// </summary>
internal void SwapWithNextLine()
{
var curStartLine = this.Document.GetLineByOffset(this.SelectionStart);
var curEndLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
var nextLine = curEndLine.NextLine;
if (nextLine != null)
{
this.BeginChange();
var lineSplitter = "\n";
if (this.Document.Text.Contains("\r\n"))
{
lineSplitter = "\r\n";
}
var nextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
this.Document.Remove(nextLine.Offset - lineSplitter.Length, nextLine.Length + lineSplitter.Length);//先删除下一行
var curStartLineOffset = curStartLine.Offset;
this.Document.Insert(curStartLineOffset, nextLineText + lineSplitter);
this.EndChange();
}
}
/// <summary>
/// 与前一行交换位置。
/// </summary>
internal void SwapWithPreviewLine()
{
var curStartLine = this.Document.GetLineByOffset(this.SelectionStart);
var curEndLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
var preLine = curStartLine.PreviousLine;
if (preLine != null)
{
this.BeginChange();
var preStartLineOffset = preLine.Offset;
var startOffset = this.SelectionStart - curStartLine.Offset;
var selLength = this.SelectionLength;
var lineSplitter = "\n";
if (this.Document.Text.Contains("\r\n"))
{
lineSplitter = "\r\n";
}
var preLineText = this.Document.GetText(preLine.Offset, preLine.Length);
this.Document.Insert(curEndLine.EndOffset, lineSplitter);
this.Document.Insert(curEndLine.EndOffset + lineSplitter.Length, preLineText);
this.Document.Remove(preLine.Offset, preLine.Length + lineSplitter.Length);
this.EndChange();
this.Select(preStartLineOffset + startOffset, selLength);
}
}
/// <summary>
/// 寻找前一个标题。
/// </summary>
public void FindPreviewHeader()
{
var curLine = this.Document.GetLineByOffset(this.SelectionStart);
if (curLine == null) return;
int i = curLine.LineNumber - 1;//当前行不算
DocumentLine preHeaderLine = null;
while (i > 0)
{
var preLine = this.Document.GetLineByNumber(i);
if (preLine == null) break;
var text = this.Document.GetText(preLine.Offset, preLine.Length);
if (text == null) break;
if (text.Length == 0)
{
i--;
continue;
}
else
{
Regex reg = new Regex(@"^[  ##]{1,}");
if (reg.Match(text).Success)
{
preHeaderLine = preLine;
break;
}
else
{
i--;
continue;
}
}
}
if (preHeaderLine != null)
{
this.Select(preHeaderLine.Offset, 0);
this.ScrollTo(preHeaderLine.LineNumber, 0);
}
else
{
this.Select(0, 0);
this.ScrollTo(0, 0);
}
}
/// <summary>
/// 寻找后一个标题。
/// </summary>
public void FindNextHeader()
{
var curLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
if (curLine == null) return;
int i = curLine.LineNumber + 1;//当前行不算。
DocumentLine nextHeaderLine = null;
while (i < this.Document.LineCount)
{
var nextLine = this.Document.GetLineByNumber(i);
if (nextLine == null) break;
var text = this.Document.GetText(nextLine.Offset, nextLine.Length);
if (text == null) break;
if (text.Length == 0)
{
i++;
continue;
}
else
{
Regex reg = new Regex(@"^[  ##]{1,}");
if (reg.Match(text).Success)
{
nextHeaderLine = nextLine;
break;
}
else
{
i++;
continue;
}
}
}
if (nextHeaderLine != null)
{
this.Select(nextHeaderLine.Offset, 0);
this.ScrollTo(nextHeaderLine.LineNumber, 0);
}
else
{
this.Select(this.Document.Lines[this.Document.Lines.Count - 1].EndOffset, 0);
this.ScrollToLine(this.Document.Lines.Count - 1);
}
}
/// <summary>
/// 使当前文本行在“普通文本”与“无序列表项”之间切换。
/// </summary>
/// <param name="convertToListItem">作为无序列表项。</param>
public void SwitchListMark(bool convertToListItem)
{
var startLine = this.Document.GetLineByOffset(this.SelectionStart);
var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
this.BeginChange();
for (int i = endLine.LineNumber - 1; i >= startLine.LineNumber - 1; i--)
{
var line = this.Document.Lines[i];
var lineText = this.Document.GetText(line.Offset, line.Length);
var lineContent = lineText.Trim(new char[] { ' ', ' ', '\t' });
if (string.IsNullOrWhiteSpace(lineContent)) continue;
if (convertToListItem == false)
{
//取消行首的无序列表标记。
Regex reg = new Regex(@"^[  ]{0,3}[\+\--\*][  \t]{0,}");
var match = reg.Match(lineText);
if (match != null && match.Success)
{
this.Document.Remove(line.Offset, match.Length);
}
}
else
{
//在行首添加星号,作为无序列表
this.Document.Insert(line.Offset, "+ ");
}
}
this.EndChange();
}
/// <summary>
/// 在选定文本行前加“引用块标记文本(> )”
/// </summary>
internal void AddBlockQuoterMarksToSelectedLines()
{
var startLine = this.Document.GetLineByOffset(this.SelectionStart);
var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
var startReplaceOffset = startLine.Offset;
var endReplaceOffset = endLine.EndOffset;
StringBuilder sb = new StringBuilder();
for (int i = startLine.LineNumber; i <= endLine.LineNumber; i++)
{
var line = this.Document.GetLineByNumber(i);
var lineText = this.Document.GetText(line.Offset, line.Length);
if (string.IsNullOrWhiteSpace(lineText))
{
sb.Append("\r\n");
continue;
}
sb.Append("> ");
sb.Append(lineText);
sb.Append("\r\n");
}
this.Document.Replace(startReplaceOffset, endReplaceOffset - startReplaceOffset + 1, sb.ToString());
}
/// <summary>
/// 使当前文本行在六级标题之间切换。
/// </summary>
/// <param name="newLevel">标题层级。</param>
internal void SwitchTitleLevel(int newLevel)
{
var startLine = this.Document.GetLineByOffset(this.SelectionStart);
var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
for (int i = startLine.LineNumber; i <= endLine.LineNumber; i++)
{
var line = this.Document.GetLineByNumber(i);
var lineText = this.Document.GetText(line.Offset, line.Length);
if (string.IsNullOrWhiteSpace(lineText))
{
var header = GetTitleHeader(newLevel);
this.Document.Replace(line.Offset, 0, GetTitleHeader(newLevel));
this.Select(line.Offset + header.Length, 0);
return;
}
var oldSelOffset = this.SelectionStart;
var oldSelLength = this.SelectionLength;
Regex reg1 = new Regex(@"^[  ]{4,}");
var match1 = reg1.Match(lineText);
if (match1 != null && match1.Success) return;//不考虑代码块
Regex reg = new Regex(@"^[  ##]{1,}");
var match = reg.Match(lineText);
if (match != null && match.Success)
{
var level = GetTitleLevel(match.Value);
if (level == newLevel)
{
this.Document.Replace(line.Offset, match.Length, "");
this.Select(oldSelOffset - match.Length, oldSelLength);
}
else
{
var newHeader = GetTitleHeader(newLevel);
this.Document.Replace(line.Offset, match.Length, newHeader);
var newSelOffset = oldSelOffset + newHeader.Length - match.Length;
this.Select(newSelOffset, oldSelLength);
}
}
else
{
var newHeader = GetTitleHeader(newLevel);
this.Document.Insert(line.Offset, GetTitleHeader(newLevel));
var newSelOffset = oldSelOffset + newHeader.Length - match.Length;
this.Select(newSelOffset, oldSelLength);
}
}
}
/// <summary>
/// 取文本行的标题级别。
/// </summary>
/// <param name="lineText">文本行。</param>
private int GetTitleLevel(string lineText)
{
if (string.IsNullOrWhiteSpace(lineText)) return 0;
int level = 0;
foreach (char c in lineText)
{
switch (c)
{
case ' ':
case ' ': continue;
case '#':
case '#':
{
level++;
continue;
}
default: continue;
}
}
return level;
}
/// <summary>
/// 根据层级生成标题的头文本(#字符串)。。
/// </summary>
/// <param name="level">标题层级。</param>
private string GetTitleHeader(int level)
{
switch (level)
{
case 1: return "#";
case 2: return "##";
case 3: return "###";
case 4: return "####";
case 5: return "#####";
case 6: return "######";
default: return "";
}
}
/// <summary>
/// 编辑器获得焦点时自动打开输入法。
/// </summary>
void MarkDownEditorBase_GotFocus(object sender, RoutedEventArgs e)
{
Globals.SwitchInputMethod(true);
}
/// <summary>
/// 查找面板是否处于开启状态。
/// * AvalonEdit的查找面板有 Bug,经常无法输入中文。
/// 所以实际上没有用它,此属性已无用处。
/// </summary>
public bool IsSearchPanelOpened
{
get
{
if (searchPanel == null) return false;
if (searchPanel.IsClosed) return false;
return true;
}
}
/// <summary>
/// [已废弃]AvalonEdit 实现的查找面板,出现在编辑区右上角。有Bug,常无法输入中文。
/// </summary>
private SearchPanel searchPanel;
/// <summary>
/// [已废弃]关闭 AvalonEdit 自己实现的搜索面板。
/// </summary>
public void CloseSearchPanel()
{
searchPanel.Close();
}
/// <summary>
/// [已废弃]查找文本。
/// </summary>
public void Find()
{
searchPanel.SearchPattern = this.TextArea.Selection.GetText();
if (searchPanel.IsClosed) searchPanel.Open();
searchPanel.Reactivate();
}
/// <summary>
/// [已废弃]逆向查找。
/// </summary>
public void FindPreview()
{
searchPanel.FindPrevious();
}
/// <summary>
/// [已废弃]正向查找。
/// </summary>
public void FindNext()
{
searchPanel.FindNext();
}
/// <summary>
/// 文档内容变化时更改 IsModified 属性的值。
/// </summary>
void MarkdownEditorBase_TextChanged(object sender, EventArgs e)
{
this.IsModified = true;//勉强解决。
}
/// <summary>
/// [已废弃]自动将输入的某些方括号转换为统一格式。
/// </summary>
void MarkdownEditorBase_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
//这个转换会造成一些意外的错误——例如添加一个图片链接时需要的不是【】而是[]。
//switch (e.Text)
//{
// case "[":
// case "[":
// case "〖":
// {
// this.Document.Insert(this.CaretOffset, "【");
// e.Handled = true;
// return;
// }
// case "]":
// case "]":
// case "〗":
// {
// this.Document.Insert(this.CaretOffset, "】");
// e.Handled = true;
// return;
// }
//}
}
#region 自动完成
/// <summary>
/// 判断此文本是否与试题相关。
/// </summary>
/// <param name="lineText">要检查的文本行。</param>
public static bool IsStartWithTestPaperKeyWord(string lineText)
{
if (string.IsNullOrEmpty(lineText)) return false;
if (lineText.StartsWith("  试题>>")) return true;
if (lineText.StartsWith("  答案>>")) return true;
if (lineText.StartsWith("  错项>>")) return true;
if (lineText.StartsWith("  解析>>")) return true;
if (lineText.StartsWith("  试题>>")) return true;
if (lineText.StartsWith("<<材料>>")) return true;
if (lineText.StartsWith("<<出处>>")) return true;
if (lineText.StartsWith("<<问题>>")) return true;
if (lineText.StartsWith("<<<信息>")) return true;
if (lineText.StartsWith(" 类型>>")) return true;
if (lineText.StartsWith(" 标题>>")) return true;
if (lineText.StartsWith(" 日期>>")) return true;
if (lineText.StartsWith(" 辑录>>")) return true;
if (lineText.StartsWith(" 作者>>")) return true;
if (lineText.StartsWith(" 电邮>>")) return true;
if (lineText.StartsWith(" 备注>>")) return true;
if (lineText.StartsWith(" 辑录>>")) return true;
if (lineText.StartsWith("<信息>>>")) return true;
return false;
}
private CompletionWindow completionWindow;
/// <summary>
/// 自动完成窗口。
/// </summary>
public CompletionWindow CompletionWindow
{
get
{
return completionWindow;
}
}
/// <summary>
/// 关闭自动完成窗口。
/// </summary>
public void InvalidateCompletionWindow()
{
if (this.completionWindow == null) return;
this.completionWindow.Close();
this.completionWindow = null;
}
/// <summary>
/// 用于在切换工作区时、工作区管理器发生变化时重新载入关于各 Markdown 文件的自动完成列表。
/// </summary>
/// <param name="workspaceTreeView">应传入工作区管理器</param>
public void LoadLinkAutoCompletionItems(TreeView workspaceTreeView, bool isEnableAutoComplete)
{
if (this.CompletionWindow == null) return;
IList<ICompletionData> dataList = CompletionWindow.CompletionList.CompletionData;
for (int i = dataList.Count - 1; i >= 0; i--)
{
var item = dataList[i] as CustomCompletionData;
if (item == null) continue;
if (item.Type == CompleteItemType.MarkdownFileLink ||
item.Type == CompleteItemType.MetaMarkdownFileLink ||
item.Type == CompleteItemType.ImageResourceLink) dataList.RemoveAt(i);
}//先清除
if (workspaceTreeView == null || workspaceTreeView.Items.Count <= 0 || isEnableAutoComplete == false) return;
var wtvi = workspaceTreeView.Items[0] as WorkspaceTreeViewItem;
if (wtvi != null)
{
AddLinksAutoCompleteItem(wtvi, ref dataList);
}
}
/// <summary>
/// 计算两个文档间的相对位置,即要能从当前文档找到目标链接文档。
/// </summary>
/// <param name="thisDocPath">要插入链接的文档的完全路径。</param>
/// <param name="linkDestDocPath">要被链接的文档的完全路径。</param>
/// <param name="withHtmlSuffix">true,返回 .html 后缀;false 返回 .md 后缀。</param>
/// <returns></returns>
public static string GetRelativePath(string thisDocPath, string linkDestDocPath, bool withHtmlSuffix)
{
try
{
var thisDocInfo = new FileInfo(thisDocPath);
var linkDestDocInfo = new FileInfo(linkDestDocPath);
//用 ~ 代替 _ 风险太高了!!
//if (linkDestDocInfo.Name.StartsWith("_"))
//{
// linkDestDocPath = linkDestDocInfo.Directory.FullName + "\\~" + linkDestDocInfo.Name.Substring(1);
//}
//基本思路:
//当前文档路径: C:\abc\def\ghi\jkl\lmn.md
//要链接到的文件路径: C:\abc\def\mnl\opq\xyz.md
//办法是先找到两者开始分叉的层级,然后算出当前文档得向上移动几层。
var parentInfo = thisDocInfo.Directory;
var root = parentInfo.Root;
var prefix = "";
while (parentInfo != null)
{
if (false == linkDestDocPath.StartsWith(parentInfo.FullName, StringComparison.CurrentCultureIgnoreCase))
{
prefix += "../";
}
else
{
//prefix += "../";//这行很奇怪,之前控制台应用程序调试时没问题,但转移到这里却会嫌多。
var length = parentInfo.FullName.Length;
if (parentInfo.FullName.EndsWith("\\") == false && parentInfo.FullName.EndsWith("/") == false)
length++;
var tail = linkDestDocPath.Substring(length); //加的1是指 \
var result = (prefix + tail).Replace("\\", "/");
if (withHtmlSuffix && result.EndsWith(".md", StringComparison.CurrentCultureIgnoreCase))
{
result = result.Substring(0, result.Length - 3) + ".html";
}
return result;
}
parentInfo = parentInfo.Parent;
}
return null;
}
catch
{
return null;
}
}
/// <summary>
/// 这里的 Link 包括普通 Markdwon 文件、目录元文件、图像资源文件。
/// </summary>
/// <param name="item"></param>
/// <param name="dataList"></param>
private void AddLinksAutoCompleteItem(WorkspaceTreeViewItem item, ref IList<ICompletionData> dataList)
{
if (item == null || dataList == null) return;
var editor = this.MasterEditor;
if (editor == null) return;
var thisFullPath = editor.FullFilePath;
if (string.IsNullOrWhiteSpace(thisFullPath)) return;
var title = item.Title;
if (item.IsMetaDirectoryPath)
{
//添加目录元文件对应的Html文件的路径。
var metaFilePath = item.MetaHtmlFilePath;
if (metaFilePath == null) return;
var relativePath = GetRelativePath(thisFullPath, metaFilePath, true);
dataList.Add(new CustomCompletionData(title, $"链接到:{title}",
$"[{title}]({relativePath} \"{title}\")")
{
Type = CompleteItemType.MetaMarkdownFileLink,
Image = linkIcon,
});
}
else if (item.IsMarkdownFilePath)
{
//添加Markdown文件对应的Html文件的路径。
var relativePath = GetRelativePath(thisFullPath, item.FullPath, true);
dataList.Add(new CustomCompletionData(title, $"链接到:{title}",
$"[{title}]({relativePath} \"{title}\")")
{
Type = CompleteItemType.MarkdownFileLink,
Image = linkIcon,
});
}
else if (item.IsImageFileExist)
{
//添加Markdown文件对应的Html文件的路径。
var relativePath = GetRelativePath(thisFullPath, item.FullPath, true);
dataList.Add(new CustomCompletionData(title, $"链接到图像:{title}",
$"[{title}]({relativePath} \"{title}\")")
{
Type = CompleteItemType.ImageResourceLink,
Image = linkIcon,
});
}
if (item.Items.Count > 0)
{
foreach (var si in item.Items)
{
var subItem = si as WorkspaceTreeViewItem;
if (subItem == null) continue;
AddLinksAutoCompleteItem(subItem, ref dataList);
}
}
}
/// <summary>
/// 向自动完成窗口添加可用的条目。
/// </summary>
/// <param name="entry">自动完成提示条目。</param>
public void AddEntryToCompletionWindow(EnToChEntry entry)
{
if (this.CompletionWindow == null) return;
IList<ICompletionData> dataList = CompletionWindow.CompletionList.CompletionData;
dataList.Add(new CustomCompletionData(entry.Chinese,
$"{entry.Chinese}{entry.Alpha}{entry.English})" + (entry.Comment == string.Empty ? null : entry.Comment),
$"{entry.Chinese}{entry.Alpha}{entry.English})"));
dataList.Add(new CustomCompletionData(entry.English,
$"{entry.Chinese}{entry.Alpha}{entry.English})" + (entry.Comment == string.Empty ? null : entry.Comment),
$"{entry.Chinese}{entry.Alpha}{entry.English})"));
}
/// <summary>
/// 在向活动编辑器中输入文本时,根据情况弹出自动提示。
/// </summary>
void TextArea_TextEntering(object sender, TextCompositionEventArgs e)
{
if (IsAutoCompletionEnabled == false) return;
if ("\r\n  `~!@#$%^&*()_+-=[]\\{}|;\':\",.<>/?~·!@#¥%……&*(){}|【】、:";',./<>?".Contains(e.Text)) return;//仅只换行,不需要提示
var line = this.Document.GetLineByOffset(this.CaretOffset);
if (line == null) return;
if (completionWindow == null)
{
completionWindow = new CompletionWindow(this.TextArea)
{
MinHeight = 34,
MinWidth = 100,
};
}
IList<ICompletionData> data = CompletionWindow.CompletionList.CompletionData;
//清除自动完成列表并重新添加其中的项目。
data.Clear();
if (IsExamEnabled)//不需要这个,强制换行即可。 && this.CaretOffset - line.Offset <= 3)
{
#region 添加试题需要的提示项
var st = new CustomCompletionData("试题>>", "每个试题都应以此开头。", "\r\n  试题>>") { Image = examIcon, };
var da = new CustomCompletionData("答案>>", "每个答案都应以此为开头。", "\r\n  答案>>") { Image = examIcon, };//缩进不采用Tab是因为易乱。
var cx = new CustomCompletionData("错项>>", "每个备选的错项都应以此为开头。", "\r\n  错项>>") { Image = examIcon, };
var jx = new CustomCompletionData("解析>>", "每个解析条目都应以此开头。", "\r\n  解析>>") { Image = examIcon, };
var cl = new CustomCompletionData("<<材料>>", "主观题每则材料均应以此开头。", "\r\n<<材料>>") { Image = examIcon, };
var cc = new CustomCompletionData("<<出处>>", "主观题每则材料的出处。", "\r\n<<出处>>") { Image = examIcon, };
var wt = new CustomCompletionData("<<问题>>", "问题集中可包含多个问题。", "\r\n<<问题>>") { Image = examIcon, };
var wb = new CustomCompletionData("==〓==", "最后一道试题的结尾添加此符号才能折叠,其它试题不需要添加。", "〓〓〓〓〓〓") { Image = examIcon, };//这样设计是保证全角等于号与半角等于号都有用。
var xx = new CustomCompletionData("信息><信息", "每个文档开头应以指明文档相关信息,如章节、创建者、创建时间等等。",
"<<<信息>\r\n" +
" 类型>><<类型\r\n" +
" 标题>>请输入文档标题<<标题\r\n" +
" 日期>><<日期\r\n" +
" 辑录>><<辑录\r\n" +
" 电邮>><<电邮\r\n" +
" 备注>><<备注\r\n" +
"<信息>>>\r\n\r\n")
{ Image = documentInfoIcon, };
//st.Completed += st_Completed;
//da.Completed += Item_Completed;
//cx.Completed += Item_Completed;
//jx.Completed += Item_Completed;
//cl.Completed += Item_Completed;
//cc.Completed += Item_Completed;
//wt.Completed += Item_Completed;
//wb.Completed += Item_Completed;
xx.Completed += xx_Completed;
data.Add(st);
data.Add(da);
data.Add(cx);
data.Add(jx);
data.Add(cl);
data.Add(cc);
data.Add(wt);
//data.Add(new MyCompletionData("【方括弧对】", "试题中应包含一个或多个填空项目,这些填空项用一对方括弧包围。","【】"));//效果不好。
data.Add(wb);
data.Add(xx);
#endregion
}
#region 添加水平线相关的几个项
var spx1 = new CustomCompletionData("水平线1", "水平线 01", "\r\n<hr class=\"hr1\"/>\r\n") { Image = horizontalLineIcon, };
var spx2 = new CustomCompletionData("水平线2", "水平线 02", "\r\n<hr class=\"hr2\"/>\r\n") { Image = horizontalLineIcon, };
var spx3 = new CustomCompletionData("水平线3", "水平线 03", "\r\n<hr class=\"hr3\"/>\r\n") { Image = horizontalLineIcon, };
var spx4 = new CustomCompletionData("水平线4", "水平线 04", "\r\n<hr class=\"hr4\"/>\r\n") { Image = horizontalLineIcon, };
var spx5 = new CustomCompletionData("水平线5", "水平线 05", "\r\n<hr class=\"hr5\"/>\r\n") { Image = horizontalLineIcon, };
data.Add(spx1);
data.Add(spx2);
data.Add(spx3);
data.Add(spx4);
data.Add(spx5);
#endregion
#region 添加指定Markdown元素
var anchor = new CustomCompletionData("锚", "锚", "\r\n[](@请输入锚的标识符)\r\n") { Image = anchorIcon, };
anchor.Completed += Anchor_Completed;
data.Add(anchor);
var region = new CustomCompletionData("折叠区", "自定义折叠区域", "\r\n\r\nregion { 请输入折叠区标题\r\n\r\n\r\n\r\n}region \r\n\r\n") { Image = collapseAreaIcon, };
region.Completed += Region_Completed;
data.Add(region);
var link = new CustomCompletionData("链接符", "适用于链接工作区以外的地址", "[请输入链接名称]()") { Image = emptyLinkIcon, };
link.Completed += Link_Completed;
data.Add(link);
#endregion
#region 添加英译中词典
if (IsEnToChineseDictEnabled)
{
//new CustomCompletionData("锚", "锚", "\r\n[](@请输入锚的标识符)\r\n");
foreach (var entry in Globals.MainWindow.Dictionary)
{
data.Add(new CustomCompletionData(entry.Chinese,
$"{entry.Chinese}{entry.Alpha}{entry.English})" + (entry.Comment == string.Empty ? null : entry.Comment),
$"{entry.Chinese}{entry.Alpha}{entry.English})")
{ Image = dictionEntryIcon, });
data.Add(new CustomCompletionData(entry.English,
$"{entry.Chinese}{entry.Alpha}{entry.English})" + (entry.Comment == string.Empty ? null : entry.Comment),
$"{entry.Chinese}{entry.Alpha}{entry.English})")
{ Image = dictionEntryIcon, });
}
}
#endregion
#region 添加对工作区中各文件的链接
LoadLinkAutoCompletionItems(Globals.MainWindow.tvWorkDirectory, Globals.MainWindow.IsAutoCompletionEnabled);
#endregion
CompletionWindow.Show();
CompletionWindow.Closed += delegate
{
completionWindow = null;
};
}
private static ImageSource documentInfoIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/document-info.png"));
private static ImageSource examIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/options.png"));
private static ImageSource horizontalLineIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/insert-horizontal-rule.png"));
private static ImageSource anchorIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/link.png"));
private static ImageSource collapseAreaIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/collapseArea.png"));
private static ImageSource emptyLinkIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/link-edit.png"));
private static ImageSource dictionEntryIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/book-key.png"));
private static ImageSource linkIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/document-link.png"));
/// <summary>
/// 自动完成链接文本标记。
/// </summary>
private void Link_Completed(object sender, EventArgs e)
{
this.Select(this.SelectionStart - 10, 7);
}
/// <summary>
/// 自动完成“region”文本标记。
/// </summary>
private void Region_Completed(object sender, EventArgs e)
{
this.Select(this.SelectionStart - 28, 8);
}
/// <summary>
/// 自动完成锚文本标记。
/// </summary>
private void Anchor_Completed(object sender, EventArgs e)
{
this.Select(this.SelectionStart - 11, 8);
}
/// <summary>
/// 自动完成“信息”文本标记。
/// </summary>
void xx_Completed(object sender, EventArgs e)
{
this.Select(this.SelectionStart - 67, 7);
}
/// <summary>
/// 自动完成(上屏)。
/// </summary>
void TextArea_TextEntered(object sender, TextCompositionEventArgs e)
{
if (IsAutoCompletionEnabled == false) return;
if (CompletionWindow == null) return;
if (e.Text.Length > 0)
{
if (CompletionWindow.CompletionList.SelectedItem == null)
{
CompletionWindow.Close();
completionWindow = null;
return;
}
if (!char.IsLetterOrDigit(e.Text[0]))
{
if (e.Text[0] != '=' && e.Text[0] != '=')//不屏蔽等号会导致一闪而过,虽然方便,但会难以理解。
{
// Whenever a non-letter is typed while the completion window is open,
// insert the currently selected element.
CompletionWindow.CompletionList.RequestInsertion(e);
}
}
}
// Do not set e.Handled=true.
// We still want to insert the character that was typed.
}
#endregion
#region 代码折叠
void foldingUpdateTimer_Tick(object sender, EventArgs e)
{
if (foldingStrategy != null)
{
foldingStrategy.UpdateFoldings(FoldingManager, this.Document);
}
}
DispatcherTimer foldingUpdateTimer = new DispatcherTimer();
/// <summary>
/// 提供这个属性是为了便于在选项卡“不在最前”时停止折叠计算,减少消耗。
/// </summary>
public DispatcherTimer FoldingUpdateTimer { get { return this.foldingUpdateTimer; } }
public CustomFoldingManager FoldingManager
{
get { return foldingManager; }
}
CustomFoldingManager foldingManager;
AbstractFoldingStrategy foldingStrategy;
public AbstractFoldingStrategy FoldingStrategy
{
get { return foldingStrategy; }
}
#endregion
// <summary>
// 以选择区起始位置为准,选择所在的整行文本。
// </summary>
internal void SelectLine()
{
if (this.SelectionStart >= 0 && this.SelectionStart <= this.Document.TextLength)
{
var line = this.Document.GetLineByOffset(this.SelectionStart);
if (line != null)
{
this.Select(line.Offset, line.EndOffset - line.Offset);
}
}
}
#region 交换上下相邻行,有更好的方案,此方案因只支持单行,被废弃
//internal void SwapToPreviewLine()
//{
// if (this.SelectionLength > 0) return;
// var line = this.Document.GetLineByOffset(this.SelectionStart);
// if (line.LineNumber <= 1) return;
// int caretOffset = this.SelectionStart - line.Offset;
// var previewLine = this.Document.GetLineByNumber(line.LineNumber - 1);
// string previewText = this.Document.GetText(previewLine.Offset, previewLine.Length);
// string thisText = this.Document.GetText(line.Offset, line.Length);
// this.Document.Remove(previewLine.Offset, previewLine.Length);
// this.Document.Remove(line.Offset, line.Length);
// int previewOffset = this.Document.GetLineByNumber(previewLine.LineNumber).Offset;
// this.Document.Insert(previewOffset, thisText);
// int thisOffset = this.Document.GetLineByNumber(line.LineNumber).Offset;
// this.Document.Insert(thisOffset, previewText);
// this.Select(previewOffset + caretOffset, 0);
//}
//internal void SwapToNextLine()
//{
// if (this.SelectionLength > 0) return;
// var line = this.Document.GetLineByOffset(this.SelectionStart);
// if (line.LineNumber >= this.Document.LineCount) return;
// int caretOffset = this.SelectionStart - line.Offset;
// var nextLine = this.Document.GetLineByNumber(line.LineNumber + 1);
// string nextText = this.Document.GetText(nextLine.Offset, nextLine.Length);
// string thisText = this.Document.GetText(line.Offset, line.Length);
// this.Document.Remove(nextLine.Offset, nextLine.Length);
// this.Document.Remove(line.Offset, line.Length);
// int thisOffset = this.Document.GetLineByNumber(line.LineNumber).Offset;
// this.Document.Insert(thisOffset, nextText);
// int nextOffset = this.Document.GetLineByNumber(nextLine.LineNumber).Offset;
// this.Document.Insert(nextOffset, thisText);
// this.Select(nextOffset + caretOffset, 0);
//}
#endregion
/// <summary>
/// 用一对反引号包围选定的文本片段(不能跨行)。
/// </summary>
internal void WrapTextWithAntiQuotes()
{
var startLine = this.Document.GetLineByOffset(this.SelectionStart);
var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
if (startLine.LineNumber != endLine.LineNumber) return;
if (this.SelectionLength > 0)
{
var oldSelLength = this.SelectionLength;
this.SelectedText = "`" + this.SelectedText + "`";
this.Select(this.SelectionStart + 1, oldSelLength);
}
else
{
this.SelectedText = "`" + this.SelectedText + "`";
this.Select(this.SelectionStart + 1, 0);
}
}
/// <summary>
/// 用一对引号包围选定的文本片段(不能跨行)。
/// </summary>
internal void WrapTextWithQuotes(bool isShift)
{
var startLine = this.Document.GetLineByOffset(this.SelectionStart);
var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
if (startLine.LineNumber != endLine.LineNumber) return;
if (this.SelectionLength > 0)
{
var oldSelLength = this.SelectionLength;
if (isShift)
{
this.SelectedText = "“" + this.SelectedText + "”";
}
else
{
this.SelectedText = "‘" + this.SelectedText + "’";
}
this.Select(this.SelectionStart + 1, oldSelLength);
}
else
{
if (isShift)
{
this.SelectedText = "“" + this.SelectedText + "”";
}
else
{
this.SelectedText = "‘" + this.SelectedText + "’";
}
this.Select(this.SelectionStart + 1, 0);
}
}
/// <summary>
/// 用方括号对【】包围选定的文本片段(不能跨行)。
/// </summary>
internal void WrapTextWithSquareQuotes()
{
var startLine = this.Document.GetLineByOffset(this.SelectionStart);
var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
if (startLine.LineNumber != endLine.LineNumber) return;
if (this.SelectionLength > 0)
{
this.SelectedText = "【" + this.SelectedText + "】";
}
else
{
this.SelectedText = "【" + this.SelectedText + "】";
this.Select(this.SelectionStart + 1, 0);
}
}
/// <summary>
/// 尝试根据试题文本判断其中有几个填空项,然后根据这些填空项自动插入一些“答案>>”“错项>>”“解析>>”之类的标签。
/// </summary>
public void InsertChoiceQuestionTags()
{
//选择区域所在的行都选中
var startLine = this.Document.GetLineByOffset(this.SelectionStart);
var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
this.Select(startLine.Offset, endLine.EndOffset - startLine.Offset);
//要求选中题干文本(全部)
var text = this.SelectedText.Replace("\r\n", "").Replace("\t", "").Replace(" ", "").Replace(" ", "");
if (text.StartsWith("试题>>") == false)
{
LMessageBox.Show("  请选中要生成标签的试题文本,这些文本中应至少有一个用方括弧对标记出来的填充项。",
Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
int fillblankCount = 0;
StringBuilder errorMsg = new StringBuilder();
if (QuestionValidateManager.ValidatePairOfBrackets(text, ref fillblankCount, true, errorMsg) == false)
{
LMessageBox.Show("  选中的文本格式存在问题,无法自动生成标签集。错误信息如下:\r\n" + errorMsg.ToString(),
Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
char[] splitterLeft = new char[1];
splitterLeft[0] = '【';
char[] splitterRight = new char[1];
splitterRight[0] = '】';
string[] arrayLeft = text.Split(splitterLeft, StringSplitOptions.RemoveEmptyEntries);
List<string> answers = new List<string>();
foreach (string s in arrayLeft)
{
if (s.Contains("】") == false) continue;
int index = s.IndexOf('】');
if (index < 0) continue;
string answer = s.Substring(0, index);
answers.Add(answer);
}
StringBuilder tagsText = new StringBuilder();
int fstAnswerLength = -1;
tagsText.Append("\r\n\t\t\t\r\n");
foreach (var s in answers)
{
if (fstAnswerLength < 0)
{
fstAnswerLength = s.Length;
}
tagsText.Append("  答案>>" + s + "\r\n");
tagsText.Append("  解析>>\r\n\t\t\t\r\n");
//默认三个错项。
tagsText.Append("  错项>>\r\n");
tagsText.Append("  解析>>\r\n\t\t\t\r\n");
tagsText.Append("  错项>>\r\n");
tagsText.Append("  解析>>\r\n\t\t\t\r\n");
tagsText.Append("  错项>>\r\n");
tagsText.Append("  解析>>\r\n\t\t\t\r\n");
}
tagsText.Append("〓〓〓〓〓〓\r\n\r\n");//空一行,顶格,方便立即开始编辑新试题
var line = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength - 1);//这个长度肯定大于0。
int oldEndOffset = line.EndOffset;
this.Document.Insert(line.EndOffset, tagsText.ToString());
this.Select(oldEndOffset + fstAnswerLength + 34, 0);
}
/// <summary>
/// 添加判断题标记。
/// </summary>
/// <param name="judgeValue">判断题答案标记。</param>
internal void InsertJudgeQuestionTags(string judgeValue = "正确")
{
//选择区域所在的行都选中
var startLine = this.Document.GetLineByOffset(this.SelectionStart);
var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
this.Select(startLine.Offset, endLine.EndOffset - startLine.Offset);
//要求选中题干文本(全部)
var text = this.SelectedText.Replace("\r\n", "").Replace("\t", "").Replace(" ", "").Replace(" ", "");
if (text.StartsWith("试题>>") == false)
{
LMessageBox.Show("  请选中要生成标签的试题文本,这些文本中不应包括方括弧【和】,因为方括弧对包括的文本会被当成填充项。",
Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
if (text.Contains("【") || text.Contains("】"))
{
LMessageBox.Show("  选中的文本格式存在问题,无法自动生成判断题标签集。错误信息如下:\r\n" +
"判断题题干中不应包括方括号【和】,被方括号包围的文本会被当成填空项来处理。",
Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
//在末尾加上【正确】或【错误】。
text += "【" + judgeValue + "】";
char[] splitterLeft = new char[1];
splitterLeft[0] = '【';
char[] splitterRight = new char[1];
splitterRight[0] = '】';
string[] arrayLeft = text.Split(splitterLeft, StringSplitOptions.RemoveEmptyEntries);
List<string> answers = new List<string>();
foreach (string s in arrayLeft)
{
if (s.Contains("】") == false) continue;
int index = s.IndexOf('】');
if (index < 0) continue;
string answer = s.Substring(0, index);
answers.Add(answer);
}
StringBuilder tagsText = new StringBuilder();
int fstAnswerLength = -1;
tagsText.Append("【" + judgeValue + "】\r\n\t\t\t\r\n");
foreach (var s in answers)
{
if (fstAnswerLength < 0)
{
fstAnswerLength = s.Length;
}
tagsText.Append("  答案>>" + judgeValue + "\r\n");
tagsText.Append("  解析>>\r\n\t\t\t\r\n");
//默认三个错项。
tagsText.Append("  错项>>" + (judgeValue == "正确" ? "错误" : "正确") + "\r\n");
tagsText.Append("  解析>>\r\n\t\t\t\r\n");
}
tagsText.Append("〓〓〓〓〓〓\r\n\r\n");//空一行,顶格,方便立即开始编辑新试题
var line = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength - 1);//这个长度肯定大于0。
int oldEndOffset = line.EndOffset;
this.Document.Insert(line.EndOffset, tagsText.ToString());
this.Select(oldEndOffset + fstAnswerLength + 25, 0);
}
/// <summary>
/// 格式化 Markdown 文本。
/// </summary>
public void Format()
{
var selStart = this.SelectionStart;
var selLength = this.SelectionLength;
this.Document.Text = CustomMarkdownSupport.FormatMarkdownText(this.Text);
//如果开启了演讲者模式,格式化后自动折叠特殊折叠块。
if (Globals.MainWindow.PerspectiveMode == Perspective.EditingAndPresentation)
{
this.FoldingManager.FoldSpecialFoldings = true;
this.FoldingStrategy.UpdateFoldings(this.FoldingManager, this.Document);
this.FoldingManager.FoldSpecialFoldings = false;
}
if (selStart < this.Document.TextLength)
{
if (selStart + selLength < this.Document.TextLength)
{
this.Select(selStart, selLength);
this.ScrollToLine(this.Document.GetLineByOffset(this.SelectionStart).LineNumber);
}
else
{
this.Select(selStart, 0);
this.ScrollToLine(this.Document.GetLineByOffset(this.SelectionStart).LineNumber);
}
}
else
{
this.Select(this.Document.TextLength, 0);
this.ScrollToLine(this.Document.GetLineByOffset(this.SelectionStart).LineNumber);
}
}
public void FormatTextTable()
{
var curLine = CurrentDocumentLine();
var curLineText = this.Document.GetText(curLine.Offset, curLine.Length);
if (CustomMarkdownSupport.IsTreeListTextLine(curLineText))
{
//如果当前行是树型列表行,则应格式化当前行前、后可能存在的树型列表行。
List<TreeListTextLine> allLines = new List<TreeListTextLine>();
//向前找
List<TreeListTextLine> preLines = new List<TreeListTextLine>();
var preLine = curLine.PreviousLine;
while (preLine != null)
{
var preLineText = this.Document.GetText(preLine.Offset, preLine.Length);
if (CustomMarkdownSupport.IsTreeListTextLine(preLineText) == false) break;
string preHeader, preTail; int preLevel;
if (IsTreeListLine(preLineText, out preHeader, out preTail, out preLevel))
{
preLines.Insert(0, new TreeListTextLine()
{
Line = preLine,
OldText = preLineText,
NewText = "",
//HeaderTextArray = preHeader.ToCharArray(),
TailText = preTail,
OldLevel = preLevel,
NewLevel = preLevel, //之前的行层级不变
});
}
preLine = preLine.PreviousLine;
}
allLines.AddRange(preLines);
string curHeader, curTail; int curLevel;
if (IsTreeListLine(curLineText, out curHeader, out curTail, out curLevel))
{
allLines.Add(new TreeListTextLine()
{
Line = curLine,
OldText = curLineText,
TailText = curTail,
OldLevel = curLevel,
NewLevel = curLevel,
NewText = "",
});
}
//向后找
List<TreeListTextLine> nextLines = new List<TreeListTextLine>();
var nextLine = curLine.NextLine;
var preLevelOfNextLines = curLevel;
while (nextLine != null)
{
var nextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
if (CustomMarkdownSupport.IsTreeListTextLine(nextLineText) == false) break;
string nextHeader, nextTail; int nextLevel;
if (IsTreeListLine(nextLineText, out nextHeader, out nextTail, out nextLevel))
{
var nextTreeListTextLine = new TreeListTextLine()
{
Line = nextLine,
//HeaderTextArray = nextHeader.ToCharArray(),
NewText = "",
OldText = nextLineText,
TailText = nextTail,
OldLevel = nextLevel,
};
if (nextTreeListTextLine.OldLevel > preLevelOfNextLines + 1)
{
nextTreeListTextLine.NewLevel = preLevelOfNextLines + 1;
}
else nextTreeListTextLine.NewLevel = nextTreeListTextLine.OldLevel;//不变
nextLines.Add(nextTreeListTextLine);
nextLine = nextLine.NextLine;
}
}
allLines.AddRange(nextLines);
var oldSelStart = this.SelectionStart;
FormatAllTreeLines(allLines, null);
if (oldSelStart < this.Document.TextLength)
{
this.Select(oldSelStart, 0);
}
return;
}
else
{
if (curLineText.Contains("|") || curLineText.Contains("|"))
{
//这种情况下格式化为二维文字表。
Format2DTextTable();
}
else
{
//如果当前行是按Shift+Backspace取消的,则要更新当前行上、下可能存在的树型文字表
//这种情况要分别格式化成两个树型文字表。
//向前找
List<TreeListTextLine> preLines = new List<TreeListTextLine>();
var preLine = curLine.PreviousLine;
while (preLine != null)
{
var preLineText = this.Document.GetText(preLine.Offset, preLine.Length);
if (CustomMarkdownSupport.IsTreeListTextLine(preLineText) == false) break;
string preHeader, preTail; int preLevel;
if (IsTreeListLine(preLineText, out preHeader, out preTail, out preLevel))
{
preLines.Insert(0, new TreeListTextLine()
{
Line = preLine,
OldText = preLineText,
NewText = "",
//HeaderTextArray = preHeader.ToCharArray(),
TailText = preTail,
OldLevel = preLevel,
NewLevel = preLevel, //之前的行层级不变
});
}
preLine = preLine.PreviousLine;
}
FormatAllTreeLines(preLines, null);
//向后找
List<TreeListTextLine> nextLines = new List<TreeListTextLine>();
var nextLine = curLine.NextLine;
while (nextLine != null)
{
var nextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
if (CustomMarkdownSupport.IsTreeListTextLine(nextLineText) == false) break;
string nextHeader, nextTail; int nextLevel;
if (IsTreeListLine(nextLineText, out nextHeader, out nextTail, out nextLevel))
{
var nextTreeListTextLine = new TreeListTextLine()
{
Line = nextLine,
//HeaderTextArray = nextHeader.ToCharArray(),
NewText = "",
OldText = nextLineText,
TailText = nextTail,
OldLevel = nextLevel - 1,
};
nextLines.Add(nextTreeListTextLine);
}
nextLine = nextLine.NextLine;
}
FormatAllTreeLines(nextLines, null);
}
}
}
/// <summary>
/// 格式化二维文字表。
/// </summary>
/// <param name="insertColumnIndex">可以指定文字表的列数。</param>
private void Format2DTextTable(int? insertColumnIndex = null)
{
var curLine = this.Document.GetLineByOffset(this.SelectionStart);
var curLineNumber = curLine.LineNumber;
int selStartOffset = this.SelectionStart - curLine.Offset;
this.Document.UndoStack.StartUndoGroup();
int startOffset;
int endOffset;
string commentText;
var srcTableText = FindSourceTableText(out startOffset, out endOffset, out commentText);
if (string.IsNullOrEmpty(srcTableText)) return;
var lines = new List<TableLine>();
var srcLines = srcTableText.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string s in srcLines)
{
if (IsTableCaptionSplittor(s)) continue;//忽略“表格格式化”功能自动添加的标题与标题分割行。
if (s.StartsWith(";") || s.StartsWith(";")) continue;//忽略备注
if (s.EndsWith("|^") || s.EndsWith("|^"))
{
lines.Add(new TableLine() { LineText = s.Substring(0, s.Length - 1), Type = TableLineType.MergeLine, });
}
else
{
lines.Add(new TableLine() { LineText = s, Type = TableLineType.Normal, });
}
}
if (lines.Count <= 0) return;
var cellSplittor = new char[] { '|' };
var maxColumn = 0;
var lineArrays = new List<TableCellArray>();
string[] columnDefinitionArray = null;
var columnDefinitionLineIndex = -1;
for (int i = 0; i < lines.Count; i++)
{
var text = lines[i].LineText;
text = text.Replace("|", "|").Trim();
if (text.StartsWith("|", StringComparison.CurrentCulture)) text = text.Substring(1);
if (text.EndsWith("|", StringComparison.CurrentCulture)) text = text.Substring(0, text.Length - 1);
var cells = text.Split(cellSplittor, StringSplitOptions.None);
for (int j = 0; j < cells.Length; j++)
{
cells[j] = cells[j].Trim();
}
if (CustomMarkdownSupport.IsColumnAlignmentDefinitionLine("|" + text + "|"))
{
columnDefinitionArray = cells;
columnDefinitionLineIndex = i;
}
else
{
lineArrays.Add(new TableCellArray() { Cells = cells, Type = lines[i].Type, });
}
lines[i].LineText = text;
maxColumn = Math.Max(cells.Length, maxColumn);
}
var columnDefinitions = new List<TableColumnDefinition>();
for (int i = 0; i < maxColumn; i++)
{
columnDefinitions.Add(new TableColumnDefinition());
}
//if (columnDefinitions.Count <= 0 || lineArrays.Count <= 0)
//{
// //LMessageBox.Show("  插入附近似乎没有可以格式化的表格!", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
// //return;
// for (int i = 0; i < maxColumn; i++)
// {
// columnDefinitions.Add(new TableColumnDefinition());
// }
//}
#region 取各列对齐方式定义
bool hascolumndefinitions = false;
if (columnDefinitionArray != null && columnDefinitionArray.Length > 0)
{
hascolumndefinitions = true;
for (int i = 0; i < maxColumn; i++)
{
if (i < columnDefinitionArray.Length)
{
columnDefinitionArray[i] = columnDefinitionArray[i].Replace(":", ":").Trim();
if (columnDefinitionArray[i].Contains("^"))
{
columnDefinitions[i].StartNumber = 0;
}
if (columnDefinitionArray[i].StartsWith(":", StringComparison.CurrentCulture) ||
columnDefinitionArray[i].StartsWith("^:", StringComparison.CurrentCulture))
{
columnDefinitions[i].Align =
(columnDefinitionArray[i].EndsWith(":", StringComparison.CurrentCulture) ||
columnDefinitionArray[i].EndsWith(":^", StringComparison.CurrentCulture)) ?
ColumnAlignment.CENTER : ColumnAlignment.LEFT;
}
else if (columnDefinitionArray[i].EndsWith(":", StringComparison.CurrentCulture) ||
columnDefinitionArray[i].EndsWith(":^", StringComparison.CurrentCulture))
{
columnDefinitions[i].Align = ColumnAlignment.RIGHT;
}
else
{
columnDefinitions[i].Align = ColumnAlignment.LEFT;
}
}
else
{
columnDefinitions[i].Align = ColumnAlignment.LEFT;
}
}
}
#endregion
#region 取各列最大字符宽度
for (int i = 0; i < lineArrays.Count; i++)
{
var cells = lineArrays[i];
for (int j = 0; j < maxColumn; j++)
{
if (j == 0 && i == 0 && cells.Cells.Length == 1 && hascolumndefinitions)
{
//第一列需要考虑标题的长度,不能把表格标题的长度作为第一列所需要的最大宽度
continue;
}
if (j < cells.Cells.Length)
{
columnDefinitions[j].MaxTextWidth = Math.Max(GetTextWidth(cells.Cells[j]), columnDefinitions[j].MaxTextWidth);
}
}
for (int j = 0; j < columnDefinitions.Count; j++)
{
if (columnDefinitions[j].MaxTextWidth % 2 == 1)
{
columnDefinitions[j].MaxTextWidth += 1;
}
}
}
#endregion
var sb = new StringBuilder();
if (columnDefinitionLineIndex >= 0)
{
//标题(可能不存在)
var headerStartIndex = 0;
if (lineArrays[0].Cells.Length == 1 && columnDefinitions.Count > 0)
{
headerStartIndex = 1;//表头行从索引值1开始算。
//如果第一行只有一个成员,算表的标题。
sb.Append("|");
sb.Append(BuildTableCaptionText(lineArrays[0].Cells[0], columnDefinitions, insertColumnIndex));
sb.Append("|\r\n|");
sb.Append(BuildTableCaptionSplittorLine(columnDefinitions, insertColumnIndex));
sb.Append("|\r\n");
}
//表头(可能不止一行),在
for (int lineIndex = headerStartIndex; lineIndex < columnDefinitionLineIndex; lineIndex++)
{
//表格标题、表头均居中对齐
var cells = lineArrays[lineIndex];
sb.Append("|");
for (int i = 0; i < maxColumn; i++)
{
int maxTextLength = columnDefinitions[i].MaxTextWidth;
if (i < cells.Cells.Length)
{
sb.Append(BuildCellText(cells.Cells[i], maxTextLength, ColumnAlignment.CENTER));
if (cells.Cells[i].Trim() == "序号" || cells.Cells[i].Trim().ToLower() == "no.")
{
columnDefinitions[i].StartNumber = 0;
}
}
else
{
sb.Append(BuildCellText(null, maxTextLength, ColumnAlignment.CENTER));
}
if (insertColumnIndex != null && insertColumnIndex.HasValue && insertColumnIndex.Value == i)
{
sb.Append("| |");
}
else
{
sb.Append("|");
}
}
sb.Append("\r\n");
}
//列定义行
if (columnDefinitionLineIndex >= 0)
{
sb.Append("|");
for (int i = 0; i < maxColumn; i++)
{
int maxTextWidth = columnDefinitions[i].MaxTextWidth;
sb.Append(GetColumnDefinitionText(maxTextWidth, columnDefinitions[i].Align, columnDefinitions[i].StartNumber == 0));
if (insertColumnIndex != null && insertColumnIndex.HasValue && insertColumnIndex.Value == i)
{
sb.Append("|---|");
}
else
{
sb.Append("|");
}
}
sb.Append("\r\n");
}
//表体(可能不存在)
for (int lineIndex = columnDefinitionLineIndex; lineIndex < lineArrays.Count; lineIndex++)
{
var cells = lineArrays[lineIndex];
sb.Append("|");
for (int i = 0; i < maxColumn; i++)
{
int maxTextLength = columnDefinitions[i].MaxTextWidth;
if (i < cells.Cells.Length)
{
if (columnDefinitions[i].StartNumber.HasValue)
{
columnDefinitions[i].StartNumber += 1;
sb.Append(BuildCellText(columnDefinitions[i].StartNumber.Value.ToString("D2"), maxTextLength, columnDefinitions[i].Align));
}
else
{
sb.Append(BuildCellText(cells.Cells[i], maxTextLength, columnDefinitions[i].Align));
}
}
else
{
sb.Append(BuildCellText(null, maxTextLength, columnDefinitions[i].Align));
}
if (insertColumnIndex != null && insertColumnIndex.HasValue && insertColumnIndex.Value == i)
{
sb.Append("| |");
}
else
{
sb.Append("|");
}
}
if (lineArrays[lineIndex].Type == TableLineType.MergeLine)
{
sb.Append("^");
}
sb.Append("\r\n");
}
if (string.IsNullOrEmpty(commentText) == false) sb.Append("\r\n");
}
else
{
//LMessageBox.Show("  在附近文本中没有找到【表示表格中各列对齐方式】的那一行文本,无法格式化!\r\n" +
//"  该文本一般会类似下面这行:\r\n\r\n" +
//"    |:------|:------:|------:|\r\n\r\n" +
//"  这行文本分别表示对应的列:【左对齐】、【居中对齐】、【右对齐】。",
// Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
//return;
//如果没有“列对齐定义”这一行,就将所有列都视为表体列好了。
for (int lineIndex = 0; lineIndex < lineArrays.Count; lineIndex++)
{
var cells = lineArrays[lineIndex];
sb.Append("|");
for (int i = 0; i < maxColumn; i++)
{
int maxTextLength = columnDefinitions[i].MaxTextWidth;
if (i < cells.Cells.Length)
{
sb.Append(BuildCellText(cells.Cells[i], maxTextLength, columnDefinitions[i].Align));
}
else
{
sb.Append(BuildCellText(null, maxTextLength, columnDefinitions[i].Align));
}
if (insertColumnIndex != null && insertColumnIndex.HasValue && insertColumnIndex.Value == i)
{
sb.Append("| |");
}
else
{
sb.Append("|");
}
}
if (lineArrays[lineIndex].Type == TableLineType.MergeLine)
{
sb.Append("^");
}
sb.Append("\r\n");
}
if (string.IsNullOrEmpty(commentText) == false) sb.Append("\r\n");
}
var result = sb.ToString();
if (srcTableText.EndsWith("\r\n") == false && result.EndsWith("\r\n"))
{
result = result.Substring(0, result.Length - 2);
}
this.Document.Replace(startOffset, endOffset - startOffset, result + (commentText ?? ""));
this.Document.UndoStack.EndUndoGroup();
if (curLineNumber <= this.Document.LineCount)
{
var cl = this.Document.GetLineByNumber(curLineNumber);
if (cl != null)
{
var selStart = cl.Offset + Math.Min(selStartOffset, cl.Length);
this.Select(selStart, 0);
}
}
}
/// <summary>
/// 生成文字表标题分割行文本。
/// </summary>
/// <param name="columnDefinitions">列定义集。</param>
/// <param name="insertColumnIndex">列数。</param>
private string BuildTableCaptionSplittorLine(List<TableColumnDefinition> columnDefinitions, int? insertColumnIndex)
{
if (columnDefinitions == null || columnDefinitions.Count <= 0) return string.Empty;
var length = 0;
foreach (var cd in columnDefinitions)
{
length += cd.MaxTextWidth;
length += 2;
}
if (length > 2) length -= 2;
if (insertColumnIndex.HasValue)
{
length += 5;////不需要考虑索引值到底是多少,只需要知道需要插入一列即可。一个空列是“ |”宽度为5。
}
var sb = new StringBuilder();
for (int i = 0; i < length; i++)
{
sb.Append("=");
}
return sb.ToString();
}
/// <summary>
/// 生成二维文字表的标题文本行。
/// </summary>
/// <param name="source">源文本。</param>
/// <param name="columnDefinitions">列定义集合。</param>
/// <param name="insertColumnIndex">列数。</param>
private string BuildTableCaptionText(string source, List<TableColumnDefinition> columnDefinitions, int? insertColumnIndex)
{
if (columnDefinitions == null || columnDefinitions.Count <= 0) return source;
var srcWidth = GetTextWidth(source);
var sumTextWidth = 0;
foreach (var cd in columnDefinitions)
{
sumTextWidth += cd.MaxTextWidth;
sumTextWidth += 2;
}
if (sumTextWidth > 2) sumTextWidth -= 2;//末尾一个“|”字符宽度不算。
if (insertColumnIndex.HasValue)
{
sumTextWidth += 5;//不需要考虑索引值到底是多少,只需要知道需要插入一列即可。一个空列是“ |”宽度为5。
}
//表格标题居中对齐
var sb = new StringBuilder();
var offset = sumTextWidth - srcWidth;
for (int i = 1; i <= offset / 2; i++)
{
sb.Append(" ");
}
string result;
if ((int)(offset / 2) * 2 + 1 == offset)
{
result = sb.ToString() + (source ?? "") + sb.ToString() + " ";
}
else
{
result = sb.ToString() + (source ?? "") + sb.ToString();
}
return result;
}
/// <summary>
/// 取出列定义文本。
/// </summary>
/// <param name="maxLength">最大长度。</param>
/// <param name="align">列对齐方式。</param>
/// <param name="withSortMark">是否带“填充序号”标志符(^)。</param>
/// <returns></returns>
private string GetColumnDefinitionText(int maxLength, ColumnAlignment align, bool withSortMark)
{
if (withSortMark)
{
switch (align)
{
case ColumnAlignment.LEFT:
{
var sb = new StringBuilder();
for (int i = 0; i < (maxLength - 2); i++)
{
sb.Append("-");
}
return ":" + sb.ToString() + "^";
}
case ColumnAlignment.RIGHT:
{
var sb = new StringBuilder();
for (int i = 0; i < (maxLength - 2); i++)
{
sb.Append("-");
}
return sb.ToString() + "^:";
}
case ColumnAlignment.CENTER:
default:
{
var sb = new StringBuilder();
for (int i = 0; i < maxLength - 3; i++)
{
sb.Append("-");
}
return ":" + sb.ToString() + "^:";
}
}
}
else
{
switch (align)
{
case ColumnAlignment.LEFT:
{
var sb = new StringBuilder();
for (int i = 0; i < (maxLength - 1); i++)
{
sb.Append("-");
}
return ":" + sb.ToString();
}
case ColumnAlignment.RIGHT:
{
var sb = new StringBuilder();
for (int i = 0; i < (maxLength - 1); i++)
{
sb.Append("-");
}
return sb.ToString() + ":";
}
case ColumnAlignment.CENTER:
default:
{
var sb = new StringBuilder();
for (int i = 0; i < maxLength - 2; i++)
{
sb.Append("-");
}
return ":" + sb.ToString() + ":";
}
}
}
}
/// <summary>
/// 取文本宽度。
/// </summary>
/// <param name="text">要取宽度的文本</param>
/// <param name="trimStartAndEnd">是否去除首尾空格。</param>
/// <returns></returns>
public static int GetTextWidth(string text, bool trimStartAndEnd = false)
{
if (string.IsNullOrEmpty(text)) return 0;
if (CustomMarkdownSupport.IsColumnAlignmentDefinitionLine(text)) return 0;
if (trimStartAndEnd) text = text.Trim();
int width = 0;
foreach (char c in text)
{
width += Encoding.Default.GetByteCount(new char[] { c });//判断全、半角不需要再手工做了。
}
return width;
}
/// <summary>
/// 用半角空格填充,使单元格中文本能够对齐。
/// </summary>
/// <param name="source">要填空空格的源文本。</param>
/// <param name="maxTextWidth">最大宽度。</param>
/// <param name="align">对齐方式</param>
private string BuildCellText(string source, int maxTextWidth, ColumnAlignment align)
{
var srcWidth = GetTextWidth(source);
string result;
switch (align)
{
case ColumnAlignment.LEFT:
{
var sb = new StringBuilder();
for (int i = 1; i <= maxTextWidth - srcWidth; i++)
{
sb.Append(" ");
}
result = (source ?? "") + sb.ToString();
break;
}
case ColumnAlignment.RIGHT:
{
var sb = new StringBuilder();
for (int i = 1; i <= maxTextWidth - srcWidth; i++)
{
sb.Append(" ");
}
result = sb.ToString() + (source ?? "");
break;
}
//case ColumnDefinition.CENTER:
default:
{
var sb = new StringBuilder();
var offset = maxTextWidth - srcWidth;
for (int i = 1; i <= offset / 2; i++)
{
sb.Append(" ");
}
if ((int)(offset / 2) * 2 + 1 == offset)
{
result = sb.ToString() + (source ?? "") + sb.ToString() + " ";
break;
}
else
{
result = sb.ToString() + (source ?? "") + sb.ToString();
break;
}
}
}
return result;
}
/// <summary>
/// 根据指定文本偏移位置,取附近属于二维文字表的所有文本行。
/// </summary>
/// <param name="startOffset">文本偏移位置(头索引)。</param>
/// <param name="endOffset">文本偏移位置(尾索引)</param>
/// <param name="commentText">注释文本。</param>
/// <returns></returns>
private string FindSourceTableText(out int startOffset, out int endOffset, out string commentText)
{
var startSelLine = this.Document.GetLineByOffset(this.SelectionStart);
var endSelLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
if (startSelLine == null || endSelLine == null)
{
startOffset = endOffset = -1;
commentText = null;
return null;
}
if (startSelLine.LineNumber == endSelLine.LineNumber)
{
var text = this.Document.GetText(startSelLine.Offset, startSelLine.Length);
if (string.IsNullOrEmpty(text) || (text.Contains("|") == false && text.Contains("|") == false))
{
startOffset = endOffset = -1;
commentText = null;
return null;
}
}
//基本思路:向上、向下找到表格的首、尾两行。
var commentString = new StringBuilder();
int i = startSelLine.LineNumber;//LineNumber要比索引大1。索引仍然是从0开始计算的。
while (i >= 1)
{
var line = this.Document.GetLineByNumber(i);
if (line == null) break;
var previewLineText = this.Document.GetText(line.Offset, line.Length);
if (previewLineText.StartsWith(";") || previewLineText.StartsWith(";"))
{
//注释不算
commentString.Insert(0, previewLineText + "\r\n");
i--;
continue;
}
if (previewLineText.Contains("|") == false && previewLineText.Contains("|") == false) break;
i--;
}
int fstLineNumber = i + 1;
i = endSelLine.LineNumber;
while (i <= this.Document.LineCount)
{
var line = this.Document.GetLineByNumber(i);
if (line == null) break;
var nextLineText = this.Document.GetText(line.Offset, line.Length);
if (nextLineText.StartsWith(";") || nextLineText.StartsWith(";"))
{
//注释不算
commentString.Append(nextLineText + "\r\n");
i++;
continue;
}
if (nextLineText.Contains("|") == false && nextLineText.Contains("|") == false) break;
i++;
}
int lastLineNumber = i - 1;
startOffset = this.Document.GetLineByNumber(fstLineNumber).Offset;
endOffset = this.Document.GetLineByNumber(lastLineNumber).EndOffset;
commentText = commentString.ToString();
var srcText = this.Document.GetText(startOffset, endOffset - startOffset);
return srcText;
}
/// <summary>
/// 判断此行文本是否只包含“|”、“=”等。这样的行是“表格格式化功能”自动添加的分割表标题与表头行的装饰行。
/// </summary>
/// <param name="lineText">文本行</param>
private bool IsTableCaptionSplittor(string lineText)
{
if (string.IsNullOrEmpty(lineText)) return false;
foreach (char c in lineText)
{
if (c == '=' || c == '|' || c == '|' || c == '=' ||
c == ' ' || //下面这三个是考虑到在<code>块中的文字表也需要格式化。
c == ' ' ||
c == '\t')
continue;
return false;
}
return true;
}
/// <summary>
/// 给二维文字表插入列。
/// </summary>
internal void InsertTableColumn()
{
var selEndOffset = this.SelectionStart + this.SelectionLength;
var curLine = this.Document.GetLineByOffset(selEndOffset);
var curLineLeftText = this.Document.GetText(curLine.Offset, selEndOffset - curLine.Offset);
int pipesCount = 0;
foreach (char c in curLineLeftText)
{
if (c == '|' || c == '|')
{
pipesCount++;
}
}
int? insertColumnIndex = null;
if (curLineLeftText.StartsWith("|") || curLineLeftText.StartsWith("|"))
{
insertColumnIndex = pipesCount;
}
else
{
insertColumnIndex = pipesCount + 1;
}
Format2DTextTable(insertColumnIndex.Value - 1);
JumpToNextCell();
if (Globals.MainWindow.miSelectCellFirst.IsChecked)
{
JumpToNextCell();//再调用一次,选中新插入的列在当前行的单元格。
}
}
}
public class TableCellArray
{
public string[] Cells { get; set; }
public TableLineType Type { get; set; }
}
}

Комментарий ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://gitlife.ru/oschina-mirror/lunarsf-Lunar-Markdown-Editor.git
git@gitlife.ru:oschina-mirror/lunarsf-Lunar-Markdown-Editor.git
oschina-mirror
lunarsf-Lunar-Markdown-Editor
lunarsf-Lunar-Markdown-Editor
v0.4-beta8