Hello everybody,

As you may remember I was working on implementing an on-the-fly code
checker for Vim (CodeCheck). I am happy to release the first version of
it. 

Unfortunately this initial version is highly restricted at the
moment: it only works on Linux with Pthreads and supports C/C++
standalone file editing with gcc or g++ compilers. 

I am attaching a patch for CodeCheck. After patching your source tree, you can 
get
more information by ``:he codecheck''.

Any comment is appreciated. My next step will be to port this code to
Windows.

Cheers,
-- 
Birgi

--~--~---------~--~----~------------~-------~--~----~
You received this message from the "vim_dev" maillist.
For more information, visit http://www.vim.org/maillist.php
-~----------~----~----~----~------~----~------~--~---

Index: runtime/doc/index.txt
===================================================================
--- runtime/doc/index.txt	(revision 1164)
+++ runtime/doc/index.txt	(working copy)
@@ -1108,7 +1108,9 @@
 |:catch|	:cat[ch]	part of a :try command
 |:cbuffer|	:cb[uffer]	parse error messages and jump to first error
 |:cc|		:cc		go to specific error
+|:ccadd|	:ccadd		add current buffer to CodeCheck watchlist
 |:cclose|	:ccl[ose]	close quickfix window
+|:ccrem|	:ccrem		remove current buffer from CodeCheck watchlist
 |:cd|		:cd		change directory
 |:center|	:ce[nter]	format lines at the center
 |:cexpr|	:cex[pr]	read errors from expr and jump to first
Index: runtime/doc/tags
===================================================================
--- runtime/doc/tags	(revision 1164)
+++ runtime/doc/tags	(working copy)
@@ -1898,8 +1898,10 @@
 :cb	quickfix.txt	/*:cb*
 :cbuffer	quickfix.txt	/*:cbuffer*
 :cc	quickfix.txt	/*:cc*
+:ccadd	code_check.txt	/*:ccadd*
 :ccl	quickfix.txt	/*:ccl*
 :cclose	quickfix.txt	/*:cclose*
+:ccrem	code_check.txt	/*:ccrem*
 :cd	editing.txt	/*:cd*
 :cd-	editing.txt	/*:cd-*
 :ce	change.txt	/*:ce*
@@ -3192,6 +3194,7 @@
 C	change.txt	/*C*
 C-editing	tips.txt	/*C-editing*
 C-indenting	indent.txt	/*C-indenting*
+CodeCheck	code_check.txt	/*CodeCheck*
 COMSPEC	starting.txt	/*COMSPEC*
 CR-used-for-NL	pattern.txt	/*CR-used-for-NL*
 CTRL-6	editing.txt	/*CTRL-6*
@@ -4829,6 +4832,8 @@
 cmdwin-char	cmdline.txt	/*cmdwin-char*
 cobol.vim	syntax.txt	/*cobol.vim*
 codeset	mbyte.txt	/*codeset*
+codecheck	code_check.txt	/*codecheck*
+code_check	code_check.txt	/*code_check*
 coding-style	develop.txt	/*coding-style*
 col()	eval.txt	/*col()*
 coldfusion.vim	syntax.txt	/*coldfusion.vim*
Index: runtime/doc/code_check.txt
===================================================================
--- runtime/doc/code_check.txt	(revision 0)
+++ runtime/doc/code_check.txt	(revision 0)
@@ -0,0 +1,62 @@
+*code_check.txt*   For Vim version 7.2.  Last change: 2008 Aug 18
+
+
+		  VIM REFERENCE MANUAL    by Bram Moolenaar
+
+
+CodeCheck [version 0.1]			*codecheck* *CodeCheck* *code_check*
+
+1. What is CodeCheck			|intro|
+2. Using :ccadd				|ccadd|
+3. Using :ccrem				|ccrem|
+4. Progress				|progress|
+
+==============================================================================
+1. What is CodeCheck						*intro*
+
+*** CodeCheck is sponsored by Google under Google Summer of Code program,
+mentored by Bram Moolenaar and implemented by Birgi Tamersoy. ***
+
+CodeCheck is an on-the-fly syntax checking tool implemented for Vim. It mimics
+the similar feature in Eclipse IDE. Whenever the user delays for a specified
+amount of time, a background thread:
+	- creates a temporary copy of the current buffer,
+	- compiles this temporary copy,
+	- parses the compiler output, and
+	- highlights the related lines in the buffer.
+
+As of today (last change date), CodeCheck is highly platform-dependent. It
+will work only on Linux with Pthreads. Current supported file types are; C/C++
+independent source files.
+
+==============================================================================
+2. Using :ccadd							*ccadd*
+
+Usage:
+:ccadd <compile command>	Adds current buffer to the CodeCheck
+				watchlist. <compile command> is required.
+				<compile command> must include the full path
+				of the corresponding buffer.
+
+Example:
+:ccadd gcc -Wall -c /home/birgi/tmp/a/b/main.c
+
+==============================================================================
+3. Using :ccrem							*ccrem*
+
+Usage:
+:ccrem				Removes current buffer from the CodeCheck
+				watchlist. This command does not take any
+				arguments. If the buffer is not being watched,
+				the command is simply ignored.
+
+==============================================================================
+4. Progress							*progress*
+
+As of today (last change date) the maintainer works on the following issues:
+- Porting to Windows.
+- Specific parts highlighting rather than line highlighting.
+- Implementing a mechanism that would make new compiler/language support
+  easier.
+
+==============================================================================
Index: src/ex_docmd.c
===================================================================
--- src/ex_docmd.c	(revision 1164)
+++ src/ex_docmd.c	(working copy)
@@ -140,6 +140,7 @@
 static char_u	*repl_cmdline __ARGS((exarg_T *eap, char_u *src, int srclen, char_u *repl, char_u **cmdlinep));
 static void	ex_highlight __ARGS((exarg_T *eap));
 static void	ex_colorscheme __ARGS((exarg_T *eap));
+static void	ex_codecheck __ARGS((exarg_T *eap));
 static void	ex_quit __ARGS((exarg_T *eap));
 static void	ex_cquit __ARGS((exarg_T *eap));
 static void	ex_quit_all __ARGS((exarg_T *eap));
@@ -6156,6 +6157,34 @@
 }
 
     static void
+ex_codecheck(eap)
+    exarg_T	*eap;
+{
+    switch (eap->cmdidx) {
+	case CMD_ccadd:
+	    if (!cc_get_is_started())
+		cc_init();
+	    if (cc_is_buf_ok(curbuf)) {
+		if (*eap->arg != NUL)
+		    cc_addbuf_setcmd(curbuf, eap->arg);
+		else
+		    EMSG(_("You have to specify a valid compile command "
+				"in order to add this buffer to watchlist."));
+	    } else
+		EMSG(_("The present version of CodeCheck does not support "
+			   "this language."));
+	    break;
+       case CMD_ccrem:
+	    cc_rem_buf(curbuf);
+	    if (cc_get_bufcount() == 0)
+		cc_exit();
+	    break;
+	default:
+	    return;
+   }
+}
+
+    static void
 ex_highlight(eap)
     exarg_T	*eap;
 {
Index: src/ex_cmds.h
===================================================================
--- src/ex_cmds.h	(revision 1164)
+++ src/ex_cmds.h	(working copy)
@@ -201,8 +201,12 @@
 			BANG|RANGE|NOTADR|WORD1|TRLBAR),
 EX(CMD_cc,		"cc",		ex_cc,
 			RANGE|NOTADR|COUNT|TRLBAR|BANG),
+EX(CMD_ccadd,		"ccadd",	ex_codecheck,
+			EXTRA|NOTRLCOM|XFILE|MODIFY),
 EX(CMD_cclose,		"cclose",	ex_cclose,
 			RANGE|NOTADR|COUNT|TRLBAR),
+EX(CMD_ccrem,		"ccrem",	ex_codecheck,
+			NOSPC|NOTRLCOM|MODIFY),
 EX(CMD_cd,		"cd",		ex_cd,
 			BANG|FILE1|TRLBAR|CMDWIN),
 EX(CMD_center,		"center",	ex_align,
Index: src/getchar.c
===================================================================
--- src/getchar.c	(revision 1164)
+++ src/getchar.c	(working copy)
@@ -1618,6 +1618,20 @@
 		c = K_IGNORE;
 	    }
 
+	    /* Handle redraw event here, this is used by CodeCheck.*/
+	    if (c == K_REDRAW) {
+		/* gui_update_screen() does not work because of 
+		 * update_screen(0) inside it. */
+		update_topline();
+		validate_cursor();
+		update_screen(SOME_VALID);
+		setcursor();
+		cursor_on();
+		out_flush();
+
+		c = K_IGNORE;
+	    }
+
 	    /* Translate K_CSI to CSI.  The special key is only used to avoid
 	     * it being recognized as the start of a special key. */
 	    if (c == K_CSI)
