-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathSynFacilCompletion.pas
2118 lines (2078 loc) · 78.2 KB
/
SynFacilCompletion.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
{
SynFacilCompletion
==================
Por Tito Hinostroza
Pendientes:
* Incluir una forma simplficada de la forma <OpenOn AfterIdentif="Alter">, para simplificar
la definición clásica.
* Ver el trabajo de la librería con caracteres UTF-8 de dos bytes.
* Optimizar el método LookAround(), evitando tener que leer dos veces la línea actual
y de ser posible creando una rutina personalizada, en lugar de usar ExploreLine().
* Incluir el manejo de las ventanas de tipo "Tip", como ayuda para los parámetros de las
funciones.
* Hacer que la ventana de completado haga seguimiento del cursor, cuando este retrocede
mucho en un identificador.
* Realizar dos pasadas en la etiqueta <completion>, para que se puedan definir las listas
en cualquier parte.
}
{Descripción
============
Unidad que expande al resaltador TSynFacilSyn, para que pueda soportar configuraciones
de autocompletado de texto.
Se usa de forma similar a SynFacilSyn. Se debe crear un resaltador, pero ahora de la
clase TSynFacilComplet:
uses ... , SynFacilCompletion;
procedure TForm1.FormShow(Sender: TObject);
begin
//configure highlighter
hlt := TSynFacilComplet.Create(self); //my highlighter
SynEdit1.Highlighter := hlt; //optional if we are going to use SelectEditor()
hlt.LoadFromFile('./languages/ObjectPascal.xml'); //load syntax
hlt.SelectEditor(SynEdit1); //assign to editor
end;
Luego se debe interceptar los evento KeyUp y UTF8KeyPress, del SynEdit:
procedure TForm1.SynEdit1KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
hlt.KeyUp(Sender, Key, Shift);
end;
procedure TForm1.SynEdit1UTF8KeyPress(Sender: TObject; var UTF8Key: TUTF8Char);
begin
hlt.UTF8KeyPress(Sender, UTF8Key);
end;
Y se debe terminar correctamente:
procedure TForm1.FormDestroy(Sender: TObject);
begin
hlt.UnSelectEditor; //release editor (only necessary if we are to call to SelectEditor(), again)
hlt.Free; //destroy the highlighter
end;
Cuando se desea desaparecer la ventana de ayuda contextual por algún evento, se debe
llamar a CloseCompletionWindow().
}
unit SynFacilCompletion;
{$mode objfpc}{$H+}
//{$define Verbose}
interface
uses
Classes, SysUtils, fgl, Dialogs, XMLRead, DOM, LCLType, Graphics, Controls,
SynEdit, SynEditHighlighter, SynEditTypes, SynEditKeyCmds, Lazlogger,
SynFacilHighlighter, SynFacilBasic, SynCompletion;
type
TFaTokInfoPtr = ^TFaTokInfo;
{ TFaCompletItem }
TFaCompletItem = class
private
fCaption: string; //etiqueta a mostrar en el menú
Replac : string; //contenido a reemplazar
Descrip: string; //descripción del campo
idxIcon: integer; //índice al ícono
function ExtractField(var str: string): string;
procedure SetCaption(AValue: string);
public
property Caption: string read FCaption write SetCaption;
function StartWith(const c: char): boolean; inline;
function StartWithU(const c: char): boolean; inline;
end;
//TFaCompletItemPtr = ^TFaCompletItem;
TFaCompletItems = specialize TFPGObjectList<TFaCompletItem>;
//Filtros que se pueden aplicar a la lista mostrada
TFaFilterList = (
fil_None, //Sin filtro. Muestra todos
fil_LastTok, //por el tokem -1
fil_LastTokPart, //por el token -1, hasta donde está el cursor
fil_LastIdent, //por el identificador anterior (usa su propia rutina para identifificadores)
fil_LastIdentPart //similar pero toma hasta el cursor
);
//Objeto lista para completado
{ TFaCompletionList }
TFaCompletionList = class
Name : string;
Items: TFaCompletItems; //lista de las palabras disponibles
procedure AddItems(list: string; idxIcon: integer);
public
constructor Create;
destructor Destroy; override;
end;
//Colección de listas
TFaCompletionLists = specialize TFPGObjectList<TFaCompletionList>;
//Tipo de Elemento del patrón
TFaTPatternElementKind = (
pak_none, //tipo no definido
pak_String, //es literal cadena
pak_Identif, //es token identificador (tkKeyword, tkIndetifier, ...)
pak_NoIdentif, //no es token identificador
pak_TokTyp, //es un tipo específico de token
pak_NoTokTyp //no es un tipo específico de token
);
//Elemento del patrón
TFaPatternElement = record
patKind: TFaTPatternElementKind;
str : string; //valor, cuando es del tipo pak_String
toktyp : integer; //valor cuando es de tipo pak_TokTyp o pak_NoTokTyp
end;
TFaPatternElementPtr = ^TFaPatternElement;
{Tipos de secuencias de escape que se puede indicar para el reemplazo de texto.
No son todas las secuencias de escape, sino solo las que necesitan procesarse
independientemente para ejecutar correctamente la acción de reemplazo.}
TFaCompletSeqType = (
csqNone, //no es secuencia de escape
csqCurPos, //secuencia que indica posición del cursor
csqTabSpa //tabulación al nivel del primer caracter de línea anterior
);
//Entorno del cursor
{ TFaCursorEnviron }
TFaCursorEnviron = class
private
hlt: TSynFacilSyn; //referencia al resaltador que lo contiene
tokens : TATokInfo; //lista de tokens actuales
StartIdentif : integer; //inicio de identificador
function ExtractStaticText(var ReplaceSeq: string; out seq: TFaCompletSeqType
): string;
procedure InsertSequence(ed: TSynEdit; Pos1, Pos2: TPoint; ReplaceSeq: string);
procedure UpdateStartIdentif;
public
inMidTok : boolean; //indica si el cursor está en medio de un token
tok0 : TFaTokInfoPtr; //referencia al token actual.
tok_1 : TFaTokInfoPtr; //referencia al token anterior.
tok_2 : TFaTokInfoPtr; //referencia al token anterior a tok_1.
tok_3 : TFaTokInfoPtr; //referencia al token anterior a tok_2.
CurX : Integer; //posición actual del cursor
CurY : Integer; //posición actual del cursor
curLine : string; //línea actual de exploración
curBlock : TFaSynBlock; //referencia al bloque actual
caseSen : boolean; //indica el estado de caja actual
procedure LookAround(ed: TSynEdit; CaseSen0: boolean);
//Las siguientes funciones, deben llaamrse después de lamar a LookAround()
function HaveLastTok: boolean;
function LastTok: string;
function LastTokPart: string;
function HaveLastIdent: boolean;
function LastIdent: string;
function LastIdentPart: string;
//Estas funciones implementan las acciones
procedure ReplaceLastTok(ed: TSynEdit; ReplaceSeq: string);
procedure ReplaceLastIden(ed: TSynEdit; ReplaceSeq: string);
procedure Insert(ed: TSynEdit; ReplaceSeq: string);
public
constructor Create(hlt0: TSynFacilSyn);
end;
TFaOpenEvent = class;
TFaOnLoadItems = procedure(opEve: TFaOpenEvent; curEnv: TFaCursorEnviron;
out Cancel: boolean) of object;
//Acciones válidas que se realizarán al seleccionar un ítem
TFAPatAction = (
pac_None, //no se realiza ninguna acción
pac_Default, //acción pro defecto
pac_Insert, //se inserta el texto seleccionado en la posición del cursor
pac_Rep_LastTok //se reemplaza el token anterior
);
//Objeto evento de apertura
{ TFaOpenEvent }
TFaOpenEvent = class
private
hlt: TSynFacilSyn; //referencia al resaltador que lo contiene
{Los índices de elem[] representan posiciones relativas de tokens
[0] -> Token que está justo después del cursor (token actual)
[-1] -> Token que está antes del token actual
[-2] -> Token que está antes del token [-1]
[-3] -> Token que está antes del token [-2] }
elem : array[-3..0] of TFaPatternElement;
nBef : integer; //número de elementos válidos haste el ítem 0 (puede ser 0,1,2 o 3)
nAft : integer; //número de elementos válidos depués del ítem 0 (puede ser 0 o 1)
procedure ExtractElementIn(var befPat: string;
patEle: TFaPatternElementPtr; var ErrStr: string);
function MatchPatternElement(nPe: integer; tokX: TFaTokInfoPtr;
CaseSens: boolean): boolean;
function MatchPatternBefore(const curEnv: TFaCursorEnviron): boolean;
function MatchPatternAfter(const curEnv: TFaCursorEnviron): boolean;
function MatchPattern(const curEnv: TFaCursorEnviron): boolean;
procedure ShiftBeforePattern;
public
name : string; //nombre del evento de apertura
startX: integer; //posición inicial del token o identificador de trabajo
filter: TFaFilterList;
block : TFaSynBlock; //bloque donde es válido
Action: TFAPatAction; //Acción al seleccionar lista
OnLoadItems: TFaOnLoadItems; //Se llama antes de cargar los ítems.
procedure FilterByChar(curEnv: TFaCursorEnviron; const c: char);
procedure DoAction(ed: TSynEdit; env: TFaCursorEnviron; ReplaceSeq: string);
procedure FillFilteredIn(const env: TFaCursorEnviron; lst: TStrings); //Llena Items en una lista
//manejo patrones
procedure ClearBeforePatt; //limpia el patron anterior
procedure ClearAfterPatt; //limpia el patron anterior
procedure AddBeforeElement(var befPat: string; out ErrStr: string);
procedure AddAfterElement(var aftPat: string; var ErrStr: string);
public //Manejo de ítems
Items : TFaCompletItems; //Lista de las palabras disponibles para el completado
Lists : TFaCompletionLists; //Referencias a listas
Avails: TFaCompletItems; //Ítems a cargar cuando se active el patrón.
procedure ClearItems;
procedure LoadItems(curEnv: TFaCursorEnviron);
procedure AddItem(txt: string; idxIcon: integer);
procedure AddItems(lst: TStringList; idxIcon: integer);
procedure AddItems(list: string; idxIcon: integer);
procedure AddList(Alist: TFaCompletionList; OnlyRef: boolean);
procedure ClearAvails;
procedure AddAvail(txt: string); //Rutina simple para agregar cadena a Avails
procedure Clear;
public
constructor Create(hlt0: TSynFacilSyn);
destructor Destroy; override;
end;
//Lista de patrones
TFaOpenEvents = specialize TFPGObjectList<TFaOpenEvent>;
type
{ TSynCompletionF }
{Clase personalizada de "TSynCompletion" usada para el completado con "TSynFacilComplet"}
TSynCompletionF = class(TSynCompletion)
function OnSynCompletionPaintItem(const {%H-}AKey: string; ACanvas: TCanvas; X,
Y: integer; {%H-}IsSelected: boolean; Index: integer): boolean;
public
IconList: TImageList; //lista de íconos
procedure Refresh;
constructor Create(AOwner: TComponent); override;
end;
{ TSynFacilComplet }
//clase principal
TSynFacilComplet = class(TSynFacilSyn)
procedure FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
procedure OnCodeCompletion(var Value: string; SourceValue: string;
var SourceStart, SourceEnd: TPoint; KeyChar: TUTF8Char; Shift: TShiftState);
procedure FormUTF8KeyPress(Sender: TObject; var UTF8Key: TUTF8Char);
protected
ed : TSynEdit; //referencia interna al editor
MenuComplet: TSynCompletionF;//menú contextual
curEnv : TFaCursorEnviron; //entorno del cursor
UtfKey : TUTF8Char; //tecla pulsada
SpecIdentifiers: TArrayTokSpec;
SearchOnKeyUp : boolean; //bandera de control
procedure MenuComplet_OnExecute(Sender: TObject); virtual;
function CheckForClose: boolean;
procedure FillCompletMenuFiltered;
procedure ProcCompletionLabel(nodo: TDOMNode);
procedure ReadSpecialIdentif;
private //Manejo de patrones de apertura
CompletLists: TFaCompletionLists; //colección de listas de compleatdo
function FindOpenEventMatching: TFaOpenEvent;
function GetIconList: TImageList;
procedure ProcXMLOpenOn(nodo: TDOMNode);
procedure SetIconList(AValue: TImageList);
public //Manejo de patrones de apertura
OpenEvents : TFaOpenEvents; //lista de eventos de apertura
CurOpenEve : TFaOpenEvent; //evento de apertura que se aplica en el momento
function FindOpenEvent(oeName: string): TFaOpenEvent; //Busca un evento de apertura
public
CompletionOn: boolean; //activa o desactiva el auto-completado
SelectOnEnter: boolean; //habilita la selección con enter
CaseSensComp: boolean; //Uso de caja, en autocompletado
OpenOnKeyUp: boolean; //habilita que se abra automáticamente al soltar una tecla
property IconList: TImageList read GetIconList write SetIconList;
function AddOpenEvent(AfterPattern, BeforePattern: string;
filter: TFaFilterList): TFaOpenEvent;
function AddComplList(lstName: string): TFaCompletionList;
function GetListByName(lstName: string): TFaCompletionList;
procedure LoadFromFile(const Filename: string); override;
function LoadSyntaxFromPath(SourceFile: string; path: string;
CaseSens: boolean=false): string;
procedure SelectEditor(ed0: TSynEdit); //inicia la ayuda contextual
procedure UnSelectEditor; //termina la ayuda contextual con el editor
procedure UTF8KeyPress(Sender: TObject; var UTF8Key: TUTF8Char);
procedure KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure OpenCompletionWindow(vKey: word; vShift: TShiftState; vUtfKey: TUTF8Char
);
procedure CloseCompletionWindow;
public //Constructor y Destructor
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
//utilidades
function ReadExtenFromXML(XMLfile: string): string;
function XMLFileHaveExten(XMLfile: string; exten: string;
CaseSens: boolean = false): boolean;
implementation
uses SynEditMiscProcs;
const
// ERR_ATTRIB_NO_EXIST = 'Atributo %s no existe. (etiqueta <COMPLETION ...>)';
// ERR_FILTER_NO_EXIST = 'Filtro %s no existe. (etiqueta <OpenOn ...>)';
// ERR_INVAL_LAB_COMP = 'Etiqueta %s no válida para etiqueta <COMPLETION ...>';
// ERR_INVAL_BLK_NAME = 'Nombre de bloque inválido.';
// ERROR_LOADING_ = 'Error loading: ';
// ERR_PAT_EXP_ENDSTR = 'Se esperaba fin de cadena';
// ERR_PAT_INVALID_ = 'Invalido: %s';
// ERR_PAT_SYNTAX_ERR = 'Error de sintaxis.';
// ERR_PAT_TOO_MAN_EL = 'Demasiados elementos.';
// ERR_PAR_AFT_PATT = 'Error en "BeforePattern"';
// ERR_PAR_BEF_PATT = 'Error en "AfterPattern"';
ERR_ATTRIB_NO_EXIST = 'Attribute %s doesn''t exist. (label <OpenOn ...>)';
ERR_LIST_NO_EXIST = 'List "%s" doesn''t exist. (label <OpenOn ...>)';
ERR_FILTER_NO_EXIST = 'Filter %s doesn''t exist. (label <OpenOn ...>)';
ERR_ACTION_NO_EXIST = 'Action %s doesn''t exist. (label <OpenOn ...>)';
ERR_INVAL_LAB_OPNON = 'Invalid label %s for <OpenOn ...>';
ERR_INVAL_LAB_COMP = 'Invalid label %s for <COMPLETION ...>';
ERR_INVAL_BLK_NAME = 'Invalid block name.';
ERROR_LOADING_ = 'Error loading: ';
ERR_PAT_EXP_ENDSTR = 'Expected end of string';
ERR_PAT_INVALID_ = 'Invalid: %s';
ERR_PAT_SYNTAX_ERR = 'Syntax error.';
ERR_PAT_TOO_MAN_EL = 'Too many elements.';
ERR_PAR_AFT_PATT = 'Error in "AfterPattern"';
ERR_PAR_BEF_PATT = 'Error in "BeforePattern"';
//Constantes para manejar parámetros de <OpenOn>
WORD_CHARS = ['a'..'z','0'..'9','A'..'Z','_'];
STR_DELIM = ['''','"'];
ALL_IDENTIF = 'AllIdentifiers';
//Para el reconocimiento de identificadores, cuando se usa "fil_LastIdent" y "fil_LastIdentPart"
CHAR_STRT_IDEN = ['a'..'z','A'..'Z','_'];
CHAR_BODY_IDEN = CHAR_STRT_IDEN + ['0'..'9'];
function ReadExtenFromXML(XMLfile: string): string;
//Lee las extensiones que tiene definidas un archivo de sintaxis.
var doc: TXMLDocument;
atri: TDOMNode;
i: Integer;
begin
try
Result := ''; //por defecto
ReadXMLFile(doc, XMLfile); //carga archivo
//busca el parámetro "ext"
for i:= 0 to doc.DocumentElement.Attributes.Length-1 do begin
atri := doc.DocumentElement.Attributes.Item[i];
if UpCase(atri.NodeName) = 'EXT' then begin
Result := trim(AnsiString(atri.NodeValue)); //valor sin espacios
end;
end;
doc.Free; //libera
except
on E: Exception do begin
ShowMessage(ERROR_LOADING_ + XMLfile + #13#10 + e.Message);
doc.Free;
end;
end;
end;
function XMLFileHaveExten(XMLfile: string; exten: string;
CaseSens: boolean = false): boolean;
//Indica si un archivo XML de sintaxis, tiene definida la extensión que se indica
//La comparación se puede hacer con o sin diferecnia de caja
var
lext: TStringList;
s: String;
tmp: String;
begin
Result := false; //por defecto
lext:= TStringList.Create; //crea lista
tmp := ReadExtenFromXML(XMLfile); //lee archivo
lext.DelimitedText:=' ';
lext.Text:=tmp; //divide
//busca de acuerdo a la caja
if CaseSens then begin
for s in lext do begin
if s = exten then begin
//encontró
Result := true;
lext.Free;
exit;
end;
end;
end else begin
for s in lext do begin
if Upcase(s) = Upcase(exten) then begin
//encontró
Result := true;
lext.Free;
exit;
end;
end;
end;
//No enecontró
lext.Free;
end;
{ TFaCompletItem }
function TFaCompletItem.ExtractField(var str: string): string;
{Extrae un campo de la cadena. Los campos deben estar delimitado con "|" sin caracter
de escape}
function EscapeBefore(i: integer): boolean; inline;
begin
if i<1 then exit(false);
if str[i-1] = '\' then exit(true) else exit(false);
end;
var
psep: SizeInt;
begin
psep := pos('|', str);
if (psep = 0) or EscapeBefore(psep) then begin
//no hay separador de campos, es un caso simple
Result := str;
str := '';
end else begin
//hay separador
Result:=copy(str,1,psep-1);
str := copy(str, psep+1, length(str));
end;
end;
procedure TFaCompletItem.SetCaption(AValue: string);
{Asigna el valor a Caption, separando los campos si es que vinieran codificados}
{Recibe una cadena que representa a un ítem y de el extrae los campos, si es que vinieran
codificados. El formato de la codificiacón es:
<texto a mostrar> | <texto a reemplazar> | <descripción>
}
function ExecEscape(const s: string): string;
{Reemplaza las secuencias de escape para mostrarlas en el menú de completado.
Tal vez convenga hacer este reemplazo, en la rutina que muestra los ítems, por un
tema de velocidad.}
begin
Result := StringReplace(s, '\n', ' ', [rfReplaceAll]);
Result := StringReplace(Result, '\t', ' ', [rfReplaceAll]);
Result := StringReplace(Result, '\u', ' ', [rfReplaceAll]);
Result := StringReplace(Result, '\\', '\', [rfReplaceAll]);
Result := StringReplace(Result, '\|', '|', [rfReplaceAll]);
Result := StringReplace(Result, '\_', '', [rfReplaceAll]);
end;
var
txt1, txt2: String;
begin
if fCaption=AValue then Exit;
txt1 := ExtractField(AValue);
if AValue='' then begin
//solo hay un campo
fCaption :=ExecEscape(txt1);
Replac :=txt1; //los caracteres de escape se expandirán al reemplazar
Descrip :='';
end else begin
//hay al menos otro campo
txt2 := ExtractField(AValue);
if AValue = '' then begin
//hay solo dos campos
fCaption :=ExecEscape(txt1);
Replac := txt2; //los caracteres de escape se expandirán al reemplazar
Descrip :='';
end else begin
//has 3 o más campos
fCaption :=ExecEscape(txt1);
Replac := txt2; //los caracteres de escape se expandirán al reemplazar
Descrip := ExecEscape(AValue);
end;
end;
end;
function TFaCompletItem.StartWith(const c: char): boolean;
begin
Result := (fCaption<>'') and (fCaption[1] = c);
end;
function TFaCompletItem.StartWithU(const c: char): boolean;
begin
Result := (fCaption<>'') and (UpCase(fCaption[1]) = c);
end;
{ TFaCompletionList }
procedure TFaCompletionList.AddItems(list: string; idxIcon: integer);
//Agrega una lista de ítems, separados por espacios, a la lista de completado
var
lst: TStringList;
i: Integer;
it: TFaCompletItem;
begin
//divide
lst := TStringList.Create;
lst.Delimiter := ' ';
lst.DelimitedText := list;
//agrega
for i:= 0 to lst.Count-1 do begin
it := TFaCompletItem.Create;
it.Caption := lst[i];
it.idxIcon:=idxIcon;
Items.Add(it);
end;
lst.Destroy;
end;
constructor TFaCompletionList.Create;
begin
Items:= TFaCompletItems.Create(true); //lista con administración
end;
destructor TFaCompletionList.Destroy;
begin
Items.Destroy;
inherited Destroy;
end;
{ TFaCursorEnviron }
constructor TFaCursorEnviron.Create(hlt0: TSynFacilSyn);
begin
hlt := hlt0;
end;
procedure TFaCursorEnviron.LookAround(ed: TSynEdit; CaseSen0: boolean);
{Analiza el estado del cursor en el editor. Se supone que se debe llamar, después de
actualizar el editor. Actualiza: PosiCursor, curBlock, tok0, tok_1, tok_2
y tok_3. Utiliza punteros, para evitar perder tiempo creando copias.}
var
iTok0 : integer; //índice al token actual
begin
caseSen:=CaseSen0; //actualiza estado
//valores por defecto
curBlock := nil;
//explora la línea con el resaltador
hlt.ExploreLine(ed.CaretXY, tokens, iTok0);
curLine := ed.Lines[ed.CaretY-1]; //Se gaurda porque se va a necesitar
if iTok0=-1 then exit; //no ubica al token actual
tok0 := @tokens[iTok0]; //lee token actual token[0]
CurX := ed.LogicalCaretXY.x; //usa posición física para comparar
CurY := ed.LogicalCaretXY.y;
inMidTok := tokens[iTok0].posIni+1 <> CurX; //actualiza bandera
//actualiza tok_1
if inMidTok then begin
tok_1 := @tokens[iTok0];
if iTok0>0 then tok_2 := @tokens[iTok0-1]
else tok_2 := nil;
if iTok0>1 then tok_3 := @tokens[iTok0-2]
else tok_3 := nil;
end else begin
if iTok0>0 then tok_1 := @tokens[iTok0-1]
else tok_1 := nil;
if iTok0>1 then tok_2 := @tokens[iTok0-2]
else tok_2 := nil;
if iTok0>2 then tok_3 := @tokens[iTok0-3]
else tok_3 := nil;
end;
//captura "curBlock"
curBlock := tok0^.curBlk; //devuelve bloque
{$IFDEF Verbose}
DbgOut(' LookAround:(');
if tok_3<>nil then DbgOut(hlt.Attrib[tok_3^.TokTyp].Name+',');
if tok_2<>nil then DbgOut(hlt.Attrib[tok_2^.TokTyp].Name+',');
if tok_1<>nil then DbgOut(hlt.Attrib[tok_1^.TokTyp].Name+',');
if tok0<>nil then DbgOut(hlt.Attrib[tok0^.TokTyp].Name);
debugln(')');
{$ENDIF}
end;
{Las siguientes funciones, deben llamarse después de lamar a LookAround(). Deben ser de
ejecución rápida}
function TFaCursorEnviron.HaveLastTok: boolean; inline;
begin
Result := (tok_1 <> nil);
end;
function TFaCursorEnviron.LastTok: string; inline;
{Devuelve el último token}
begin
Result := tok_1^.txt;
end;
function TFaCursorEnviron.LastTokPart: string; inline;
{Devuelve el último token, truncado a la posición del cursor}
begin
// Result := copy(tok0^.txt,1,CurX-tok0^.posIni-1);
Result := copy(tok_1^.txt, 1, CurX-tok_1^.posIni-1);
end;
procedure TFaCursorEnviron.UpdateStartIdentif;
{Actualiza el índice al inicio del identificador anterior, a la posición actual del cursor.
Este es un algoritmo, un poco especial, porque los identificadores no se
definen para explorarlos hacia atrás.}
var
i: Integer;
begin
StartIdentif := -1; //valor por defecto
if CurX<=1 then exit; //está al inicio
i:= CurX-1; //caracter anterior al cursor
{Se asume que el cursor, está después de un identificador y retrocede por los
caracteres hasta encontrar un caracter que pueda ser inicio de identificador}
while (i>0) and (curLine[i] in CHAR_BODY_IDEN) do begin
if curLine[i] in CHAR_STRT_IDEN then begin
StartIdentif := i; //guarda una posible posición de inicio
end;
dec(i);
end;
end;
function TFaCursorEnviron.HaveLastIdent: boolean;
{Indica si hay un identificador antes del cursor. Debe llamarse siempre antes de
usar LastIdent().}
begin
UpdateStartIdentif;
Result := (StartIdentif <> -1);
end;
function TFaCursorEnviron.LastIdent: string;
{Devuelve el identificador anterior al cursor. Debe llamarse siempre despues de llamar
a HaveLastIdent}
var
i: Integer;
begin
{Ya sabemos que hay identificador hasta antes del cursor, ahora debemos ver, hasta
dónde se extiende}
i := CurX;
while curLine[i] in CHAR_BODY_IDEN do //no debería ser necesario verificar el final
inc(i);
Result := copy(curLine, StartIdentif, i-StartIdentif+1);
end;
function TFaCursorEnviron.LastIdentPart: string;
{Devuelve el identificador anterior al cursor. Debe llamarse siempre despues de llamar
a HaveLastIdent}
begin
Result := copy(curLine, StartIdentif, CurX-StartIdentif);
end;
{Estas funciones implementan las acciones. Procesan las secuencias de escape}
function TFaCursorEnviron.ExtractStaticText(var ReplaceSeq: string;
out seq: TFaCompletSeqType): string;
{Extrae un fragmento de texto de "ReplaceSeq", que puede insertarse directamente en el editor,
sin necesidad de hacer cálculos de posición, o que no contengan comandos de posicionamiento
del cursor. La idea es que el texto que se devuelva aquí, se pueda insertar directamente
en el editor con una simple operación "Insert". El tipo de secuencia que produjo la ruptura,
se devuelve en "seq"}
function ReplaceEscape(const s: string): string;
begin
Result := StringReplace(s, '\n', LineEnding, [rfReplaceAll]);
Result := StringReplace(Result, '\t', #9, [rfReplaceAll]);
Result := StringReplace(Result, '\|', '|', [rfReplaceAll]);
Result := StringReplace(Result, '\\', '\', [rfReplaceAll]);
end;
function FirstPos(substrs: array of string; str: string; out found: string): integer;
{Busca la ocurrencia de cualquiera de las cadenas dadas en "substrs". Devuelve el índice
a la primera encontrada. Si no enceuntra ninguna, devuelve 0.}
var
i, p: Integer;
limit: Integer;
lin: string;
begin
Result := 0; //valor inicial
found := '';
limit := length(str);
for i:=0 to high(substrs) do begin
lin := copy(str, 1, limit);
p := Pos(substrs[i], lin);
if p<>0 then begin
//encontró uno, compara
if p<limit then begin
limit := p; //restringe límite para la siguiente búsqueda
found := substrs[i]; //lo que enontró
Result := p;
end;
end;
end;
end;
var
p: Integer;
hay: string;
begin
//Detcta las secuencias de posición de cursor, o tabulación '\u'.
p := FirstPos(['\_','\u'], ReplaceSeq, hay); //tabulación al primer caracter no blanco de la línea superior no blanca
if hay = '' then begin
//No hay secuecnia especial
Result := ReplaceEscape(ReplaceSeq);
seq := csqNone; //no se ecnontró secuencia de ruptura
ReplaceSeq := '';
end else if hay = '\_' then begin
//primero está la secuencia de cursor
Result := ReplaceEscape(copy(ReplaceSeq,1,p-1));
seq := csqCurPos; //Indica secuencia de posicionamiento de cursor
ReplaceSeq := copy(ReplaceSeq, p+2, length(ReplaceSeq));
end else if hay = '\u' then begin
//primero está la secuencia de tabulación
Result := ReplaceEscape(copy(ReplaceSeq,1,p-1));
seq := csqTabSpa; //Indica secuencia
ReplaceSeq := copy(ReplaceSeq, p+2, length(ReplaceSeq));
end;
end;
procedure TFaCursorEnviron.InsertSequence(ed: TSynEdit; Pos1, Pos2: TPoint; ReplaceSeq: string);
{Inserta una secuencia de reemplazo en el bloque definido por P1 y P2}
function FindNoWhiteLineUp(ed: TSynEdit): string;
{Busca hacia arriba, una línea con caracteres diferentes de espacio y que ocupen una posición
más a la derecha de la posición actual del cursor. La búsqueda se hace a partir de la
posición actual del cursor. Si no encuentra, devuelve línea en blanco.}
var
x,y: Integer;
lin: String;
begin
y := ed.CaretY-1; //desde la línea anterior
x := ed.CaretX;
while y>0 do begin
lin := ed.Lines[y-1];
if trim(copy(lin,x, length(lin)))<>'' then
exit(lin);
dec(y);
end;
//no encontró
exit('');
end;
var
toRepl: String;
cursorPos: TPoint;
seq: TFaCompletSeqType;
linNoWhite, curLin: String;
i, dif: Integer;
begin
ed.BeginUndoBlock;
ed.TextBetweenPointsEx[Pos1,Pos2, scamEnd] := ''; //elimina el contenido y deja cursor al final
cursorPos.x := -1; //marca bandera
while ReplaceSeq<>'' do begin
toRepl := ExtractStaticText(ReplaceSeq, seq);
case seq of
csqNone: begin
//no hay ruptura, es un texto sencillo
//reemplaza y deja cursor al final
Pos1 := ed.CaretXY;
ed.TextBetweenPointsEx[Pos1,Pos1, scamEnd] := toRepl;
//se suepone que esta es la última secuencia
end;
csqCurPos: begin
//hay comando de posicionamiento de cursor
//reemplaza, deja cursor al final y guarda posición
Pos1 := ed.CaretXY;
ed.TextBetweenPointsEx[Pos1,Pos1, scamEnd] := toRepl;
cursorPos := ed.CaretXY;
end;
csqTabSpa: begin
//hay comando de tabulación inteligente
Pos1 := ed.CaretXY;
//inserta fragmento
ed.TextBetweenPointsEx[Pos1,Pos1, scamEnd] := toRepl;
//calcula espaciado
linNoWhite := FindNoWhiteLineUp(ed);
if linNoWhite<>'' then begin
//hay línea sin blancos, busca posición de caracter no blanco
for i:=ed.CaretX to length(linNoWhite) do begin
//La definición de blanco #1..#32, corresponde al resaltador
if not (linNoWhite[i] in [#1..#32]) then begin
//Encontró. Ahora hay que posicionar el cursor en "i".
curLin := ed.LineText; //línea actual
if length(curLin)<i then begin
//No hay caracteres, en donde se quiere colocar el cursor.
dif := i - length(curLin); //esto es lo que falta
ed.CaretX := length(curLin)+1; //pone cursor al final
ed.InsertTextAtCaret(Space(dif)); {Completa con espacios. Usa InsertTextAtCaret,
para poder deshacer.}
ed.CaretX:=i; //ahora sí se puede posicionar el cursor.
end else begin
//Se puede posicionar directamente
ed.CaretX:=i;
end;
break; //sale del FOR
end;
end;
{Si llega aquí sin encontrar el caracter buscado, indicaria que el
algoritmo de búsqueda de FindNoWhiteLineUp() no es consistente con este código.}
end;
end;
end;
end;
if cursorPos.x<>-1 then begin
//ha habido posicionamiento de cursor
ed.CaretXY := cursorPos;
end;
ed.EndUndoBlock;
end;
procedure TFaCursorEnviron.ReplaceLastTok(ed: TSynEdit; ReplaceSeq: string);
{Reemplaza el último token}
var
Pos1, Pos2: TPoint;
begin
if not HaveLastTok then exit;
Pos1 := Point(tok_1^.posIni + 1, CurY);
Pos2 := Point(tok_1^.posIni + tok_1^.length+1, CurY);
//Realiza el reemplazo del texto, con procesamiento
InsertSequence(ed, Pos1, Pos2, ReplaceSeq);
end;
procedure TFaCursorEnviron.ReplaceLastIden(ed: TSynEdit; ReplaceSeq: string);
{Reemplaza el último identificador}
var
Pos1, Pos2: TPoint;
i: Integer;
begin
if not HaveLastIdent then exit;
Pos1 := Point(StartIdentif, CurY);
i := CurX;
while (i<=length(curLine)) and (curLine[i] in CHAR_BODY_IDEN) do
inc(i);
Pos2 := Point(i, CurY);
InsertSequence(ed, Pos1, Pos2, ReplaceSeq);
end;
procedure TFaCursorEnviron.Insert(ed: TSynEdit; ReplaceSeq: string);
{Reemplaza un texto en la posición actual del cursor}
var
Pos1: TPoint;
begin
Pos1 := Point(CurX, CurY);
InsertSequence(ed, Pos1, Pos1, ReplaceSeq);
end;
{ TFaOpenEvent }
procedure TFaOpenEvent.DoAction(ed: TSynEdit; env: TFaCursorEnviron;
ReplaceSeq: string);
{Ejecuta la acción que se tenga definido para el evento de apertura}
begin
case Action of
pac_None:; //no ahce nada
pac_Default: //acción por defecto
case Filter of
fil_None: ; //no hay elemento de selcción
fil_LastTok,
fil_LastTokPart: //trabaja con el último token
env.ReplaceLastTok(ed, ReplaceSeq);
fil_LastIdent,
fil_LastIdentPart: //trabaja con el úmtimo identificador
env.ReplaceLastIden(ed, ReplaceSeq);
end;
pac_Insert: //inserta
env.Insert(ed, ReplaceSeq);
pac_Rep_LastTok:
env.ReplaceLastTok(ed, ReplaceSeq);
{Se pueden completar más acciones}
else
env.ReplaceLastTok(ed, ReplaceSeq);
end;
end;
procedure TFaOpenEvent.FillFilteredIn(const env: TFaCursorEnviron; lst: TStrings);
{Filtra los ítems que contiene (usando "env") y los pone en la lista indicada}
procedure FilterBy(const str: string);
//Llena el menú de completado a partir de "Avails", filtrando solo las
//palabras que coincidan con "str"
var
l: Integer;
it: TFaCompletItem;
str2: String;
begin
l := length(str);
//Genera la lista que coincide
if env.caseSen then begin
for it in Avails do begin
//esta no es la forma más eficiente de comparar, pero sirve por ahora.
if str = copy(it.fCaption,1,l) then
// lst.Add(Avails[i]^.text);
lst.AddObject(it.fCaption, it);
end;
end else begin //ignora la caja
str2 := UpCase(str);
for it in Avails do begin
if str2 = upcase(copy(it.fCaption,1,l)) then begin
// lst.Add(Avails[i]^.text);
lst.AddObject(it.fCaption, it);
end;
end;
end;
end;
var
it: TFaCompletItem;
begin
case Filter of
fil_None: begin //agrega todos
for it in Avails do begin //agrega sus ítems
lst.AddObject(it.fCaption, it);
end;
end;
fil_LastTok: begin //último token
if env.HaveLastTok then
FilterBy(env.LastTok);
end;
fil_LastTokPart: begin //último token hasta el cursor
if env.HaveLastTok then
FilterBy(env.LastTokPart);
end;
fil_LastIdent: begin //último token
if env.HaveLastIdent then
FilterBy(env.LastIdent);
end;
fil_LastIdentPart: begin //último token hasta el cursor
if env.HaveLastIdent then
FilterBy(env.LastIdentPart);
end;
end;
end;
//manejo de elementos de patrones
procedure TFaOpenEvent.ClearBeforePatt;
begin
elem[-3].patKind := pak_none;
elem[-2].patKind := pak_none;
elem[-1].patKind := pak_none;
nBef:=0; //no hay elementos válidos
end;
procedure TFaOpenEvent.ClearAfterPatt;
begin
elem[0].patKind := pak_none;
nAft:=0; //no hay
end;
procedure TFaOpenEvent.ExtractElementIn(var befPat: string; patEle:TFaPatternElementPtr;
var ErrStr: string);
{Extrae un elemento de un patrón de tokens que viene en cadena y lo almacena en "patEle".
La cadena puede ser algo así como "Identifier,'.',AllIdentifiers".
Si encuentra error, devuelve el mensaje en "ErrStr".}
function ExtractIdentifier(var befPat: string): string;
{Extrae un identificador de la cadena "befPat"}
var
i: Integer;
begin
i := 1;
while (i<=length(befPat)) and (befPat[i] in WORD_CHARS) do begin
inc(i);
end;
Result := copy(befPat, 1,i-1); //extrae cadena
befPat := copy(befPat, i, length(befPat)); //recorta
end;
function ExtractString(var befPat: string; var ErrStr: string): string;
{Extrae una cadena de "befPat". Si hay error devuelve mensaje en "ErrStr"}
var
i: Integer;
ci: Char;
begin
ci := befPat[1]; //caracter inicial
i := 2;
while (i<=length(befPat)) and (befPat[i] <> ci) do begin
inc(i);
end;
if i>length(befPat) then begin
ErrStr := ERR_PAT_EXP_ENDSTR;
exit;
end;
Result := copy(befPat, 1,i); //extrae cadena
befPat := copy(befPat, i+1, length(befPat)); //recorta
end;
procedure ExtractChar(var befPat: string);
begin
befPat := copy(befPat, 2, length(befPat));
end;
procedure ExtractComma(var befPat: string);
begin
befPat := TrimLeft(befPat); //quita espacios
//quita posible coma final
if (befPat<>'') and (befPat[1] = ',') then
befPat := copy(befPat, 2, length(befPat));
end;
var
strElem: String;
begin
if befPat[1] in WORD_CHARS then begin
//Es un identificador: tipo de token o la cadena especial "AllIdentifiers"
strElem := ExtractIdentifier(befPat);
if upcase(strElem) = upcase(ALL_IDENTIF) then begin
//es de tipo "Todos los identificadores"
patEle^.patKind := pak_Identif;
end else if hlt.IsAttributeName(strElem) then begin //es
//Es nombre de tipo de token
patEle^.patKind := pak_TokTyp;
patEle^.toktyp := hlt.GetAttribIDByName(strElem); //tipo de atributo
end else begin //no es, debe haber algún error
ErrStr := Format(ERR_PAT_INVALID_,[strElem]);
exit;
end;
ExtractComma(befpat);
end else if befPat[1] = '!' then begin
//debe ser de tipo "No es ..."
ExtractChar(befPat);
strElem := ExtractIdentifier(befPat);
if upcase(strElem) = upcase(ALL_IDENTIF) then begin
//es de tipo "Todos los identificadores"
patEle^.patKind := pak_NoIdentif;
end else if hlt.IsAttributeName(strElem) then begin
//Es nombre de tipo de token
patEle^.patKind := pak_NoTokTyp;
patEle^.toktyp := hlt.GetAttribIDByName(strElem); //tipo de atributo
end else begin //no es, debe haber algún error
ErrStr := Format(ERR_PAT_INVALID_,[strElem]);
exit;
end;
ExtractComma(befpat);
end else if befPat[1] in STR_DELIM then begin
//es un literal cadena
strElem := ExtractString(befPat, ErrStr);
if ErrStr<>'' then exit;
patEle^.patKind := pak_String;