I've been looking for a Notepad++ plugin that can close HTML/XML tags after a slash just like the way Dreamweaver does for a long time.
The only things I could find(TextFX, XML Tools etc.) close the tags right after ">" is typed in, which was not what I wanted.
A couple days ago I found a plugin called Automation Scripts. It allows me to write scripts in C#, so I don't have to spend time learning how to write Notepad++ plugins.
So here you go:
//npp_shortcut Ctrl+Shift+Z //Automation Scripts plugin needs to be installed for this to work using System.Collections.Generic; using System.Text.RegularExpressions; using System.Text; using System; using System.Windows.Forms; using NppScripts; public class Script : NppScript { public Script() { this.OnNotification = (notification) => { if(notification.nmhdr.code == (uint)SciMsg.SCN_CHARADDED) { doInsertHtmlCloseTag((char)notification.ch); } }; } public override void Run() { checkInsertHtmlCloseTag(); } bool doCloseTag; void checkInsertHtmlCloseTag() { doCloseTag = !doCloseTag; Win32.SendMessage(Npp.NppHandle, NppMsg.NPPM_SETMENUITEMCHECK, Plugin.FuncItems.Items[this.ScriptId]._cmdID, doCloseTag ? 1 : 0); } string GetStartTagName(string input) { Regex regex=new Regex(@"<[A-Za-z][A-Za-z0-9]*[^>]*[^/]>|<[A-Za-z]>"); if(!regex.IsMatch(input)) return ""; StringBuilder result=new StringBuilder(); int i=1; while(input[i]!=' ' && input[i]!='>' & i<input.Length) { result.Append(input[i]); i++; } return result.ToString(); } string GetEndTagName(string input) { Regex regex=new Regex(@"</[A-Za-z][A-Za-z0-9]*[^>]*>"); if(!regex.IsMatch(input)) return ""; StringBuilder result=new StringBuilder(); int i=2; while(input[i]!=' ' && input[i]!='>' & i<input.Length) { result.Append(input[i]); i++; } return result.ToString(); } string GetSelection() { IntPtr hCurrentEditView = Npp.CurrentScintilla; StringBuilder result=new StringBuilder(); Win32.SendMessage(hCurrentEditView,SciMsg.SCI_GETSELTEXT,0,result); return result.ToString(); } int FindNextTag(int pos) { IntPtr hCurrentEditView = Npp.CurrentScintilla; string pattern=@"</?[A-Za-z][A-Za-z0-9]*[^>]*[^/]>|</?[A-Za-z]>"; Win32.SendMessage(hCurrentEditView, SciMsg.SCI_SETSEL, pos, pos); Win32.SendMessage(hCurrentEditView,SciMsg.SCI_SEARCHANCHOR,0,0); return (int)Win32.SendMessage(hCurrentEditView,SciMsg.SCI_SEARCHPREV,(int)SciMsg.SCFIND_REGEXP,pattern); } void doInsertHtmlCloseTag(char newChar) { LangType docType = LangType.L_TEXT; Win32.SendMessage(Npp.NppHandle, NppMsg.NPPM_GETCURRENTLANGTYPE, 0, ref docType); bool isDocTypeHTML = (docType == LangType.L_HTML || docType == LangType.L_XML || docType == LangType.L_PHP); if (doCloseTag && isDocTypeHTML && newChar=='/') { IntPtr hCurrentEditView = Npp.CurrentScintilla; int currentPos = (int)Win32.SendMessage(hCurrentEditView, SciMsg.SCI_GETCURRENTPOS, 0, 0); char lastChar=(char)Win32.SendMessage(hCurrentEditView,SciMsg.SCI_GETCHARAT,currentPos-2,0); StringBuilder insertString=new StringBuilder(); if(lastChar=='<') { int pos=currentPos; Stack<string> stack=new Stack<string>(); string tag; while(true) { pos=FindNextTag(pos); if(pos==-1) { Win32.SendMessage(hCurrentEditView, SciMsg.SCI_SETSEL, currentPos, currentPos); return; } tag=GetSelection(); if(tag[1]=='/') { stack.Push(GetEndTagName(tag)); } else { tag=GetStartTagName(tag); if(stack.Count==0) break; else { string endTag=stack.Pop(); while(tag!=endTag && stack.Count>0) { endTag=stack.Pop(); } if(tag!=endTag) break; } } } insertString.Append(tag+">"); Win32.SendMessage(hCurrentEditView, SciMsg.SCI_BEGINUNDOACTION, 0, 0); Win32.SendMessage(hCurrentEditView, SciMsg.SCI_SETSEL, currentPos, currentPos); Win32.SendMessage(hCurrentEditView, SciMsg.SCI_REPLACESEL, 0, insertString); Win32.SendMessage(hCurrentEditView, SciMsg.SCI_SETSEL, currentPos+insertString.Length, currentPos+insertString.Length); Win32.SendMessage(hCurrentEditView, SciMsg.SCI_ENDUNDOACTION, 0, 0); } } } }