264 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
			
		
		
	
	
			264 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
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.
 | 
						|
 |