private void trimUnusedTreeCategories() {

		/*
		 			Unusual Algorithm
		
			The tree nodes represent categories, but do not contain nodes for individual
		 	scripts.  We wish to remove any of the tree nodes that no longer represent script
		 	categories.  (This can happen when a script is deleted or its category is changed.)
		 	This algorithm will assume that all nodes need to be deleted.  Then, each script is
		 	examined, using its category to mark a given node as 'safe'; that node's parents are
			also marked as safe.   Any nodes remaining unmarked have no reference script and
			will be deleted.
		 */

		// note: turn String[] to List<String> to use hashing
		Set<List<String>> categories = new HashSet<>();
		for (ScriptInfo info : infoManager.getScriptInfoIterable()) {
			String[] path = info.getCategory();
			List<String> category = Arrays.asList(path);
			for (int i = 1; i <= category.size(); i++) {
				categories.add(category.subList(0, i));
			}
		}

		List<GTreeNode> toDelete = new LinkedList<>();
		Iterator<GTreeNode> nodes = new BreadthFirstIterator(scriptRoot);
		for (GTreeNode node : CollectionUtils.asIterable(nodes)) {
			String[] path = getCategoryPath(node);
			List<String> category = Arrays.asList(path);
			if (!categories.contains(category)) {
				toDelete.add(node);
			}
		}

		for (GTreeNode node : toDelete) {
			GTreeNode parent = node.getParent();
			if (parent != null) {
				parent.removeNode(node);
			}
		}
	}
refresh the list of scripts by listing files in each script directory.

Note: this method can be used off the swing event thread.
Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java