#help_index "AutoComplete/Dictionary"
U0 ACDDictWordsAdd(U8 *st)
{
	I64 i;
	U8 *ptr;

	if (st && *st && (ptr = ACDWordPtAt(st)))
	{
		for (i = 0; i < ACD_FILLINS_NUM; i++)
		{
			if (*ptr++ != ACD_WORD_CHAR)
				break;
			if (i)
				'\n';
			acd.fillins[i] = ptr - 1;
			"$$GREEN$$'%d'$$FG$$ $$BLACK$$%-23ts$$FG$$", i, ptr;
			ptr += StrLen(ptr) + 3;
		}
		acd.num_fillins = i;
	}
}

#help_index "AutoComplete"
U0 ACDocReset(I64 left, I64 top)
{
	CDoc *doc = DocPut;

	DocReset(doc, TRUE);
	doc->flags |= DOCF_SIZE_MIN;
	Fs->border_src  = BDS_CONST;
	Fs->border_attr = LTGRAY << 4 + DriveTextAttrGet(':') & 15;
	Fs->text_attr   = LTGRAY << 4 + BLUE;
	LBtr(&Fs->display_flags, DISPLAYf_SHOW);
	WinHorz(left, Fs->win_right);
	WinVert(top,  Fs->win_bottom);
	DocCursor;
}

I64 ACSkipCrap(U8 *src, I64 len)
{
	I64 j;

	j = len - 1;
	while (j >= 0)
	{
		if (Bt(char_bmp_alpha_numeric, src[j]))
			break;
		else
			j--;
	}
	return j + 1;
}

I64 ACPriorWordInStr(U8 *src, U8 *dst, I64 len, I64 buf_size)
{
	I64 i, j = 0, k;

	i = len - 1;
	while (i >= 0)
		if (!Bt(char_bmp_alpha_numeric, src[i]))
			break;
		else
			i--;
	if (i >= -1 && len > 0)
		for (k = i + 1; k < len && j < buf_size - 1; k++)
			dst[j++] = src[k];
	dst[j] = 0;
	return i + 1;
}

U0 ACFillInAdd(CHashAC *tmpw)
{
	I64 k;

	if (ac.num_fillins < AC_FILLINS_NUM || tmpw->hits > ac.fillin_hits[ac.num_fillins - 1])
	{
		for (k = ac.num_fillins - 1; k >= 0; k--)
		{
			if (tmpw->hits <= ac.fillin_hits[k])
				break;
			else
			{
				ac.fillin_matches[k + 1] = ac.fillin_matches[k];
				ac.fillin_hits[k + 1]	 = ac.fillin_hits[k];
			}
		}
		ac.fillin_matches[k + 1] = tmpw;
		ac.fillin_hits[k + 1]	 = tmpw->hits;
		if (ac.num_fillins < AC_FILLINS_NUM)
			ac.num_fillins++;
	}
}