Index: src/code_check.c
===================================================================
--- src/code_check.c	(revision 0)
+++ src/code_check.c	(revision 0)
@@ -0,0 +1,1063 @@
+/* vi:set ts=8 sts=4 sw=4:
+ *
+ * VIM - Vi IMproved	by Bram Moolenaar
+ * 			CodeCheck extension by Birgi Tamersoy
+ *				[EMAIL PROTECTED]
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+/*
+ * code_check.c: On-the-fly syntax checking tool.
+ */
+
+#include <pthread.h>
+#include <semaphore.h>
+#include "vim.h"
+
+#define CC_VERSION  0.1
+
+enum cc_ret_vals {
+    CC_FAIL = 0,
+    CC_SUCCESS,
+    CC_BUFEXISTS,
+    CC_NOSUCHBUF,
+};
+
+enum cc_ew_types {
+    CC_NOEW = 0,
+    CC_WARNING,
+    CC_ERROR
+};
+
+enum cc_search_mode {
+    CC_FOR_ADD = 0,
+    CC_FOR_REM,
+    CC_FOR_FIND
+};
+
+enum cc_print_direc {
+    CC_FWD = 0,
+    CC_REW
+};
+
+enum cc_copy_type {
+    CC_STANDALONE = 0,
+    CC_MASTER,
+    CC_PROJECT		/* Makefile exists */
+};
+
+/*
+ * Multiple buffers can be syntax checked simultaneously and each buffer 
+ * has its own list of errors and warnings. Consequently we have two list
+ * structures. One holds information about processed buffers and the other
+ * one holds information about buffer-specific errors & warnings.
+ *
+ * Both lists will be accessed frequently, so they will be sorted:
+ *  buffer list will be sorted with respect to full file names, and
+ *  error/warning list will be sorted with respect to line numbers.
+ */
+
+#define	MAX_EW_TEXT 200
+
+/* compile command length */
+#define MAX_CMD_LENGTH 600
+
+#define MAX_PATH_LENGTH 300
+
+typedef struct cc_ewline_S cc_ewline_T;
+struct cc_ewline_S {
+    cc_ewline_T	*prev;
+    cc_ewline_T	*next;
+    int		ew_type;
+    char_u	ew_text[MAX_EW_TEXT];
+    linenr_T	ew_lnum;
+};
+
+typedef struct cc_bufline_S cc_bufline_T;
+struct cc_bufline_S {
+    cc_bufline_T    *prev;
+    cc_bufline_T    *next;
+    char_u	    *buf_name;
+    cc_ewline_T	    *buf_ewlist_head;
+    pthread_mutex_t buf_mutex;
+    char_u	    buf_compile_cmd[MAX_CMD_LENGTH];
+};
+
+typedef struct cc_info_S cc_info_T;
+struct cc_info_S {
+    int		    cc_bufcount;
+    cc_bufline_T    *cc_list_head;
+    cc_bufline_T    *cc_list_curr;
+};
+
+/* global list of buffers. */
+static cc_info_T cc_list;
+#define MAX_BUFLINES 100
+static cc_bufline_T *cc_bufline_ptrs[MAX_BUFLINES];
+
+/* supported languages, the format should be ".<ext1>" ...  */
+/* each extension should padded with ' ' characters, st. total is 5 chars */ 
+static char_u *cc_sup_exts = (char_u *) ".c   "
+					".cpp ";
+
+/* worker thread. */
+static pthread_t cc_slave;
+
+/* a queue for the pending jobs that the worker thread should handle.
+ * basically a list of buffer compile requests. */
+enum cc_pjob_types {
+    CC_COMPILE = 0
+};
+
+typedef struct cc_pjob_S cc_pjob_T;
+struct cc_pjob_S {
+    int		cc_pjob_type;
+    buf_T	*cc_pjob_buf;
+};
+
+#define MAX_PENDING_JOBS 10
+static cc_pjob_T *cc_pjobs[MAX_PENDING_JOBS];
+static int pindex = 0;
+static int cindex = 0;
+
+static sem_t full;
+static sem_t empty;
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/* functions */
+static void cc_free_ewlist(cc_ewline_T *ewlist_head);
+static cc_bufline_T *cc_locate_buf(buf_T *buf);
+static cc_bufline_T *cc_locate_buf_bin(buf_T *buf, int *buf_idx, 
+	int *put_before, int mode);
+static void cc_free_buflist(cc_info_T *cc_list_a, 
+	cc_bufline_T *cc_bufline_ptrs_a[MAX_BUFLINES]);
+static int cc_start_slave_thread(void);
+static void *cc_slave_sroutine(void *args);
+static int cc_pjobs_produce(cc_pjob_T *tmp_pjob);
+static cc_pjob_T *cc_pjobs_consume(void);
+static int cc_pjobs_buf_exists(cc_pjob_T *tmp_pjob);
+static int cc_create_tmp_copy(buf_T *buf, char_u *tmp_copy_ffname, 
+	int copy_type);
+static int cc_compile_tmp_copy(cc_bufline_T *bufline, 
+	char_u *tmp_copy_ffname, int copy_type);
+static cc_ewline_T *cc_create_ewlist(char_u *tmp_out_ffname);
+void cc_sigalrm_handler(int signum);
+void cc_update_screen(void);
+static void cc_free(void);
+static cc_bufline_T* cc_add_buf(buf_T *buf);
+static int cc_set_tmp_copy_ffname(buf_T *buf, char_u *tmp_copy_ffname);
+
+static int cc_started = FALSE;
+static int old_p_ut = 0;
+
+/*
+ * TODO:
+ *
+ * 2. error/warning format should be added so that new languages are 
+ *	    easily added to the code_check.c.
+ */
+
+/*
+ * Initializes the related structures.
+ */
+    int
+cc_init(void) {
+    int	    i;
+    int	    retval;
+
+    cc_list.cc_bufcount = 0;
+    cc_list.cc_list_head = NULL;
+    cc_list.cc_list_curr = NULL;
+
+   retval = sem_init(&full, 0, 0); 
+   if (retval)
+       return CC_FAIL;
+
+   retval = sem_init(&empty, 0, MAX_PENDING_JOBS); 
+   if (retval)
+       return CC_FAIL;
+
+    cc_start_slave_thread();
+
+    for (i = 0; i < MAX_BUFLINES; ++i)
+	cc_bufline_ptrs[i] = NULL;
+
+    for (i = 0; i < MAX_PENDING_JOBS; ++i)
+	cc_pjobs[i] = NULL;
+
+    old_p_ut = p_ut;
+    p_ut = 1000;
+
+    cc_started = TRUE;
+
+    return CC_SUCCESS;
+}
+
+/*
+ * Returns the current number of buffers in the watchlist.
+ */
+    int
+cc_get_bufcount(void) {
+    return cc_list.cc_bufcount;
+}
+
+/*
+ * Returns TRUE if CodeCheck is already started.
+ */
+    int
+cc_get_is_started(void) {
+    return cc_started;
+}
+
+/*
+ * CodeCheck creates a working thread which would run in the background.
+ * This thread is responsible of compiling the specified buffers, parsing 
+ * the outputs and forming the corresponding error/warning lists.
+ */
+    static int
+cc_start_slave_thread(void) {
+    pthread_attr_t  *attr	= NULL;
+    int		    retval;
+
+    attr = (pthread_attr_t *) calloc(1, sizeof(pthread_attr_t));
+
+    retval = pthread_attr_init(attr);
+    if (retval)
+	goto sslave_fail;
+
+    retval = pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED);
+    if (retval)
+	goto sslave_fail;
+
+    retval = pthread_create(&cc_slave, attr, cc_slave_sroutine, NULL);
+    if (retval)
+	goto sslave_fail;
+
+    return CC_SUCCESS;
+
+sslave_fail:
+    free(attr);
+    return CC_FAIL;
+}
+
+/*
+ * Function updates the error/warning line numbers after new lines are inserted
+ * or some existing lines are removed. This is just to move the highlighted
+ * parts before an actual compile.
+ *
+ * TODO: This function is mainly copied from color_me.c (the preliminary 
+ * project). So, double check if everything is OK or not!!!
+ */
+    int
+cc_update_ew_lnums(buf_T *buf, int lnum, int col, long xtra) {
+    if (!cc_started)
+	return CC_FAIL;
+
+    cc_bufline_T    *tmp_bufline;
+    cc_ewline_T	    *tmp_ewline;
+    char_u	    *line	    = NULL;
+    int		    dummy;
+    int		    i;
+    int		    skip	    = FALSE;
+
+    tmp_bufline = cc_locate_buf_bin(buf, &dummy, &dummy, CC_FOR_FIND);
+    if (tmp_bufline == NULL)
+	return CC_FAIL;
+
+    pthread_mutex_lock(&(tmp_bufline->buf_mutex));
+
+    for (tmp_ewline = tmp_bufline->buf_ewlist_head;
+	    tmp_ewline != NULL; tmp_ewline = tmp_ewline->next) {
+
+	    skip = FALSE;
+	    if (tmp_ewline->ew_lnum < lnum)
+		    continue;
+	    if (tmp_ewline->ew_lnum == lnum) {
+		    /* is this the beginning of the line? */
+		    line = ml_get_buf(buf, lnum, FALSE);
+		    for (i = col - 1; i > 0 && 
+				    (line[i] == '\t' || line[i] == ' '); --i)
+			    ;
+		    if (i > 0)
+			    continue;
+	    }
+	    tmp_ewline->ew_lnum += xtra;
+    }
+
+    pthread_mutex_unlock(&(tmp_bufline->buf_mutex));
+    return CC_SUCCESS;
+}
+
+/* 
+ * Worker thread function, which continuously checks for unserved requests.
+ */
+    static void *
+cc_slave_sroutine(void *args) {
+    cc_pjob_T	    *tmp_pjob;
+    cc_bufline_T    *tmp_bufline;
+    int		    retval;
+    int		    dummy;
+    char_u	    tmp_copy_ffname[MAX_PATH_LENGTH];
+    char_u	    tmp_out_ffname[MAX_CMD_LENGTH];
+    cc_ewline_T	    *ew_head = NULL;
+
+    while (TRUE) {
+	/* consume a pending job */
+	tmp_pjob = cc_pjobs_consume();
+	if (tmp_pjob == NULL)
+	    continue;
+
+	/* when gui mode is used, an other update screen is required. */
+	/*				    i don't know why :)!!!. */
+	if (gui.in_use)
+	    cc_update_screen();
+
+	/* find the buffer line */
+
+	/* create the temporary copy */
+	retval = cc_create_tmp_copy(tmp_pjob->cc_pjob_buf, 
+		tmp_copy_ffname, CC_STANDALONE);
+	if (retval == CC_FAIL) 
+	    continue;
+
+	tmp_bufline = cc_locate_buf_bin(tmp_pjob->cc_pjob_buf, &dummy, 
+		&dummy, CC_FOR_FIND);
+	if (tmp_bufline == NULL)
+	    continue;
+
+	/* compile the temporary copy */
+	retval = cc_compile_tmp_copy(tmp_bufline, tmp_copy_ffname, 
+		CC_STANDALONE);
+	if (retval == CC_FAIL) 
+	    continue;
+
+	/* create the error list from the temporary copy */
+	sprintf((char *)tmp_out_ffname, "%s.out", (char *)tmp_copy_ffname);
+	ew_head = cc_create_ewlist(tmp_out_ffname);
+
+	/* having ew_head == NULL is not an error, since all
+	 * the errors and the warnings may be cleaned. */
+
+	/* free the previous list & set the new list */
+	pthread_mutex_lock(&(tmp_bufline->buf_mutex));
+
+	cc_free_ewlist(tmp_bufline->buf_ewlist_head);
+	tmp_bufline->buf_ewlist_head = ew_head;
+
+	pthread_mutex_unlock(&(tmp_bufline->buf_mutex));
+
+	/* update the screen */
+	cc_update_screen();
+
+	/* that would be it for this buffer */
+    }
+}
+
+/* 
+ * Function is used to update the screen properly.
+ */
+    void
+cc_update_screen(void) {
+    if (!gui.in_use) {
+	/* in console mode updating the screen from the worker thread
+	 * does not cause any problems. */
+	update_topline();
+	validate_cursor();
+	update_screen(SOME_VALID);
+	setcursor();
+	cursor_on();
+	out_flush();
+    } else {
+	/* updating the screen in gui mode is troublesome. */
+	char_u	bytes[3];
+
+	bytes[0] = CSI;
+	bytes[1] = KS_EXTRA;
+	bytes[2] = KE_REDRAW;
+
+	add_to_input_buf(bytes, 3);
+    }
+}
+
+/*
+ * Function returns the correct error/warning type for a specific lnum.
+ * Returns CC_NOEW if the lnum does not have an error or a warning.
+ */
+    int
+cc_get_ew_type(buf_T *buf, linenr_T lnum) {
+    if (!cc_started)
+	return CC_FAIL;
+
+    cc_bufline_T    *tmp_bufline;
+    cc_ewline_T	    *tmp_ewline;
+    int		    dummy;
+    int		    ew_type	    = CC_NOEW;
+
+    tmp_bufline = cc_locate_buf_bin(buf, &dummy, &dummy, CC_FOR_FIND);
+    if (tmp_bufline == NULL)
+	return CC_NOEW;
+
+    pthread_mutex_lock(&(tmp_bufline->buf_mutex));
+    tmp_ewline = tmp_bufline->buf_ewlist_head;
+    while (tmp_ewline != NULL) {
+	if (tmp_ewline->ew_lnum != lnum) {
+	    tmp_ewline = tmp_ewline->next;
+	    continue;
+	}
+
+	if (ew_type < tmp_ewline->ew_type)
+	    ew_type = tmp_ewline->ew_type;
+
+	tmp_ewline = tmp_ewline->next;
+    }
+    pthread_mutex_unlock(&(tmp_bufline->buf_mutex));
+    return ew_type;
+}
+
+/*
+ * Function creates a quickfix error list from the compiler output.
+ * TODO: Right now works only for .c files and the gcc compiler.
+ *	Should be automated to work with multiple languages and multiple
+ *	compilers.
+ */
+    static cc_ewline_T *
+cc_create_ewlist(char_u *tmp_out_ffname) {
+    FILE	    *err_file	= NULL;
+    char_u	    *buf	= NULL; 
+    size_t	    len		= 0;
+    size_t	    read;
+    cc_ewline_T	    *ew_head	= NULL;
+    cc_ewline_T	    *ew_curr	= NULL;
+    char_u	    *token1	= NULL;
+    char_u	    *token2	= NULL;
+    char_u	    *token3	= NULL;
+    char_u	    *token4	= NULL;
+    cc_ewline_T	    *tmp_ewline = NULL;
+
+
+    err_file = fopen((char *)tmp_out_ffname, "r");
+    if (err_file == NULL)
+	return NULL;
+
+    while ((read = getline((char **) &buf, &len, err_file)) != -1) {
+	token1 = (char_u *)strtok((char *) buf, ":");
+	token2 = (char_u *)strtok(NULL, ":");
+	token3 = (char_u *)strtok(NULL, ":");
+	token4 = (char_u *)strtok(NULL, ":");
+
+	/* TODO: be sure it is gXX type of output. */
+
+	if (!token4)
+	    continue;
+
+	tmp_ewline = (cc_ewline_T *) calloc(1, sizeof(cc_ewline_T));
+	STRCPY(tmp_ewline->ew_text, token4);
+	/* remove the last '\n' from the error/warning message */
+	tmp_ewline->ew_text[STRLEN(tmp_ewline->ew_text) - 1] = '\0';
+	tmp_ewline->ew_lnum = (linenr_T) atoi((char *) token2);
+	tmp_ewline->ew_type = token3[1] == 'w' ? CC_WARNING : CC_ERROR;
+
+	if (!ew_head) {
+	    ew_head = tmp_ewline;
+	    ew_curr = tmp_ewline;
+	} else {
+	    ew_curr->next = tmp_ewline;
+	    tmp_ewline->prev = ew_curr;
+	    ew_curr = ew_curr->next;
+	}
+    }
+
+    /* buf should be freed, because it is allocated in getline */
+    if (buf)
+	free(buf);
+
+    return ew_head;
+}
+
+/*
+ * Function compiles the temporary copy saving the output in a
+ * temporary file.
+ */
+    static int
+cc_compile_tmp_copy(cc_bufline_T *bufline, char_u *tmp_copy_ffname, 
+	int copy_type) {
+    char_u	cmd[MAX_CMD_LENGTH];
+
+    if (bufline->buf_compile_cmd[0] == NUL)
+	return CC_FAIL;
+
+    /* clear the arrays. */
+    memset(cmd, 0, MAX_CMD_LENGTH);
+
+    /* direct STDOUT & STDERR to the same file, so that the output of
+     * this will not interfer with the vim terminal */
+    sprintf((char *)cmd, "%s > %s.out 2>&1", 
+	    (char *)bufline->buf_compile_cmd, (char *)tmp_copy_ffname);
+#if 1    
+    system((char *)cmd);
+#endif
+
+    return CC_SUCCESS;
+}
+
+/*
+ * Function creates a temporary copy of the buffer. copy_type determines
+ * the type of the copy process.
+ */
+    static int
+cc_create_tmp_copy(buf_T *buf, char_u *tmp_copy_ffname, int copy_type) {
+    int		retval;
+    char_u	cmd[MAX_CMD_LENGTH];
+
+    switch (copy_type) {
+	case CC_STANDALONE:
+	    cc_set_tmp_copy_ffname(buf, tmp_copy_ffname);
+	    sprintf((char *)cmd, "touch %s", (char *)tmp_copy_ffname);
+    
+	    system((char *)cmd);
+#if 1
+	    retval = buf_write(buf, tmp_copy_ffname, NULL,
+		    (linenr_T) 1, buf->b_ml.ml_line_count, NULL, 
+		    FALSE, FALSE, FALSE, TRUE);
+
+	    if (retval == FAIL)
+		return CC_FAIL;
+	    else
+#endif
+		return CC_SUCCESS;
+
+	/* these are not implemented yet. */
+	case CC_MASTER:
+	case CC_PROJECT:
+	default:
+	    return CC_FAIL;
+    }
+}
+
+/*
+ * Outside accessible compile request for a specific buffer.
+ */
+    int
+cc_request_compile(buf_T *buf) {
+    if (!cc_started)
+	return CC_FAIL;
+
+    cc_pjob_T	*tmp_pjob;
+
+    tmp_pjob = (cc_pjob_T *) calloc(1, sizeof(cc_pjob_T));
+    if (tmp_pjob == NULL)
+	return CC_FAIL;
+
+    tmp_pjob->cc_pjob_buf = buf;
+    tmp_pjob->cc_pjob_type = CC_COMPILE;
+
+    return cc_pjobs_produce(tmp_pjob);
+}
+
+/*
+ * Function required to populate the pending jobs array.
+ * TODO: FAILURE IN pthread_mutex_unlock is SERIOUS!!!
+ */
+    static int
+cc_pjobs_produce(cc_pjob_T *tmp_pjob) {
+    int	    retval;
+
+    sem_wait(&empty);
+
+    retval = pthread_mutex_lock(&mutex);
+    if (retval)
+	return CC_FAIL;
+
+    /* check if this buffer already has a pending job request. */
+    /* if so directly return. */
+    if (cc_pjobs_buf_exists(tmp_pjob)) {
+	sem_post(&empty);
+	retval = pthread_mutex_unlock(&mutex);
+	if (retval)
+	    return CC_FAIL;
+	return CC_BUFEXISTS;
+    }
+
+    cc_pjobs[pindex % MAX_PENDING_JOBS] = tmp_pjob;
+    ++pindex;
+
+    retval = pthread_mutex_unlock(&mutex);
+    if (retval)
+	return CC_FAIL;
+
+    retval = sem_post(&full);
+    if (retval)
+	return CC_FAIL;
+
+    return CC_SUCCESS;
+}
+
+/*
+ * Returns TRUE if there is another pending job for this process.
+ */
+    static int
+cc_pjobs_buf_exists(cc_pjob_T *tmp_pjob) {
+    int	    i;
+
+    for (i = 0; i < MAX_PENDING_JOBS; ++i) {
+	if (cc_pjobs[i] == NULL || 
+		strcmp((char *) cc_pjobs[i]->cc_pjob_buf->b_ffname,
+		    (char *) tmp_pjob->cc_pjob_buf->b_ffname))
+	    continue;
+	else
+	    return TRUE;
+    }
+    return FALSE;
+}
+
+/*
+ * Removes the "top" element.
+ */
+    static cc_pjob_T *
+cc_pjobs_consume(void) {
+    int		retval;
+    cc_pjob_T	*tmp_pjob   = NULL;
+
+    tmp_pjob = (cc_pjob_T *) calloc(1, sizeof(cc_pjob_T));
+    if (tmp_pjob == NULL)
+	return NULL;
+
+    sem_wait(&full);
+
+    retval = pthread_mutex_lock(&mutex);
+    if (retval)
+	goto con_fail;
+
+    tmp_pjob->cc_pjob_type = cc_pjobs[cindex % MAX_PENDING_JOBS]->cc_pjob_type;
+    tmp_pjob->cc_pjob_buf = cc_pjobs[cindex % MAX_PENDING_JOBS]->cc_pjob_buf;
+
+    /* free the old job!!! */
+    free(cc_pjobs[cindex % MAX_PENDING_JOBS]);
+    cc_pjobs[cindex % MAX_PENDING_JOBS] = NULL;
+
+    cindex++;
+    
+    retval = pthread_mutex_unlock(&mutex);
+    if (retval)
+	goto con_fail;
+
+    retval = sem_post(&empty);
+    if (retval)
+	goto con_fail;
+
+    return tmp_pjob;
+
+con_fail:
+    free(tmp_pjob);
+    return NULL;
+}
+
+/*
+ * Checks if the specified buffer can be syntax checked or not.
+ */
+    int
+cc_is_buf_ok(buf_T *buf) {
+    if (!cc_started)
+	return CC_FAIL;
+
+    char_u  *ffname	= NULL;
+    char_u  *p		= NULL;
+    char_u  *r		= NULL;
+    
+    if (buf == NULL || ((ffname = buf->b_ffname) == NULL))
+	return CC_FAIL;
+
+    /* find the file extension */
+    p = (char_u *) strrchr((char *) ffname, '.');
+
+    if (p == NULL)
+	return CC_FAIL;
+
+    /* check if this extension is supported or not */
+    r = (char_u *) strstr((char *) cc_sup_exts, (char *) p);
+
+    if (r == NULL)
+	return CC_FAIL;
+    else 
+        return CC_SUCCESS;
+}
+
+/*
+ * Returns TRUE if the specified buffer is in the watchlist.
+ * TODO: THIS CAN RETURN THE FOUND BUFFER TO SAVE SOME TIME.
+ *
+ */
+    int
+cc_is_buf_watched(buf_T *buf) {
+    if (!cc_started)
+	return CC_FAIL;
+
+    cc_bufline_T    *tmp_bufline;
+    int		    dummy;
+
+    tmp_bufline = cc_locate_buf_bin(buf, &dummy, &dummy, CC_FOR_FIND);
+    if (tmp_bufline == NULL)
+	return FALSE;
+    else 
+	return TRUE;
+}
+
+/*
+ * Finds and sets a file name for the temporary copy of the buffer.
+ */
+    static int
+cc_set_tmp_copy_ffname(buf_T *buf, char_u *tmp_copy_ffname) {
+    char_u	*buf_sfname;
+    char_u	tmp_buf[MAX_PATH_LENGTH];
+    char_u	*p;
+
+    /* clear the array. */
+    memset(tmp_buf, 0, MAX_PATH_LENGTH);
+
+    /* find the tmp_copy_ffname. */
+    /* uses gettail(buf->b_sfname) because sometimes b_sfname is 
+     * actually b_ffname. */
+    buf_sfname = buf->b_sfname ? gettail(buf->b_sfname) 
+				: gettail(buf->b_ffname);
+    if (buf_sfname == NULL || !strcmp((char *) buf_sfname, ""))
+	return CC_FAIL;
+
+    /* finds the folder path of the buffer. */
+    p = (char_u *)strstr((char *)buf->b_ffname, (char *)buf_sfname);
+    if (p == NULL)
+	return CC_FAIL;
+    STRNCPY(tmp_buf, buf->b_ffname, (p - buf->b_ffname));
+
+    /* rather than creating the copy in /tmp/, create it in the directory
+     * of the buffer. this solves a few issues without a lot of effort. */
+    sprintf((char *) tmp_copy_ffname, "%s.cc_%s", 
+	    (char *) tmp_buf, (char *) buf_sfname);
+    return CC_SUCCESS;
+}
+
+/*
+ * Adds the specified buffer and sets the compile command.
+ */
+    int
+cc_addbuf_setcmd(buf_T *buf, char_u *cmd) {
+    cc_bufline_T    *tmp_bufline    = NULL;
+    char_u	    tmp_compile_cmd[MAX_CMD_LENGTH];
+    char_u	    *p		    = NULL;
+    char_u	    tmp_copy_ffname[MAX_PATH_LENGTH];
+    size_t	    bname_len;
+
+    /* clear the arrays. */
+    memset(tmp_compile_cmd, 0, MAX_CMD_LENGTH);
+    memset(tmp_copy_ffname, 0, MAX_PATH_LENGTH);
+
+    tmp_bufline = cc_add_buf(buf);
+    if (tmp_bufline == NULL)
+	return CC_FAIL;
+
+    cc_set_tmp_copy_ffname(buf, tmp_copy_ffname);
+
+    /* at this point we should update the compile command, so
+     * that it works for the temporary copy, rather than the 
+     * original copy. 
+     * *** assumes that the user entered compile command has the 
+     * full file name of the current buffer. */
+    p = (char_u *)strstr((char *)cmd, 
+	    (char *)tmp_bufline->buf_name);
+    if (p == NULL)
+	return CC_FAIL;
+    bname_len = STRLEN(tmp_bufline->buf_name);
+
+    STRNCPY(tmp_compile_cmd, cmd, 
+	    (p - cmd));
+    STRCAT(tmp_compile_cmd, tmp_copy_ffname);
+    STRCAT(tmp_compile_cmd, p + bname_len);
+
+    STRCPY(tmp_bufline->buf_compile_cmd, tmp_compile_cmd);
+
+    /* TODO: check if tmp_bufline->buf_compile_cmd is valid. */
+    return CC_SUCCESS;
+}
+
+/*
+ * Adds the specified buffer to the watch list.
+ *
+ * modified a little: returns a pointer to the inserted cc_bufline_T 
+ * object, to make things a little faster.
+ *
+ */
+    static cc_bufline_T*
+cc_add_buf(buf_T *buf) {
+    if (!cc_started)
+	return NULL;
+
+    cc_bufline_T    *tmp_bufline    = NULL;
+    cc_bufline_T    *tmp_insafter   = NULL;
+    int		    buf_idx;
+    int		    put_before;
+    int		    i;
+    int		    retval;
+
+    /* check if this buffer is in the watch list, if so don't add */
+    tmp_bufline = cc_locate_buf_bin(buf, &buf_idx, &put_before, CC_FOR_FIND);
+    if (tmp_bufline != NULL)
+	return NULL;
+
+    if (cc_list.cc_bufcount == MAX_BUFLINES)
+	return NULL;
+    
+    tmp_bufline = (cc_bufline_T *) calloc(1, sizeof(cc_bufline_T));
+
+    if (tmp_bufline == NULL)
+	return NULL;
+
+    tmp_bufline->buf_name = buf->b_ffname;
+    memset(tmp_bufline->buf_compile_cmd, 0, MAX_CMD_LENGTH);
+    pthread_mutex_init(&(tmp_bufline->buf_mutex), NULL);
+
+    if (cc_list.cc_bufcount == 0) {
+	/* this is the first buffer */
+	cc_list.cc_list_head = tmp_bufline;
+	tmp_bufline->prev = NULL;
+	tmp_bufline->next = NULL;
+	cc_bufline_ptrs[0] = tmp_bufline;
+    } else {
+	tmp_insafter = cc_locate_buf_bin(buf, &buf_idx, &put_before, CC_FOR_ADD);
+	
+	if (tmp_insafter == NULL) {
+	    free(tmp_bufline);
+	    return NULL;
+	}
+
+	if (put_before) {
+	    /* move elements in cc_bufline_ptrs */
+	    for (i = cc_list.cc_bufcount; i > buf_idx; --i)
+		cc_bufline_ptrs[i] = cc_bufline_ptrs[i - 1];
+
+	    cc_bufline_ptrs[buf_idx] = tmp_bufline;
+
+	    tmp_bufline->prev = tmp_insafter->prev;
+	    if (tmp_insafter->prev != NULL)
+		tmp_insafter->prev->next = tmp_bufline;
+	    else 
+		cc_list.cc_list_head = tmp_bufline;
+
+	    tmp_bufline->next = tmp_insafter;
+	    tmp_insafter->prev = tmp_bufline;
+	} else {
+	    /* move elements in cc_bufline_ptrs */
+	    for (i = cc_list.cc_bufcount; i > buf_idx + 1; --i)
+		cc_bufline_ptrs[i] = cc_bufline_ptrs[i - 1];
+
+	    cc_bufline_ptrs[buf_idx + 1] = tmp_bufline;
+
+	    tmp_bufline->next = tmp_insafter->next;
+	    if (tmp_bufline->next != NULL)
+		tmp_bufline->next->prev = tmp_bufline;
+
+	    tmp_insafter->next = tmp_bufline;
+	    tmp_bufline->prev = tmp_insafter;
+	}
+    }
+
+    /* create an initial compile request for this buffer */
+    retval = cc_request_compile(buf);
+    if (retval == CC_FAIL)
+	return NULL;
+
+    cc_list.cc_list_curr = tmp_bufline;
+    cc_list.cc_bufcount++;
+
+    return tmp_bufline;
+}
+
+/*
+ * Removes the specified buffer from the watch list.
+ * TODO: current implementation does not have sorted lists.
+ */
+    int
+cc_rem_buf(buf_T *buf) {
+    if (!cc_started)
+	return CC_FAIL;
+
+    cc_bufline_T    *tmp_bufline    = NULL;
+    int		    buf_idx;
+    int		    put_before;
+    int		    i;
+
+    tmp_bufline = cc_locate_buf_bin(buf, &buf_idx, &put_before, CC_FOR_REM);
+    
+    if (tmp_bufline == NULL)
+	return CC_NOSUCHBUF;
+
+    if (tmp_bufline->prev != NULL)
+	tmp_bufline->prev->next = tmp_bufline->next;
+
+    if (tmp_bufline->next != NULL)
+        tmp_bufline->next->prev = tmp_bufline->prev;
+
+    if (tmp_bufline->buf_ewlist_head != NULL)
+	cc_free_ewlist(tmp_bufline->buf_ewlist_head);
+
+    vim_free(tmp_bufline);
+
+    /* update cc_bufline_ptrs */
+    for (i = buf_idx; i < cc_list.cc_bufcount; ++i)
+	cc_bufline_ptrs[i] = cc_bufline_ptrs[i + 1];
+
+    cc_list.cc_bufcount--;
+
+    /* update the screen in case there are some highlighted lines. */
+    cc_update_screen();
+
+    return CC_SUCCESS;
+}
+
+/*
+ * Returns a pointer to the specified buffer node.
+ * Returns NULL if there is no such buffer.
+ * This is the linear search version.
+ * THIS SHOULD NOT BE USED ANYMORE!!!
+ */
+    static cc_bufline_T *
+cc_locate_buf(buf_T *buf) {
+    cc_bufline_T    *tmp_bufline    = NULL;
+    char_u	    *ffname	    = NULL;
+
+    ffname = buf->b_ffname;
+
+    for (tmp_bufline = cc_list.cc_list_head;
+	    tmp_bufline != NULL; tmp_bufline = tmp_bufline->next) {
+	if (strcmp((char *) tmp_bufline->buf_name, (char *) ffname))
+	    continue;
+	else
+	    break;
+    }
+
+    return tmp_bufline;
+}
+
+/*
+ * Binary search of the buffer list. Has two modes:
+ *  - search for addition (find the correct position in the list which the
+ *	specified buffer should be inserted to)(return CC_BUFEXISTS if the 
+ *	buffer is already in the watch list),
+ *  - search for removal (find the exact location of the buffer, return NULL
+ *	if could not be found).
+ */
+    static cc_bufline_T *
+cc_locate_buf_bin(buf_T *buf, int *buf_idx, int *put_before, int mode) {
+    cc_bufline_T    *tmp_bufline    = NULL;
+    int		    start	    = 0;
+    int		    end		    = cc_list.cc_bufcount - 1;
+    int		    mid		    = (start + end) / 2;
+    int		    retval;
+
+    while (start <= end) {
+	mid = (start + end) / 2;
+
+	/* since this is a linked list we are using cc_bufline_ptrs
+	 * to find the correct node. */
+	tmp_bufline = cc_bufline_ptrs[mid];
+
+	retval = strcmp((char *) buf->b_ffname, (char *) tmp_bufline->buf_name);
+
+	if (retval == 0) {
+	    *buf_idx = mid;
+	    if (mode == CC_FOR_ADD)
+		return NULL;
+	    else if (mode == CC_FOR_REM || mode == CC_FOR_FIND)
+		return tmp_bufline;
+	} else if (retval < 0) {
+	    end = mid - 1;
+	    *put_before = TRUE;
+	} else {
+	    start = mid + 1;
+	    *put_before = FALSE;
+	}
+    }
+
+    /* the buffer is not inside the list */
+    *buf_idx = mid;
+    if (mode == CC_FOR_ADD)
+	return tmp_bufline;
+    else if (mode == CC_FOR_REM || mode == CC_FOR_FIND)
+	return NULL;
+    else /* should not be reached. */
+	return NULL;
+}
+
+/*
+ * Frees the specified error & warning list.
+ */
+    static void
+cc_free_ewlist(cc_ewline_T *ewlist_head) {
+    if (ewlist_head == NULL)
+	return;
+
+    cc_ewline_T	*tmp_ewline = ewlist_head;
+    cc_ewline_T *tmp_ewlinep = ewlist_head;
+
+    while (tmp_ewline != NULL) {
+	tmp_ewline = tmp_ewline->next;
+	free(tmp_ewlinep);
+	tmp_ewlinep = tmp_ewline;
+    }
+}
+
+/*
+ * Frees the specified buffer watch list.
+ * TODO: CHECK THIS BEHAVIOR!!! IMPORTANT!!!
+ */
+    static void
+cc_free_buflist(cc_info_T *cc_list_a, 
+	cc_bufline_T *cc_bufline_ptrs_a[MAX_BUFLINES]) {
+    int	    i;
+
+    if (cc_list_a->cc_list_head == NULL)
+	return;
+
+    cc_bufline_T *tmp_bufline = cc_list_a->cc_list_head;
+    cc_bufline_T *tmp_buflinep = cc_list_a->cc_list_head;
+    
+    while (tmp_bufline != NULL) {
+	cc_free_ewlist(tmp_bufline->buf_ewlist_head);
+
+	tmp_bufline = tmp_bufline->next;
+	free(tmp_buflinep);
+	tmp_buflinep = tmp_bufline;
+    }
+    
+    for (i = 0; i < cc_list_a->cc_bufcount; ++i)
+	cc_bufline_ptrs_a[i] = NULL;
+
+    cc_list_a->cc_list_head = NULL;
+    cc_list_a->cc_list_curr = NULL;
+    cc_list_a->cc_bufcount = 0;
+}
+
+/*
+ * Frees the memory.
+ */
+    static void
+cc_free(void) {
+    if (!cc_started)
+	return;
+
+    cc_list.cc_list_curr = NULL;
+    cc_free_buflist(&cc_list, cc_bufline_ptrs);
+}
+
+/*
+ * TODO: cc_exit() function.
+ *	what else should it do???
+ */
+    void
+cc_exit(void) {
+    p_ut = old_p_ut;
+    cc_started = FALSE;
+    cc_free();
+}
+
Index: src/misc1.c
===================================================================
--- src/misc1.c	(revision 1164)
+++ src/misc1.c	(working copy)
@@ -2674,6 +2674,11 @@
 #endif
 
     changed_common(lnum, col, lnume, xtra);
