unit atsynedit_form_complete_html; {$mode objfpc}{$H+} interface uses Classes, SysUtils, Graphics, ATSynEdit, ATSynEdit_Carets, RegExpr, Dialogs; //it needs file html_list.ini from SynWrite distro procedure DoEditorCompletionHtml(AEdit: TATSynEdit; const AFilenameHtmlList: string); type TCompleteHtmlMode = ( acpModeNone, acpModeTags, acpModeTagsClose, acpModeAttrs, acpModeVals ); //detect tag and its attribute at caret pos procedure EditorGetHtmlTag(Ed: TATSynedit; out STag, SAttr: string; out AMode: TCompleteHtmlMode); function EditorHasCssAtCaret(Ed: TATSynEdit): boolean; implementation uses ATStringProc, ATSynEdit_form_complete; type { TAcp } TAcp = class private List: TStringlist; procedure DoOnGetCompleteProp(Sender: TObject; out AText, ASuffix: string; out ACharsLeft, ACharsRight: integer); public Ed: TATSynEdit; constructor Create; virtual; destructor Destroy; override; end; var Acp: TAcp = nil; function SFindRegex(const SText, SRegex: string; NGroup: integer): string; var R: TRegExpr; begin Result:= ''; R:= TRegExpr.Create; try R.ModifierS:= false; R.ModifierM:= true; R.ModifierI:= true; R.Expression:= SRegex; R.InputString:= SText; if R.ExecPos(1) then Result:= Copy(SText, R.MatchPos[NGroup], R.MatchLen[NGroup]); finally R.Free; end; end; procedure EditorGetHtmlTag(Ed: TATSynedit; out STag, SAttr: string; out AMode: TCompleteHtmlMode); const //regex to catch tag name at line start cRegexTagPart = '^\w+\b'; cRegexTagOnly = '^\w*$'; cRegexTagClose = '^/\w*$'; //character class for all chars inside quotes cRegexChars = '[\s\w,\.:;\-\+\*\?=\(\)\[\]\{\}/\\\|~`\^\$&%\#@!]'; //regex to catch attrib name, followed by "=" and not-closed quote, only at line end cRegexAttr = '\b([\w\-]+)\s*\=\s*([''"]' + cRegexChars + '*)?$'; //regex group cGroupTagPart = 0; cGroupTagOnly = 0; cGroupTagClose = 0; cGroupAttr = 1; var Caret: TATCaretItem; S: atString; N: integer; begin STag:= ''; SAttr:= ''; AMode:= acpModeNone; //str before caret Caret:= Ed.Carets[0]; S:= Ed.Strings.Lines[Caret.PosY]; S:= Copy(S, 1, Caret.PosX); if S='' then Exit; //cut string before last "<" or ">" char N:= Length(S); while (N>0) and (S[N]<>'<') and (S[N]<>'>') do Dec(N); if N=0 then Exit; Delete(S, 1, N); STag:= SFindRegex(S, cRegexTagClose, cGroupTagClose); if STag<>'' then begin AMode:= acpModeTagsClose; exit end; STag:= SFindRegex(S, cRegexTagOnly, cGroupTagOnly); if STag<>'' then begin AMode:= acpModeTags; exit end; STag:= SFindRegex(S, cRegexTagPart, cGroupTagPart); if STag<>'' then begin SAttr:= SFindRegex(S, cRegexAttr, cGroupAttr); if SAttr<>'' then AMode:= acpModeVals else AMode:= acpModeAttrs; end else AMode:= acpModeTags; end; function EditorHasCssAtCaret(Ed: TATSynEdit): boolean; var STag, SAttr: string; Mode: TCompleteHtmlMode; begin EditorGetHtmlTag(Ed, STag, SAttr, Mode); Result:= (Mode=acpModeVals) and (LowerCase(SAttr)='style'); end; procedure TAcp.DoOnGetCompleteProp(Sender: TObject; out AText, ASuffix: string; out ACharsLeft, ACharsRight: integer); const cWordChars = '-'; var mode: TCompleteHtmlMode; s_word: atString; s_tag, s_attr, s_item, s_subitem, s_value: string; i: integer; ok: boolean; begin AText:= ''; ASuffix:= ''; ACharsLeft:= 0; ACharsRight:= 0; EditorGetHtmlTag(Ed, s_tag, s_attr, mode); EditorGetCurrentWord(Ed, cWordChars, s_word, ACharsLeft, ACharsRight); case mode of acpModeTags, acpModeTagsClose: begin if mode=acpModeTagsClose then ASuffix:= '>' else ASuffix:= ' '; for i:= 0 to List.Count-1 do begin s_item:= List.Names[i]; //filter items if s_word<>'' then begin ok:= SBeginsWith(UpperCase(s_item), UpperCase(s_word)); if not ok then Continue; end; AText:= AText+'tag|'+s_item+#13; end; end; acpModeAttrs: begin ASuffix:='='; s_item:= List.Values[s_tag]; if s_item='' then exit; repeat s_subitem:= SGetItem(s_item, '|'); if s_subitem='' then Break; s_subitem:= SGetItem(s_subitem, '<'); //filter items if s_word<>'' then begin ok:= SBeginsWith(UpperCase(s_subitem), UpperCase(s_word)); if not ok then Continue; end; AText:= AText+s_tag+' attrib|'+s_subitem+#13; until false; end; acpModeVals: begin ASuffix:=' '; s_item:= List.Values[s_tag]; if s_item='' then exit; repeat s_subitem:= SGetItem(s_item, '|'); if s_subitem='' then Break; if SGetItem(s_subitem, '<')<>s_attr then Continue; repeat s_value:= SGetItem(s_subitem, '?'); if s_value='' then Break; AText:= AText+s_attr+' value|"'+s_value+'"'+#13; until false; until false; end; end; end; constructor TAcp.Create; begin inherited; List:= TStringlist.create; end; destructor TAcp.Destroy; begin FreeAndNil(List); inherited; end; procedure DoEditorCompletionHtml(AEdit: TATSynEdit; const AFilenameHtmlList: string); begin Acp.Ed:= AEdit; //load file only once if Acp.List.Count=0 then begin if not FileExists(AFilenameHtmlList) then exit; Acp.List.LoadFromFile(AFilenameHtmlList); end; DoEditorCompletionListbox(AEdit, @Acp.DoOnGetCompleteProp); end; initialization Acp:= TAcp.Create; cCompleteFontStyles[0]:= []; cCompleteColorFont[0]:= clPurple; cCompleteColorFont[1]:= clBlack; finalization FreeAndNil(Acp); end.