U0 ACPutChoices(CDoc *focus_l, CDocEntry *doc_e, CTask *focus_task, Bool force_refresh)
{
	I64			 i, data_col;
	U8			*buf, *buf1, *src = NULL, *st;
	CHashAC		*tmpw;
	F64			 timeout_time = tS + 0.5;
	CHashSrcSym	*tmph;

	if (LBtr(&ac.flags, ACf_LAST_WAS_KEYMAP))
	{
		ac.col = ac.old_col;
		ac.row = ac.old_row;
	}
	else
	{
		ac.col = Fs->win_left;
		ac.row = Fs->win_top;
	}

	src = DocScanLine(focus_l, doc_e, &data_col);
	DocUnlock(focus_l);
	i = StrLen(src);
	buf  = MAlloc(MaxI64(i + 1, 256));
	buf1 = MAlloc(MaxI64(i + 1, 256));
	if (data_col == -1)
		data_col = 0;
	data_col = ACPriorWordInStr(src, buf, data_col, 256);
	ac.partial_len = StrLen(buf);
	data_col = ACSkipCrap(src, data_col);
	data_col = ACPriorWordInStr(src, buf1, data_col, 256);

	if (!ac.cur_word || StrCompare(ac.cur_word, buf) || force_refresh)
	{
		st = ac.cur_word;
		ac.cur_word = ZStrNew(buf);
		Free(st);
		ac.num_fillins = 0;
		if (*ac.cur_word)
			for (i = 0; i <= ac.hash_table->mask && tS < timeout_time; i++)
			{
				tmpw = ac.hash_table->body[i];
				while (tmpw)
				{
					if (!MemCompare(ac.cur_word, tmpw->str, StrLen(ac.cur_word)))
						ACFillInAdd(tmpw);
					tmpw = tmpw->next;
				}
			}
		ACDocReset(ac.col, ac.row);
		if (ac.cur_word && *ac.cur_word)
		{
			"$$RED$$Word:%s$$FG$$\n", ac.cur_word;
			for (i = 0; i < ac.num_fillins; i++)
			{
				st = ac.fillin_matches[i]->str;
				"$$GREEN$$F%02d$$FG$$$$HL$$ ", i + 1;
				if (TaskValidate(focus_task) && (tmph = HashFind(st, focus_task->hash_table, HTG_SRC_SYM)) && tmph->src_link)
				{
					if (tmph->type & HTF_PUBLIC)
						"$$IV$$";
					"$$TX+UL+L+PU,\"%$$Q\",A=\"%s\"$$$$IV,0$$\n", st, tmph->src_link;
				}
				else
					"%s\n", st;
				"$$HL,0$$";
			}
			if (acd.has_words)
				ACDDictWordsAdd(ac.cur_word);
		}
		else if (FileFind("::/Doc/StandBy.DD"))
			Type("::/Doc/StandBy.DD", 0);
	}
	Free(src);
	Free(buf);
	Free(buf1);
}

U0 ACTaskNormal(I64 sc, I64 last_sc, CTask *focus_task, CTask *original_focus_task)
{
	CDoc	  *doc;
	CDocEntry *doc_e;

	if ((doc=DocPut(focus_task)) && focus_task != Fs && Bt(&focus_task->display_flags, DISPLAYf_SHOW))
	{
		DocLock(doc);
		if (TaskValidate(focus_task) &&
			original_focus_task == sys_focus_task &&
			doc &&
			doc == DocPut(focus_task) &&
			(doc_e = doc->cur_entry))
		{
			if (doc_e == doc)
				doc_e = doc_e->last;
			while (doc_e->last != doc && (doc_e->type_u8 == DOCT_NEW_LINE || doc_e->type_u8 == DOCT_SOFT_NEW_LINE))
				doc_e = doc_e->last;
			while (doc_e->last->type_u8 != DOCT_NEW_LINE && doc_e->last != doc)
				doc_e = doc_e->last;
			ACPutChoices(doc, doc_e, focus_task, ToBool(sc != last_sc));
		}
		else
			DocUnlock(doc);
	}

	if (!LBts(&Fs->display_flags, DISPLAYf_SHOW))
		WinZBufUpdate;
}

U0 ACTaskCtrl(I64 sc, I64 last_sc, CTask *focus_task, CTask *original_focus_task)
{
	if (TaskValidate(focus_task) && (focus_task->scroll_x || focus_task->scroll_y))
	{
		if (LBtr(&Fs->display_flags, DISPLAYf_SHOW))
			WinZBufUpdate;
	}
	else
	{
		if (sc != last_sc)
		{
			Bts(&ac.flags, ACf_LAST_WAS_KEYMAP);
			ac.old_col = ac.col;
			ac.old_row = ac.row;
			if (sc & SCF_ALT)
			{
				ACDocReset(TEXT_COLS - 50, 3);

				if (TaskValidate(original_focus_task) && !Bt(&original_focus_task->win_inhibit, WIf_SELF_KEY_DESC))
					KeyMapFamily(original_focus_task, 0, ToBool(!(sc & SCF_SHIFT)), ToBool(sc & SCF_SHIFT));

				KeyMapCtrlAltFamily(ToBool(!(sc & SCF_SHIFT)), ToBool(sc & SCF_SHIFT));
			}
			else if (TaskValidate(original_focus_task) && !Bt(&original_focus_task->win_inhibit, WIf_SELF_KEY_DESC))
			{
				ACDocReset(TEXT_COLS - 50, 3);
				KeyMapFamily(original_focus_task, SCF_CTRL, ToBool(!(sc & SCF_SHIFT)), ToBool(sc & SCF_SHIFT));
			}
		}

		if (!LBts(&Fs->display_flags, DISPLAYf_SHOW))
			WinZBufUpdate;
	}
}

