monaco-editor实现全局内容和文件搜索

写在前面 欢迎关注公众号:java开发高级进阶, 有不会的能够直接公众号留言提问javascript

monaco-editor不提供全局搜索,只提供单个文件内的搜索,那么如何实现全局搜索呢?html

环境:Nodejs + React + Dva + monaco-editor + react-monaco-editor + antdjava

1、绑定快捷键

调用editor.addCommand方法绑定快捷键,经过monaco.KeyMod和monaco.KeyCode选择快捷键node

快捷键要在编辑器建立完成时建立,故这段代码要写在editorDidMount里面react

全局搜索要有一个搜索框供输入关键字,故在触发快捷键的时候让搜索框显示,默认是不显示。代码以下:git

editorDidMount = (editor, monaco) => {
	editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KEY_F, () => {
		// 显现全局搜索框
		this.setState({
			showModel: true
		})
	});

	editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KEY_N, () => {
		// 显现文件搜索框
		this.setState({
			showFileModel: true
		})
	});
	editor.focus();
}
复制代码

2、搜索框实现

搜索框样式主要看本身的需求,我这里只写个简单的搜索框github

如何实现搜索框根据内容多少滚动显示列表是咱们要考虑的一个问题,通过一番搜索,我锁定了react-infinite-scroller,经过InfiniteScroll API实现滚动下拉效果。代码以下:json

<div style={{height:'400px', overflow:'auto'}}>
	<InfiniteScroll
			pageStart={0}
			loadMore={true}
			hasMore={true || false}
			loader={null}
			useWindow={false}
	>
		{rowsList}
	</InfiniteScroll>
</div>
复制代码

3、关键字搜索功能

monaco-editor不提供全局搜索功能,若是要在monaco-editor基础上实现全局搜索,咱们能够借助单个文件的搜索功能,而后遍历业务系统的全部文件,对每一个文件的搜索结果进行汇总实现全局搜索。api

这里须要考虑几个问题:antd

(1) monaco-editor单个文件搜索是基于建立model的基础上,而经过编辑器点击打开某个文件时才会建立model,咱们怎么能获取业务系统全部文件的model? (2) 业务系统有.git,node_modules等不须要遍历的文件,还有文件夹里的子文件夹及子文件,那么如何实现遍历?

首先针对第一个问题作出解答:

若是对monaco-editor没有通过一番研究可能不会发现这个规律,经过查看官网的api,发现monaco-editor提供createModel方法,咱们能够手动建立model

针对第二个问题作出解答

其实.git ,node_modules文件很好排除,只要遍历的时候加个if判断就行;

遍历到文件夹时,咱们能够经过nodejs提供的fs.Stats类里面的isDirectory()方法判断,若是是文件夹,就进入文件夹里继续遍历,这里用到递归遍历

在单个文件中搜索关键字可使用model.findMatches(keyword)实现

再建一个全局的变量用于保存遍历到的结果就行。

代码以下:

let result =  new Map();

/**
 * 业务系统文件遍历
 * @param projectPath
 * @param searchText
 */
getProjectFileListInfo = (projectPath, searchText) => {

	let pa = window.fs.readdirSync(projectPath);
	let that = this;
	pa.forEach(function(ele,index){
		let info = window.fs.statSync(projectPath+"/"+ele);
		if(ele == 'node_modules' || ele == '.git' ) {

		} else if(info.isDirectory()){
			that.getProjectFileListInfo(projectPath + "/"+ ele , searchText)

		}else{
			// 读取文件内容
			const fileInfo = window.fs.readFileSync(projectPath+'/'+ele, 'utf-8');
			// 根据文件名后缀判断应该建立哪一种model
			// 分离后缀
			let suffix = ele.split('.')[1];
			switch (suffix) {
				case 'js':

					let tempModel1 = monaco.editor.createModel(fileInfo, 'javascript');
					that.findMatches(tempModel1, searchText, projectPath + "/"+ ele, ele);
					break;
				case 'html':

					let tempModel2 = monaco.editor.createModel(fileInfo, 'html');
					that.findMatches(tempModel2, searchText, projectPath + "/"+ ele, ele);
					break;
				case 'json':

					let tempModel3 = monaco.editor.createModel(fileInfo, 'javascript');
					that.findMatches(tempModel3, searchText, projectPath + "/"+ ele, ele);
					break;
				case 'java':

					let tempModel4 = monaco.editor.createModel(fileInfo, 'java');
					that.findMatches(tempModel4, searchText, projectPath + "/"+ ele, ele);
					break;
			}

		}
	})

};


/**
 * 根据关键字匹配单个文件
 * @param model
 * @param searchText
 * @param filePath
 * @param fileName
 */
