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到跟多知識。

在這裏插入圖片描述
相關文章
相關標籤/搜索