+
+    /* update ew linenumbers for CodeCheck if the current buffer is 
+     * being watched. */
+    if (cc_is_buf_watched(curbuf))
+	cc_update_ew_lnums(curbuf, lnum, col, xtra);
 }
 
     static void
Index: src/Makefile
===================================================================
--- src/Makefile	(revision 1164)
+++ src/Makefile	(working copy)
@@ -1366,6 +1366,7 @@
 BASIC_SRC = \
 	buffer.c \
 	charset.c \
+	code_check.c \
 	diff.c \
 	digraph.c \
 	edit.c \
@@ -1436,6 +1437,7 @@
 OBJ = \
 	objects/buffer.o \
 	objects/charset.o \
+	objects/code_check.o \
 	objects/diff.o \
 	objects/digraph.o \
 	objects/edit.o \
@@ -1495,6 +1497,7 @@
 PRO_AUTO = \
 	buffer.pro \
 	charset.pro \
+	code_check.pro \
 	diff.pro \
 	digraph.pro \
 	edit.pro \
@@ -2327,6 +2330,9 @@
 objects/charset.o: charset.c
 	$(CCC) -o $@ charset.c
 
+objects/code_check.o: code_check.c
+	$(CCC) -o $@ code_check.c
+
 objects/diff.o: diff.c
 	$(CCC) -o $@ diff.c
 
