[Sciviews-commits] r346 - in komodo/SciViews-K: components content/js

noreply at r-forge.r-project.org noreply at r-forge.r-project.org
Tue Dec 21 14:19:56 CET 2010


Author: prezez
Date: 2010-12-21 14:19:55 +0100 (Tue, 21 Dec 2010)
New Revision: 346

Modified:
   komodo/SciViews-K/components/svRinterpreter.js
   komodo/SciViews-K/content/js/pref-R.js
   komodo/SciViews-K/content/js/prefs.js
   komodo/SciViews-K/content/js/r.js
   komodo/SciViews-K/content/js/sciviews.js
   komodo/SciViews-K/content/js/socket.js
Log:
sv.r.run, sv.r.runEnter, sv.getTextRange: support for rectangular selection.
sv.socket.rClient is the default rClient function = either rClientHttp or rClientSocket.
Setting "socket" back as the default connection - problems with http.


Modified: komodo/SciViews-K/components/svRinterpreter.js
===================================================================
--- komodo/SciViews-K/components/svRinterpreter.js	2010-12-20 21:06:38 UTC (rev 345)
+++ komodo/SciViews-K/components/svRinterpreter.js	2010-12-21 13:19:55 UTC (rev 346)
@@ -25,26 +25,26 @@
 function svRinterpreter() {}
 
 svRinterpreter.prototype = {
-    
+
     // Properties required for XPCOM registration
     classDescription: "The SciViews-K R interpreter",
     classID:          Components.ID("{57dbf673-ce91-4858-93f9-2e47fea3495d}"),
     contractID:       "@sciviews.org/svRinterpreter;1",
-    
+
     // Category: An array of categories to register this component in.
     _xpcom_categories: [{
-  
+
       // Each object in the array specifies the parameters to pass to
       // nsICategoryManager.addCategoryEntry(). 'true' is passed for both
       // aPersist and aReplace params.
       category: "r",
-  
+
       // Optional, defaults to the object's classDescription
       //entry: "",
-  
+
       // Optional, defaults to object's contractID (unless 'service' specified)
       //value: "...",
-  
+
       // Optional, defaults to false. When set to true, and only if 'value' is
       // not specified, the concatenation of the string "service," and the
       // object's contractID is passed as aValue parameter of addCategoryEntry.
@@ -56,7 +56,7 @@
     QueryInterface: XPCOMUtils.generateQI([Components.interfaces.svIRinterpreter]),
 
     //chromeURL: "chrome://komodo/content/colorpicker/colorpicker.html",
-    	
+
     /**
     * Escape from multiline mode in the R interpreter.
     */
@@ -64,7 +64,7 @@
 		// Currently do noting
 		return null;
 	},
-    
+
     /**
     * Query the R interpreter to get a calltip.
     * @param code - The piece of code currently edited requiring calltip.
@@ -82,7 +82,7 @@
 					var kvSvc = Components
 						.classes["@activestate.com/koViewService;1"]
 						.getService(Components.interfaces.koIViewService);
-					var ke = kvSvc.currentView.document.getView().scimoz;					
+					var ke = kvSvc.currentView.document.getView().scimoz;
 					try {
 						if (ke.callTipActive()) ke.callTipCancel();
 						ke.callTipShow(ke.anchor, tip.replace(/[\r\n]+/g, "\n"));
@@ -94,7 +94,7 @@
 		);
 		return(res);
     },
-    
+
     /**
     * Query the R interpreter to get a completion list.
     * @param code - The piece of code currently edited requiring completion.
@@ -131,7 +131,7 @@
 					// otherwise, the algorithm does not find them (try: typing T, then ctrl+J, then R)
 					// TODO: there is a problem with items with special character (conversion problems)
 					autoCstring = autoCstring.replace(/\r?\n/g, autoCSeparatorChar);
-					
+
 					// code below taken from "CodeIntelCompletionUIHandler"
 				//	var iface = Components.interfaces.koICodeIntelCompletionUIHandler;
 				//	ke.registerImage(iface.ACIID_FUNCTION, ko.markers.
@@ -187,7 +187,7 @@
 
 
 //// Komodo statusbar access ///////////////////////////////////////////////////
-function clearCodeintelMessage () {					
+function clearCodeintelMessage () {
 	var sm = Components
 		.classes["@activestate.com/koStatusMessage;1"]
 		.createInstance(Components.interfaces.koIStatusMessage);
@@ -203,7 +203,7 @@
 		.classes["@mozilla.org/appshell/window-mediator;1"]
 		.getService(Components.interfaces.nsIWindowMediator)
 		.getMostRecentWindow("Komodo")
-		.document.getElementById('statusbar-message');	
+		.document.getElementById('statusbar-message');
 	//messageWidget.setAttribute("category", sm.category);
 	//messageWidget.setAttribute("value", sm.msg);
 	//messageWidget.setAttribute("tooltiptext", sm.msg);
@@ -236,42 +236,46 @@
 //// R socket server configuration /////////////////////////////////////////////
 // Get server type preference and set sv.clientType accordingly
 if (typeof(sv) == "undefined") var sv = {};
-sv.clientType = getPrefString("sciviews.client.type", "http"); // We use http by default
+sv.clientType = getPrefString("sciviews.client.type", "socket"); // We use http by default
 
 // String converter used between Komodo and R (localeToCharset()[1] in R)
 var converter = Components
 	.classes["@mozilla.org/intl/scriptableunicodeconverter"]
 	.createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+
 // Use ASCII encoding by default
 try { converter.charset = "ASCII"; } catch (e) { }
 
+
 // The conversion functions
 function _fromUnicode (str, charset) {
-	if (charset !== undefined && converter.charset != charset) {
+	if (charset !== undefined && converter.charset != charset)
 		converter.charset = charset;
-	}
-	try { 
-		str = converter.ConvertFromUnicode(str);
+	try {
+		if (converter.charset)
+			str = converter.ConvertFromUnicode(str) + converter.Finish();
 	} catch(e) {
-		koLoggerException(e, "Unable to convert from Unicode");
+		koLoggerException(e, "sv.socket is unable to convert from Unicode to " +
+		 	converter.charset + ". The string was " + str);
 	}
 	return(str);
 }
 
 function _toUnicode (str, charset) {
-	if (charset !== undefined && converter.charset != charset) {
+	if (charset !== undefined && converter.charset != charset)
 		converter.charset = charset;
-	}
-	try { 
-		str = converter.ConvertToUnicode(str);
+	try {
+		if (converter.charset)
+			str = converter.ConvertToUnicode(str);
 	} catch(e) {
-		koLoggerException(e, "Unable to convert from Unicode");
+		koLoggerException(e, "sv.socket is unable to convert to Unicode from " +
+			converter.charset + ". The string was " + str);
 	}
 	return(str);
 }
 
-// The main socket client function to connect to R socket server				
-function rClientSocket(host, port, cmd, listener) {	
+// The main socket client function to connect to R socket server
+function rClientSocket(host, port, cmd, listener) {
 	// Workaround for NS_ERROR_OFFLINE returned by 'createTransport' when
 	// there is no network connection (when network goes down). Based on
 	// toggleOfflineStatus() in chrome://browser/content/browser.js.
@@ -289,13 +293,13 @@
 		var outstream = transport.openOutputStream(0, 0, 0);
 		cmd = _fromUnicode(cmd);
 		outstream.write(cmd, cmd.length);
-	
+
 		var stream = transport.openInputStream(0, 0, 0);
 		var instream = Components
 			.classes["@mozilla.org/scriptableinputstream;1"]
 			.createInstance(Components.interfaces.nsIScriptableInputStream);
 		instream.init(stream);
-	
+
 		var dataListener = {
 			data: "",
 			onStartRequest: function(request, context) { this.data = ""; },
@@ -323,12 +327,12 @@
 
 				// Determine if we have a prompt at the end
 				if (chunk.search(/\+\s+$/) > -1) {
-					chunk = chunk.rtrim() + " ";
+					chunk = chunk.replace(/\s+$/g, "") + " ";
 				}
 				this.data += chunk;
 			}
 		}
-	
+
 		var pump = Components
 			.classes["@mozilla.org/network/input-stream-pump;1"]
 			.createInstance(Components.interfaces.nsIInputStreamPump);
@@ -350,10 +354,10 @@
 //	if (!navigator.onLine) Components
 //		.classes["@mozilla.org/network/io-service;1"]
 //		.getService(Components.interfaces.nsIIOService2).offline = false;
-	
+
 	try {
 		var httpRequest = Components
-			.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]  
+			.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
             .createInstance(Components.interfaces.nsIXMLHttpRequest);
 		httpRequest.onreadystatechange = function () {
 			try {
@@ -382,20 +386,20 @@
 			}
 			return(null);
 		};
-				
+
 		//url is http://<host>:<port>/custom/SciViews?<cmd>
 		var url = "http://" + host + ":" + port + "/custom/SciViews?" +
 			encodeURIComponent(_fromUnicode(cmd))
 		httpRequest.open('GET', url, true);
 		httpRequest.send('');
-		
+
 	} catch (e) {
 		koLogger.error("rClientHttp() raises an unknown error: " + e);
 		return(e);
 	}
 	return(null);
 }
-	
+
 // Send an R command through the socket
 function rCommand(cmd, procfun) {
 	var host = getPrefString("sciviews.server.host", "127.0.0.1");
@@ -418,7 +422,7 @@
 				} else { // In fact we can add a property even to a function
 					procfun.value = data;
 				}
-			}	
+			}
 		}
 	}
 	var res = "";

Modified: komodo/SciViews-K/content/js/pref-R.js
===================================================================
--- komodo/SciViews-K/content/js/pref-R.js	2010-12-20 21:06:38 UTC (rev 345)
+++ komodo/SciViews-K/content/js/pref-R.js	2010-12-21 13:19:55 UTC (rev 346)
@@ -215,15 +215,17 @@
 
 function OnPreferencePageOK(prefset) {
 	prefset = parent.hPrefWindow.prefset;
-	prefset.setStringPref("svRDefaultInterpreter",
-	document.getElementById("svRDefaultInterpreter").value);
-	prefset.setStringPref("svRApplication",
-	document.getElementById('svRApplication')
-		.selectedItem.getAttribute("value"));
-	prefset.setStringPref("svRApplicationId",
-	document.getElementById('svRApplication').selectedItem.id);
 
+	//prefset.setStringPref("svRDefaultInterpreter",
+	//document.getElementById("svRDefaultInterpreter").value);
+	//prefset.setStringPref("svRApplication",
+	//	document.getElementById('svRApplication')
+	//	.selectedItem.getAttribute("value"));
 
+	//prefset.setStringPref("svRApplicationId",
+	//	document.getElementById('svRApplication').selectedItem.id);
+
+
 	var outDec = document.getElementById('r.csv.dec').value;
 	var outSep = document.getElementById('r.csv.sep').value;
 
@@ -253,8 +255,10 @@
 	// Check if selected item is different from current sv.clientType
 	if (clientType != sv.clientType)
 		sv.alert("R server type changed", "The server type you selected will be" +
-			" used after restarting of both R and Komodo!")
+			" used after restarting of both R and Komodo!");
 
+	//sv.socket.setSocketType(clientType);
+
 	menuListGetValues();
 
 	// Restart socket server if running and port changed

Modified: komodo/SciViews-K/content/js/prefs.js
===================================================================
--- komodo/SciViews-K/content/js/prefs.js	2010-12-20 21:06:38 UTC (rev 345)
+++ komodo/SciViews-K/content/js/prefs.js	2010-12-21 13:19:55 UTC (rev 346)
@@ -19,14 +19,14 @@
 // sv.prefs.defaults[preferenceName] = preferenceValue
 sv.prefs.defaults = {
 	"sciviews.server.socket": "7052",
-	"sciviews.client.type": "http",
+	"sciviews.client.type": "socket", // temporarily changed from "http"
 	"sciviews.client.socket": "8888",
 	"sciviews.client.id": "SciViewsK",
 	"sciviews.server.host": "127.0.0.1",
 	svRDefaultInterpreter: "", //????
 	svRApplication: "", //????
-	svRApplicationId: "", //????
-	svRQuiet: "", // Must be string, otherwise sv.prefs.getString will fail
+	//svRApplicationId: "", //????
+	//svRQuiet: "", // Must be string, otherwise sv.prefs.getString will fail
 	svRArgs: "--quiet", // For future use, replaces svRQuiet
     "r.csv.dec": ".",
 	"r.csv.sep": ",",
@@ -135,11 +135,11 @@
 //TODO: Rework this with respect to Mac and R.app
 // Default R interpreter Id: use a reasonable default, given the platform
 if (navigator.platform.indexOf("Win") === 0) {
-	sv.prefs.setString("svRApplicationId", "r-gui", false);
+	sv.prefs.setString("svRApplication", "r-gui", false);
 	if (!svRDefaultInterpreter)
 		svRDefaultInterpreter = sv.tools.file.whereIs("Rgui");
 } else {
-	sv.prefs.setString("svRApplicationId", "r-terminal", false);
+	sv.prefs.setString("svRApplication", "r-terminal", false);
 	if (!svRDefaultInterpreter)
 		svRDefaultInterpreter = sv.tools.file.whereIs("R");
 }
@@ -153,6 +153,7 @@
 // for this snippet
 // Help page triggered by a given URL
 sv.prefs.setString("URL-help", "", true);
+
 // R HTML help pages triggered with '?topic'
 sv.prefs.setString("R-help", "", true);
 // Help page on the R Wiki

Modified: komodo/SciViews-K/content/js/r.js
===================================================================
--- komodo/SciViews-K/content/js/r.js	2010-12-20 21:06:38 UTC (rev 345)
+++ komodo/SciViews-K/content/js/r.js	2010-12-21 13:19:55 UTC (rev 346)
@@ -367,53 +367,45 @@
 // Run current selection or line buffer in R
 sv.r.run = function () {
 	try {
-		var kv = ko.views.manager.currentView;
-		if (!kv) return(false); // No current view, do nothing!
-		kv.setFocus();
-		var ke = kv.scimoz;
-		var currentLine = ke.lineFromPosition(ke.currentPos);
-		if (ke.selText == "") {
-			ke.home();
-			ke.lineEndExtend();
-			// while ke.selText contains no code, look for next line
-			while (ke.selText.replace(/^\s*$/, "") == "") {
-				//Are we at the last line?
-				currentLine = ke.lineFromPosition(ke.currentPos);
-				if (currentLine == (ke.lineCount - 1)) return(false);
-				// Select next line
-				ke.lineDown();
-				ke.home();
-				ke.lineEndExtend();
-			}
+		var view = ko.views.manager.currentView;
+		if (!view) return(false); // No current view, do nothing!
+		view.setFocus();
+
+		var text = sv.getTextRange("sel", true);
+		if(text == "") { // No selection
+			var scimoz = view.scimoz;
+			var currentLine = scimoz.lineFromPosition(scimoz.currentPos);
+			var scimoz = view.scimoz;
+			var oText = { value: ''};
+			var lineCount =	scimoz.lineCount;
+			while(currentLine < lineCount && !(text = oText.value.trim()))
+				scimoz.getLine(currentLine++, oText);
+			scimoz.gotoLine(currentLine);
 		}
-		var res = sv.r.eval(ke.selText);
-		ke.currentPos = ke.selectionEnd; // We want to go past the highest pos
-		ke.lineDown();
-		ke.homeDisplay();
+
+		if(text != "") 	return(sv.r.eval(text));
+		return(false);
+
 	} catch(e) { return(e); }
-	return(res);
+
 }
 
 // Run current line (or selection) up to position and optionally add line feed
 sv.r.runEnter = function (breakLine) {
 	try {
-		var res = false;
-		var kv = ko.views.manager.currentView;
-		if (!kv) return(false); // No current view, do nothing!
-		kv.setFocus();
-		var ke = kv.scimoz;
-		if (ke.selText == "") {	// Only proceed if selection is empty
+		var view = ko.views.manager.currentView;
+		if (!view) return(false); // No current view, do nothing!
+		view.setFocus();
+		var scimoz = view.scimoz;
+		var text = sv.getTextRange("sel", true);
+		if (!text) {	// Only proceed if selection is empty
 			// Get text from a line and move caret to the eol
 			// Do we want to break line here or execute it to the end?
-			var text = sv.getTextRange(breakLine? "linetobegin" : "line", true);
-			ko.commands.doCommand('cmd_newlineExtra');
-			if (text != "") res = sv.r.eval(text);
-		} else {
-			var res = sv.r.eval(ke.selText);
-			ke.currentPos = ke.selectionEnd; // We want to go past the highest pos
-			ke.selectionStart = ke.selectionEnd; // Collapse selection
-			ko.commands.doCommand('cmd_newlineExtra');
+			text = sv.getTextRange(breakLine? "linetobegin" : "line", true);
 		}
+		ko.commands.doCommand('cmd_newlineExtra');
+		var res = sv.r.eval(text);
+
 	} catch(e) { return(e); }
 	return(res);
 }
@@ -422,11 +414,11 @@
 sv.r.source = function (what) {
 	var res = false;
 	try {
-		var kv = ko.views.manager.currentView;
-		if (!kv) return(false); // No current view, do nothing!
-		kv.setFocus();
-		var scimoz = kv.scimoz;
-		var doc = kv.document;
+		var view = ko.views.manager.currentView;
+		if (!view) return(false); // No current view, do nothing!
+		view.setFocus();
+		var scimoz = view.scimoz;
+		var doc = view.document;
 
 		var file;
 		if (!doc.isUntitled && doc.file) {
@@ -442,7 +434,7 @@
 		if (what == "all" && doc.file && doc.file.isLocal &&
 		!doc.isUntitled && !doc.isDirty) {
 			res = sv.r.eval('source("' + file +  '", encoding = "' +
-			kv.encoding + '")');
+			view.encoding + '")');
 		} else {
 			// Save all or part in the temporary file and source that file.
 			// After executing, tell R to delete it.
@@ -475,10 +467,10 @@
 sv.r.send = function (what) {
 	//sv.log.debug("sv.r.send " + what);
 	var res = false;
-	var kv = ko.views.manager.currentView;
-	if (!kv) return(false); // No current view, do nothing!
-	kv.setFocus();
-	var ke = kv.scimoz;
+	var view = ko.views.manager.currentView;
+	if (!view) return(false); // No current view, do nothing!
+	view.setFocus();
+	var ke = view.scimoz;
 
 	try {
 		if (!what) what = "all"; // Default value
@@ -1255,7 +1247,7 @@
 				break;
 			}
 		}
-	} 
+	}
 
 	// Execute the command in R (TODO: check for possible error here!)
 	// TODO: run first in R; make dirs in R; then change in Komodo!
@@ -1440,7 +1432,7 @@
 }
 
 
-// KB: I think these functions should be included only in a "developer's version", 
+// KB: I think these functions should be included only in a "developer's version",
 //     most users will not need them
 // Create a translation (.pot) file for a project
 sv.r.kpf2pot = function (kpfFile) {

Modified: komodo/SciViews-K/content/js/sciviews.js
===================================================================
--- komodo/SciViews-K/content/js/sciviews.js	2010-12-20 21:06:38 UTC (rev 345)
+++ komodo/SciViews-K/content/js/sciviews.js	2010-12-21 13:19:55 UTC (rev 346)
@@ -146,10 +146,8 @@
 
 	var currentView = ko.views.manager.currentView;
 	if (!currentView) return("");
-
 	currentView.setFocus();
 	var scimoz = currentView.scimoz;
-
 	var text = "";
 	var curPos = scimoz.currentPos;
 	var curLine = scimoz.lineFromPosition(curPos);
@@ -165,6 +163,17 @@
 	switch(what) {
 	 case "sel":
 		// Simply retain current selection
+		var nSelections = scimoz.selections;
+		if(nSelections > 1) { // rectangular selection
+			var msel = [];
+			for (var i = 0; i < scimoz.selections; i++) {
+				msel.push(scimoz.getTextRange(scimoz.getSelectionNStart(i), scimoz.getSelectionNEnd(i)));
+			}
+			text = msel.join("\n");
+			// TODO: What to do with multiple ranges?
+			pStart = scimoz.getSelectionNStart(0);
+			pEnd = 	scimoz.getSelectionNEnd(nSelections - 1);
+		}
 		break;
 	 case "word":
 		if (pStart == pEnd) { // only return word if no selection
@@ -326,12 +335,13 @@
 		text = scimoz.text;
 	}
 
-	if (what != "all") {
-		text = scimoz.getTextRange(pStart, pEnd).replace(/(^[\n\r]+|[\n\r]+$)/, "");
-		if (gotoend) scimoz.gotoPos(pEnd);
-		if (select) scimoz.setSel(pStart, pEnd);
-	}
-	if ((typeof range == "object") && (range != null)) {
+	if (!text) text = scimoz.getTextRange(pStart, pEnd).trim();
+
+	if (gotoend) scimoz.gotoPos(pEnd);
+	if (select && what !="sel") scimoz.setSel(pStart, pEnd);
+
+
+	if (range != undefined && (typeof range == "object")) {
 		range.value = {start: pStart, end: pEnd};
 	}
 	return(text);

Modified: komodo/SciViews-K/content/js/socket.js
===================================================================
--- komodo/SciViews-K/content/js/socket.js	2010-12-20 21:06:38 UTC (rev 345)
+++ komodo/SciViews-K/content/js/socket.js	2010-12-21 13:19:55 UTC (rev 346)
@@ -238,9 +238,11 @@
 	return(null);
 }
 
+this.rClient = this.rClientSocket; // default client type
+
 // TODO: use this on preference change "sciviews.client.type"
 this.setSocketType = function (type) {
-	switch(serverType) {
+	switch(type) {
 		case "http":
 			_this.rClient = this.rClientHttp;
 			break;
@@ -252,13 +254,14 @@
 	}
 }
 
+this.setSocketType(sv.prefs.getString("sciviews.client.type", "socket"));
+
 // Send an R command through the socket
 // any additional arguments will be passed to procfun
 // procfun can be also an object, then the result will be stored in procfun.value
 this.rCommand = function (cmd, echo, procfun) {
-	sv.cmdout.append("rCommand:: " + cmd);
+	//sv.cmdout.append("rCommand:: " + cmd);
 
-
 	if (echo === undefined) echo = true;
 	//if (procfun === undefined) procfun = "sv.socket.rProcess";
 
@@ -292,20 +295,18 @@
 			}
 		}
 	}
-	var res = "";
 
-	// TODO: use .rClient instead
-	//res = _this.rClient(host, port, id + cmd + "\n", listener,
+	var res = _this.rClient(host, port, id + cmd + "\n", listener,
+			echo, procname);
+	// Socket server in svSocket
+	//if (sv.prefs.getString("sciviews.client.type", "socket") == "socket") {
+	//if (sv.clientType == "socket") {
+	//	res = _this.rClientSocket(host, port, id + cmd + "\n", listener,
 	//		echo, procname);
-
-	// Socket server in svSocket
-	if (sv.prefs.getString("sciviews.client.type", "socket") == "socket") {
-		res = _this.rClientSocket(host, port, id + cmd + "\n", listener,
-			echo, procname);
-	} else {						// Http server in svGUI
-		res = _this.rClientHttp(host, port, id + cmd + "\n", listener,
-			echo, procname);
-	}
+	//} else {						// Http server in svGUI
+	//	res = _this.rClientHttp(host, port, id + cmd + "\n", listener,
+	//		echo, procname);
+	//}
 	if (res && res.name && res.name == "NS_ERROR_OFFLINE")
 		ko.statusBar.AddMessage("Error: Komodo went offline!",
 			"SciViews-K client", 5000, true);
@@ -340,15 +341,14 @@
 // TODO: add the current working directory and report WD changes from R automagically
 this.rUpdate = function () {
 	// Make sure that dec and sep are correctly set in R
-	this.rCommand('<<<H>>>options(OutDec = "' +
-		sv.prefs.getString("r.csv.dec", ".") +
-		'", OutSep = "' +
-		sv.prefs.getString("r.csv.sep", ",") +
+	this.rCommand('<<<h>>>options(' +
+		'OutDec = "' + sv.prefs.getString("r.csv.dec", ".") +
+		'", OutSep = "' + sv.prefs.getString("r.csv.sep", ",") +
 		'"); invisible(guiRefresh(force = TRUE)); ' +
+		'cat("", localeToCharset()[1], sep="");',
 		// ??? The following does not work.
 		//'cat("<<<charset=", localeToCharset()[1], ">>>", sep = "")',
-		'cat("", localeToCharset()[1], sep="")',
-		false, function (s) {
+		false, function (s) { // this is a callback receiving the character set
 			_this.charset = sv.tools.strings.trim(s);
 			if (_this.debug) sv.log.debug("R charset: " + _this.charset);
 	});



More information about the Sciviews-commits mailing list