Browse Source

klishd: Completions

Serj Kalichev 1 year ago
parent
commit
8a626f740a

+ 10 - 0
examples/simple/example.xml

@@ -20,6 +20,16 @@
 	<ACTION sym="print">test</ACTION>
   </ENTRY>
 
+  <ENTRY name="cmd2" help="Clear settings" mode="sequence">
+	<ENTRY name="COMMAND" purpose="ptype" ref="/COMMAND"/>
+	<ACTION sym="print">test cmd2</ACTION>
+  </ENTRY>
+
+  <ENTRY name="command" value="comm" help="Clear settings" mode="sequence">
+	<ENTRY name="COMMAND" purpose="ptype" ref="/COMMAND"/>
+	<ACTION sym="print">test2</ACTION>
+  </ENTRY>
+
   <ENTRY name="exit" help="Exit view">
 	<ENTRY name="COMMAND" purpose="ptype" ref="/COMMAND"/>
 	<ACTION sym="nav">pop</ACTION>

+ 1 - 0
klish/kcontext.h

@@ -70,6 +70,7 @@ const char *kcontext_script(const kcontext_t *context);
 bool_t kcontext_named_udata_new(kcontext_t *context,
 	const char *name, void *data, kudata_data_free_fn free_fn);
 void *kcontext_named_udata(const kcontext_t *context, const char *name);
+const kentry_t *kcontext_command(const kcontext_t *context);
 
 
 C_DECL_END

+ 10 - 0
klish/ksession/kcontext.c

@@ -223,6 +223,16 @@ void *kcontext_named_udata(const kcontext_t *context, const char *name)
 }
 
 