@@ -2655,6 +2661,10 @@
   ascii.h keymap.h term.h macros.h option.h structs.h regexp.h gui.h \
   gui_beval.h proto/gui_beval.pro ex_cmds.h proto.h globals.h farsi.h \
   arabic.h
+objects/code_check.o: code_check.c vim.h auto/config.h feature.h os_unix.h auto/osdef.h \
+  ascii.h keymap.h term.h macros.h option.h structs.h regexp.h gui.h \
+  gui_beval.h proto/gui_beval.pro ex_cmds.h proto.h globals.h farsi.h \
+  arabic.h
 objects/diff.o: diff.c vim.h auto/config.h feature.h os_unix.h auto/osdef.h \
   ascii.h keymap.h term.h macros.h option.h structs.h regexp.h gui.h \
   gui_beval.h proto/gui_beval.pro ex_cmds.h proto.h globals.h farsi.h \
Index: src/keymap.h
===================================================================
--- src/keymap.h	(revision 1164)
+++ src/keymap.h	(working copy)
@@ -256,6 +256,7 @@
     , KE_NOP		/* doesn't do something */
     , KE_FOCUSGAINED	/* focus gained */
     , KE_FOCUSLOST	/* focus lost */
+    , KE_REDRAW		/* update the screen */
 };
 
 /*
@@ -452,6 +453,8 @@
 
 #define K_CURSORHOLD	TERMCAP2KEY(KS_EXTRA, KE_CURSORHOLD)
 
+#define K_REDRAW	TERMCAP2KEY(KS_EXTRA, KE_REDRAW)
+
 /* Bits for modifier mask */
 /* 0x01 cannot be used, because the modifier must be 0x02 or higher */
 #define MOD_MASK_SHIFT	    0x02
