/// <summary> /// Simulates the insertion of the whole text, use this to reset the lines info /// (when switching document for instance) /// </summary> public void Reset() { Init(); var scn = new SCNotification { linesAdded = SciGetLineCount() - 1, position = 0, length = SciGetLength() }; scn.text = Api.Send(SciMsg.SCI_GETRANGEPOINTER, new IntPtr(scn.position), new IntPtr(scn.length)); OnScnModified(scn, true, Sci.Encoding); }
/// <summary> /// Updates the line info when inserting text /// </summary> /// <param name="scn"></param> private void OnInsertedText(SCNotification scn) { var startLine = SciLineFromPosition(scn.position); if (scn.linesAdded == 0) { var insCharLenght = GetCharCount(scn.text, scn.length); SetHoleInLine(startLine, insCharLenght); } else { var startCharPos = CharPositionFromLine(startLine); var lineByteStart = SciPositionFromLine(startLine); var lineByteLength = SciLineLength(startLine); var lineCharLenght = GetCharCount(lineByteStart, lineByteLength); var insCharLenght = lineCharLenght - LineCharLength(startLine); FillTheHole(); for (int i = 0; i < scn.linesAdded; i++) { startCharPos += lineCharLenght; var line = startLine + i + 1; lineByteStart += lineByteLength; lineByteLength = SciLineLength(line); lineCharLenght = GetCharCount(lineByteStart, lineByteLength); insCharLenght += lineCharLenght; _linesList.Insert(line, startCharPos); } SetHoleInLine(startLine + scn.linesAdded, insCharLenght); FillTheHole(); // We should not have a null length, but we actually can : // when a file is modified outside npp, npp suggests to reload it, a modified notification is sent // but is it sent BEFORE the text is actually put into scintilla! So what we do here doesn't work at all // so in that case, we need to refresh the info when the text is actually inserted, that is after updateui // Clarification : the notification sent is correct (nb lines > 0, length, text are ok), but calling SciLineLength // will always return 0 at this moment! if (scn.length > 0 && TextLength == 0) { NotificationsPublisher.ActionsAfterUpdateUi.Enqueue(Reset); } } }
/// <summary> /// When receiving a modification notification by scintilla /// </summary> public void OnScnModified(SCNotification scn, bool isInsertion, Encoding encoding) { _lastEncoding = encoding; _singleByteCharEncoding = _lastEncoding.IsSingleByte; _singleByteCharEncoding = false; // for 1.7.4 // bypass the hard work for simple encoding if (_singleByteCharEncoding) { return; } if (isInsertion) { OnInsertedText(scn); } else { OnDeletedText(scn); } }
/// <summary> /// updates the line info when deleting text /// </summary> /// <param name="scn"></param> private void OnDeletedText(SCNotification scn) { var startLine = SciLineFromPosition(scn.position); if (scn.linesAdded == 0) { var delCharLenght = GetCharCount(scn.text, scn.length); SetHoleInLine(startLine, -delCharLenght); } else { var lineByteStart = SciPositionFromLine(startLine); var lineByteLength = SciLineLength(startLine); var delCharLenght = -(GetCharCount(lineByteStart, lineByteLength) - LineCharLength(startLine)); FillTheHole(); for (int i = 0; i < -scn.linesAdded; i++) { delCharLenght += LineCharLength(startLine + 1); _linesList.RemoveAt(startLine + 1); } SetHoleInLine(startLine, -delCharLenght); FillTheHole(); } }
/// <summary> /// handles the notifications send by npp and scintilla to the plugin /// </summary> public static void OnNppNotification(SCNotification nc) { try { uint code = nc.nmhdr.code; // Plugin waiting to be started... if (!PluginIsReady) { switch (code) { case (uint)NppNotif.NPPN_TBMODIFICATION: // this is the event that we want to respond to, it sets the toolbar icons UnmanagedExports.NppFuncItems.RefreshItems(); Plug.DoNppNeedToolbarImages(); return; case (uint)NppNotif.NPPN_READY: // notify plugins that all the procedures of launch of notepad are done ActionsAfterUpdateUi = new Queue <Action>(); Npp.UpdateCurrentSci(); // init current scintilla UiThread.Init(); PluginIsReady = Plug.DoNppReady(); // call OnNppReady then OnPlugReady if it all went ok if (PluginIsReady) { Plug.DoPlugStart(); OnNppNotification(new SCNotification((uint)NppNotif.NPPN_BUFFERACTIVATED)); // simulate buffer activated // set hooks on mouse/keyboard SetHooks(); } return; case (uint)NppNotif.NPPN_SHUTDOWN: // uninstall hooks on mouse/keyboard UninstallHooks(); UiThread.Close(); Plug.DoNppShutDown(); return; case (uint)NppNotif.NPPN_CANCELSHUTDOWN: PluginIsReady = true; return; } } else { // the plugin is fully loaded and ready to do stuff if ((uint)SciNotif.SCN_NOTIF_BEGIN < code && code < (uint)SciNotif.SCN_NOTIF_END) { switch (code) { // -------------------------------------------------------- // Scintilla message // -------------------------------------------------------- case (uint)SciNotif.SCN_CHARADDED: // called each time the user add a char in the current scintilla // It's actually better to use the SCI_MODIFIED instead, this notification // is not always called when it should! (ex not called for /t) return; case (uint)SciNotif.SCN_UPDATEUI: while (ActionsAfterUpdateUi.Any()) { ActionsAfterUpdateUi.Dequeue()(); } Plug.OnSciUpdateUi(nc); return; case (uint)SciNotif.SCN_MODIFIED: // This notification is sent when the text or styling of the document changes or is about to change // (note : this notif isn't sent when the user SWITCHES to tab file (already opened in another tab) ! // But it is sent when the user opens a NEW file) bool deletedText = (nc.modificationType & (int)SciModificationMod.SC_MOD_DELETETEXT) != 0; bool insertedText = (nc.modificationType & (int)SciModificationMod.SC_MOD_INSERTTEXT) != 0; bool undo = (nc.modificationType & (int)SciModificationMod.SC_PERFORMED_UNDO) != 0; bool redo = (nc.modificationType & (int)SciModificationMod.SC_PERFORMED_REDO) != 0; bool singleCharModification = false; if ((insertedText || deletedText) && !ScnModifiedDisabled) { var encoding = Sci.Encoding; Npp.CurrentSci.Lines.OnScnModified(nc, !deletedText, encoding); // register line modifications if (!undo && !redo) { // if the text has changed unsafe { var nbCarets = Sci.Selection.Count; if (_currentCaret > 0) { _currentCaret++; if (_currentCaret <= nbCarets) { return; } // then it is the first caret again, we can handle the char _currentCaret = 0; } if (nbCarets > 1) { _currentCaret++; } // only 1 char appears to be modified if (nc.length <= 2) { // get the char var bytes = (byte *)nc.text; var arrbyte = new byte[nc.length]; int index; for (index = 0; index < nc.length; index++) { arrbyte[index] = bytes[index]; } var c = encoding.GetChars(arrbyte); var cLength = c.Length; // do we really have a 1 char input? if (cLength == 1 || (cLength == 2 && c[0] == '\r')) { if (insertedText) { ActionsAfterUpdateUi.Enqueue(() => Plug.OnCharAdded(c[0], nc.position)); } else { ActionsAfterUpdateUi.Enqueue(() => Plug.OnCharDeleted(c[0], nc.position)); } singleCharModification = true; } } } } ActionsAfterUpdateUi.Enqueue(() => Plug.OnTextModified(nc, insertedText, deletedText, singleCharModification, undo, redo)); } return; case (uint)SciNotif.SCN_STYLENEEDED: // if we use the contained lexer, we will receive this notification and we will have to style the text Plug.OnStyleNeeded(Sci.GetEndStyled(), nc.position); return; case (uint)SciNotif.SCN_MARGINCLICK: // called each time the user click on a margin Plug.OnSciMarginClick(nc); return; case (uint)SciNotif.SCN_MODIFYATTEMPTRO: // Code a checkout when trying to modify a read-only file return; case (uint)SciNotif.SCN_DWELLSTART: // when the user hover at a fixed position for too long Plug.OnSciDwellStart(); return; case (uint)SciNotif.SCN_DWELLEND: // when he moves his cursor Plug.OnSciDwellEnd(); return; } } else if ((uint)NppNotif.NPPN_NOTIF_BEGIN < code && code < (uint)NppNotif.NPPN_NOTIF_END) { // -------------------------------------------------------- // Npp message // -------------------------------------------------------- switch (code) { case (uint)NppNotif.NPPN_BUFFERACTIVATED: // the user changes the current document (this event is called when the current document is switched (via the tabs) // and also when a new file is opened in npp Npp.UpdateCurrentSci(); // update current scintilla Npp.CurrentSci.Lines.Reset(); // register new lines NppBufferActivated(); return; case (uint)NppNotif.NPPN_FILERENAMED: // the user can open a .txt and rename it as a .p NppBufferActivated(); return; case (uint)NppNotif.NPPN_FILESAVED: // the user can open a .txt and save it as a .p NppBufferActivated(); Plug.DoNppDocumentSaved(); return; case (uint)NppNotif.NPPN_FILEBEFORELOAD: // fire when a file is opened // When loading a new file into NPP, the events fired are (in order) : // NPPN_FILEBEFORELOAD > SCN_MODIFIED > NPPN_FILEBEFOREOPEN > NPPN_FILEOPENED > NPPN_BUFFERACTIVATED // we deactivate the SCN_MODIFIED between NPPN_FILEBEFORELOAD and NPPN_FILEBEFOREOPEN ScnModifiedDisabled = true; Plug.DoNppFileBeforeLoad(); return; case (uint)NppNotif.NPPN_FILEBEFOREOPEN: ScnModifiedDisabled = false; return; case (uint)NppNotif.NPPN_FILEOPENED: // on file opened Plug.OnNppFileOpened(); return; case (uint)NppNotif.NPPN_FILEBEFORECLOSE: // on file closed Plug.OnNppFileBeforeClose(); return; case (uint)NppNotif.NPPN_LANGCHANGED: // on lang type changed Plug.OnLangChanged(); NppBufferActivated(); return; case (uint)NppNotif.NPPN_WORDSTYLESUPDATED: // The styles have been modified Npp.StylersXml.Reload(); // unfortunatly, if the user changed of styler.xml file (he selected another theme) then we // will incorrectly read the styles since we have to wait for the config.xml to be updated // and it only updates on npp shutdown return; case (uint)NppNotif.NPPN_BEFORESHUTDOWN: // prevent the plugin from handling a lot of events when npp is about to shutdown PluginIsReady = false; return; } } } } catch (Exception e) { ErrorHandler.ShowErrors(e, "Error in beNotified : code = " + nc.nmhdr.code); } }
public static void beNotified(IntPtr notifyCode) { SCNotification nc = (SCNotification)Marshal.PtrToStructure(notifyCode, typeof(SCNotification)); NotificationsPublisher.OnNppNotification(nc); }