U0 ACTaskAlt(I64 sc, I64 last_sc, CTask *, CTask *original_focus_task)
{
	if (sc != last_sc && TaskValidate(original_focus_task) && !Bt(&original_focus_task->win_inhibit, WIf_SELF_KEY_DESC))
	{
		Bts(&ac.flags, ACf_LAST_WAS_KEYMAP);
		ac.old_col = ac.col;
		ac.old_row = ac.row;

		ACDocReset(TEXT_COLS - 50, 3);
		KeyMapFamily(original_focus_task, SCF_ALT, ToBool(!(sc & SCF_SHIFT)), ToBool(sc & SCF_SHIFT));
	}
	if (!LBts(&Fs->display_flags, DISPLAYf_SHOW))
		WinZBufUpdate;
}

U0 ACTaskEndCB()
{
	ac.task = NULL;
	RegWrite("AutoComplete", "ac.col=%d;ac.row=%d;", ac.col, ac.row);
	Exit;
}

U0 ACTask(I64)
{
	CTask	*focus_task, *original_focus_task;
	I64		 ch, scan_code = 0, last_scan_code = 0;
	CDoc	*doc;

	Fs->task_end_cb = &ACTaskEndCB;
	DocTermNew;
	LBts(&Fs->display_flags, DISPLAYf_SHOW);
	ACDocReset(ac.col, ac.row);
	LBts(&Fs->display_flags, DISPLAYf_WIN_ON_TOP);
	Fs->win_inhibit = WIG_NO_FOCUS_TASK_DEFAULT;
	Free(ac.cur_word);
	ac.cur_word = NULL;

	while (TRUE)
	{
		if (scan_code & (SCF_CTRL | SCF_ALT) || TSCGet > KbdMouseEventTime + counts.time_stamp_freq >> 1)
		{
			last_scan_code = scan_code;
			scan_code = kbd.scan_code;
		}
		original_focus_task = focus_task = sys_focus_task;
		while (TaskValidate(focus_task) && Bt(&focus_task->task_flags, TASKf_INPUT_FILTER_TASK))
			focus_task = focus_task->parent_task;
		if (scan_code & SCF_CTRL)
			ACTaskCtrl(scan_code, last_scan_code, focus_task, original_focus_task);
		else if (TaskValidate(focus_task))
		{
			if (scan_code & SCF_ALT)
				ACTaskAlt(scan_code, last_scan_code, focus_task, original_focus_task);
			else
				ACTaskNormal(scan_code, last_scan_code, focus_task, original_focus_task);
		}
		Sleep(333);
		if (MessageScan(&ch,, 1 << MESSAGE_KEY_DOWN) && (ch == CH_ESC || ch == CH_SHIFT_ESC))
			break;
		doc = DocPut;
		DocLock(doc);
		if (doc->cur_entry->de_flags & DOCEF_LINK)
		{
			'' CH_SPACE;
			doc->cur_entry = doc;
		}
		DocUnlock(doc);
	}
}

public Bool AutoComplete(Bool val=OFF)
{//Turn AutoComplete OFF or ON.
	Bool old_val = FALSE;

	while (Bt(&ac.flags, ACf_INIT_IN_PROGRESS))
		Sleep(10);
	if (val)
	{
		if (Bt(&sys_run_level, RLf_AUTOCOMPLETE))
		{
			if (TaskValidate(ac.task))
				old_val = TRUE;
			else
			{
				ac.task = Spawn(&ACTask, NULL, "AutoComplete");
				TaskWait(ac.task);
			}
			WinToTop(ac.task);
		}
	}
	else
	{
		if (TaskValidate(ac.task))
		{
			if (Bt(&sys_run_level, RLf_AUTOCOMPLETE))
				old_val = TRUE;
			Kill(ac.task);
		}
	}
	return old_val;
}