Index: src/screen.c
===================================================================
--- src/screen.c	(revision 1164)
+++ src/screen.c	(working copy)
@@ -2709,6 +2709,26 @@
 #else
     extra_check = 0;
 #endif
+
+    /* 
+     * Highlight the line if this buffer is in the code_check.c watchlist,
+     * and there is an associated error in the corresponding error/warning
+     * list.
+     */
+    if (cc_is_buf_watched(curbuf)) {
+	switch (cc_get_ew_type(curbuf, lnum)) {
+	    case /*CC_WARNING*/1:
+		line_attr = hl_attr(HLF_WM);
+		break;
+	    case /*CC_ERROR*/2:
+		line_attr = hl_attr(HLF_E);
+		break;
+	    default:
+		line_attr = 0;
+		break;
+	}
+    }
+
 #ifdef FEAT_SYN_HL
     if (syntax_present(wp->w_buffer) && !wp->w_buffer->b_syn_error)
     {
Index: src/fileio.c
===================================================================
--- src/fileio.c	(revision 1164)
+++ src/fileio.c	(working copy)
@@ -8473,6 +8473,12 @@
 {
     int		state;
 
+    /* trigger a compile request if the current buffer is in
+     * the watchlist and the current mode is NOT CMDLINE. */
+    if ((State & CMDLINE) == 0)
+	if (cc_is_buf_watched(curbuf))
+	    cc_request_compile(curbuf);
+
     if (!did_cursorhold && has_cursorhold() && !Recording
 #ifdef FEAT_INS_EXPAND
 	    && !ins_compl_active()

Raspunde prin e-mail lui