+const kentry_t *kcontext_command(const kcontext_t *context)
+{
+	assert(context);
+	if (!context)
+		return NULL;
+
+	return kpargv_command(kcontext_pargv(context));
+}
+
+
 kplugin_t *kcontext_plugin(const kcontext_t *context)
 {
 	const kaction_t *action = NULL;

+ 2 - 2
klish/ksession/ksession_parse.c

@@ -25,7 +25,7 @@
 
 static bool_t ksession_validate_arg(ksession_t *session, kpargv_t *pargv)
 {
-	const char *out = NULL;
+	char *out = NULL;
 	int retcode = -1;
 	const kentry_t *ptype_entry = NULL;
 	kparg_t *candidate = NULL;
@@ -684,7 +684,7 @@ static bool_t action_stdout_ev(faux_eloop_t *eloop, faux_eloop_type_e type,
 
 
 bool_t ksession_exec_locally(ksession_t *session, const kentry_t *entry,
-	kpargv_t *parent_pargv, int *retcode, const char **out)
+	kpargv_t *parent_pargv, int *retcode, char **out)
 {
 	kexec_t *exec = NULL;
 	faux_eloop_t *eloop = NULL;

+ 1 - 1
klish/ksession_parse.h

@@ -23,7 +23,7 @@ kexec_t *ksession_parse_for_exec(ksession_t *session, const char *raw_line,
 kexec_t *ksession_parse_for_local_exec(ksession_t *session,
 	const kentry_t *entry, const kpargv_t *parent_pargv);
 bool_t ksession_exec_locally(ksession_t *session, const kentry_t *entry,
-	kpargv_t *parent_pargv, int *retcode, const char **out);
+	kpargv_t *parent_pargv, int *retcode, char **out);
 
 C_DECL_END
 

+ 66 - 3
klish/ktp/ktpd_session.c

@@ -305,6 +305,7 @@ static bool_t ktpd_session_process_completion(ktpd_session_t *ktpd, faux_msg_t *
 	ktp_cmd_e cmd = KTP_COMPLETION_ACK;
 	uint32_t status = KTP_STATUS_NONE;
 	const char *prefix = NULL;
+	size_t prefix_len = 0;
 
 	assert(ktpd);
 	assert(msg);
@@ -329,11 +330,73 @@ static bool_t ktpd_session_process_completion(ktpd_session_t *ktpd, faux_msg_t *
 		status |= KTP_STATUS_EXIT; // Notify client about exiting
 	}
 
-	// Send ACK message
+	// Prepare ACK message
 	ack = ktp_msg_preform(cmd, status);
+
+	// Last unfinished word. Common prefix for all completions
 	prefix = kpargv_last_arg(pargv);
-	if (!faux_str_is_empty(prefix))
-		faux_msg_add_param(ack, KTP_PARAM_PREFIX, prefix, strlen(prefix));
+	if (!faux_str_is_empty(prefix)) {
+		prefix_len = strlen(prefix);
+		faux_msg_add_param(ack, KTP_PARAM_PREFIX, prefix, prefix_len);
+	}
+
+	// Fill msg with possible completions
+	if (!kpargv_completions_is_empty(pargv)) {
+		const kentry_t *candidate = NULL;
+		kpargv_completions_node_t *citer = kpargv_completions_iter(pargv);
+		while ((candidate = kpargv_completions_each(&citer))) {
+			const kentry_t *completion = NULL;
+			kparg_t *parg = NULL;
+			int rc = -1;
+			char *out = NULL;
+			bool_t res = BOOL_FALSE;
+			char *l = NULL; // One line of completion
+			const char *str = NULL;
+
+			// Get completion entry from candidate entry
+			completion = kentry_nested_by_purpose(candidate,
+				KENTRY_PURPOSE_COMPLETION);
+			// If candidate entry doesn't contain completion then try
+			// to get completion from entry's PTYPE
+			if (!completion) {
+				const kentry_t *ptype = NULL;
+				ptype = kentry_nested_by_purpose(candidate,
+					KENTRY_PURPOSE_PTYPE);
+				if (!ptype)
+					continue;
+				completion = kentry_nested_by_purpose(ptype,
+					KENTRY_PURPOSE_COMPLETION);
+			}
+			if (!completion)
+				continue;
+			parg = kparg_new(candidate, prefix);
+			kpargv_set_candidate_parg(pargv, parg);
+			res = ksession_exec_locally(ktpd->session, completion,
+				pargv, &rc, &out);
+			kparg_free(parg);
+			if (!res || (rc < 0) || !out)
+				continue;
+
+			// Get all completions one by one
+			str = out;
+			while ((l = faux_str_getline(str, &str))) {
+				char *compl_str = l;
+				// Compare prefix
+				if ((prefix_len > 0) &&
+					(faux_str_cmpn(prefix, l, prefix_len) != 0)) {
+					faux_str_free(l);
+					continue;
+				}
+				compl_str = l + prefix_len;
+				faux_msg_add_param(ack, KTP_PARAM_LINE,
+					compl_str, strlen(compl_str));
+				faux_str_free(l);
+			}
+
+			faux_str_free(out);
+		}
+	}
+
 	faux_msg_send_async(ack, ktpd->async);
 	faux_msg_free(ack);
 

+ 12 - 4
plugins/klish/ptypes.c

@@ -39,23 +39,31 @@ int klish_ptype_COMMAND(kcontext_t *context)
 
 
 /** @brief COMPLETION: Consider ENTRY's name (or "value" field) as a command
+ *
+ * This completion function has main ENTRY that is a child of COMMAND ptype
+ * ENTRY. The PTYPE entry has specific ENTRY (with name and possible value)
+ * as a parent.
+ *
+ * command (COMMON ENTRY) with name or value
+ *     ptype (PTYPE ENTRY)
+ *         completion (COMPLETION ENTRY) - start point
  */
 int klish_completion_COMMAND(kcontext_t *context)
 {
 	const kentry_t *entry = NULL;
-	const char *value = NULL;
 	const char *command_name = NULL;
 
 	entry = kcontext_candidate_entry(context);
-	value = kcontext_candidate_value(context);
 
 	command_name = kentry_value(entry);
 	if (!command_name)
 		command_name = kentry_name(entry);
 	if (!command_name)
-		return -1;
+		return 0;
 
-	return faux_str_casecmp(value, command_name);
+	printf("%s\n", command_name);
+
+	return 0;
 }