findMatches = (model, searchText, filePath, fileName) => {
	let arr = [];
	for (let match of model.findMatches(searchText)) {
		arr.push({
			text:model.getLineContent(match.range.startLineNumber),
			range: match.range,
			model: model,
			filePath: filePath,
			fileName: fileName
		});
	}

	result.set(model.uri.toString(),arr);
};
复制代码

4、搜索结果列表展现

先看效果图

在这里插入图片描述

在展现列表中,咱们但愿看到关键字所在的文件名,行号,甚至是路径,只要咱们保存展现结果时将这些信息都保存进去,而后react渲染时渲染出来,代码如上面arr变量所示

5、关键字高亮显示

在上图咱们能够看到展现结果中全部的登陆关键字都是黄色背景显示的,这里调用antd的Typography 实现高亮。

代码以下:

// ev为result,全局搜索的结果
let i=0;
let rowsList = [];
ev.forEach((values,key) =>{
	values.forEach((row) => {
		i++;
		let rowReplace = row.text.replace(keyword,'<Text mark>'+keyword+'</Text>');
		// 关键字字符串截取,添加高亮
		let preV = row.text.substring(0,row.range.startColumn - 1);
		let nextV = row.text.substring(row.range.endColumn - 1, row.text.length);
		rowsList.push(<List.Item className="SelectInfo-listItem" key={i} onClick={() => goto(row.range, row.model, row.filePath, row.fileName)}>
			<div>{preV}<Text mark>{keyword}</Text>{nextV}</div>
			<List.Item.Meta/>
			<div className="SelectInfo-foot">{row.fileName}: {row.range.startLineNumber}</div>
			</List.Item>);
	})
});
复制代码

从代码中能够看出,我这里使用的是字符串截取找到关键字而后给关键字添加 。能够看出我给展现添加了文件名和所在行属性。

6、goto点击跳转

实现差很少了,怎么能少掉点击展现列跳转到对应文件即编辑器打开对应文件

从标题五能够看出,我给每一行加了onClick点击调用goto方法。

代码以下:

//这里range和model,对应findAllMatches返回结果集合里面对象的range和model属性
goto = (range, model, filePath, fileName) => {
	this.openFileEditor(filePath, fileName);
	this.setState({
		showModel: false
	});
	this.setState({
		showFileModel: false
	})

};

openFileEditor = (file, fileName) => {

	const { dispatch } = this.props;
	dispatch({
		type: 'project/openTab',
		payload: {
			key: fileName, title: fileName, model: { filePath: file, fileName: fileName }
		}
	});
};
复制代码
/**
 * TAB:{
 *       key:'', 标签页惟一标识
 *       title:'', 标签页标题
 *       isDirty: false, 标识模型数据是否发生变化, 关闭标签页前判断isDirty是否true,若是是,则须要触发保存的方法
 *       model: {
 *       }
 * }
 * @param {*} state 
 * @param {*} action 
 */
openTab(state, {payload}) {
	let openTabs = state.openTabs.concat([]);
	const openFile = {
		key: payload.key,
		title: payload.title,
		extension: payload.extension,
		model: payload.model
	};
	
	openFile.isDirty = false;
	const index = openTabs.findIndex(file => file.key == openFile.key);
	if (index == -1) {
		openTabs.push(openFile);
	}
	return {...state, openTabs, selectTabPane: openFile.key, lastSelectedPane: state.selectTabPane}
},
// 经过openTab收集全部打开的文件保存在openTabs里,方便后续的操做
复制代码
/**
 * 打开的文件
 */
let openTabs = this.props.openTabs.map(tab => {
	const title = (tab.isDirty ? "* " : '' ) + tab.title;
	return <TabPane style={{height: '100%'}} closable={true} tab={title} key={ tab.key }> {pluginInject.openDynamicTab(tab)} </TabPane>
});
panels = panels.concat(openTabs);

/**
经过{
		panels
	}将面板显示出来
*/
复制代码
/**
 * 打开标签页面板
 */
openDynamicTab(tab) {
	const { key } = tab;
	//若是传入的参数没有extension, 则经过key取得
	const extension = tab.extension ? tab.extension : key.split('.')[1];
	const editor = this.getEditorPlugin(extension);
	if (editor) {
		return this.getEditorPlugin(extension).getTabPanel(tab, extension);
	}
	return <div>未找到[{extension}]类型的编辑器</div>
},
复制代码

七 全局文件搜索

文件搜索比关键字搜索简单一点,在遍历业务系统时遍历全部文件,将和关键字匹配的全部文件保存到result中, 其余的操做同样

结束

部分源码请到Github上:github.com/griabcrh/gl…

到这里就结束了,恭喜你学会了怎么实现全局搜索,若是以为本问对你有帮助,欢迎关注公众号:java开发高级进阶,get到跟多知识。

在这里插入图片描述
相关文章
相关标签/搜索