Skip to content

Commit

Permalink
feat: (alpha) support terminal intell
Browse files Browse the repository at this point in the history
  • Loading branch information
life2015 committed Mar 11, 2024
1 parent 65b42c3 commit d7eb345
Show file tree
Hide file tree
Showing 20 changed files with 2,768 additions and 2 deletions.
3 changes: 1 addition & 2 deletions packages/core-browser/src/bootstrap/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ import {
import { IElectronMainLifeCycleService } from '@opensumi/ide-core-common/lib/electron';

import { ClientAppStateService } from '../application';
import { ElectronConnectionHelper, WebConnectionHelper } from '../application/runtime';
import { ESupportRuntime } from '../application/runtime';
import { ESupportRuntime, ElectronConnectionHelper , WebConnectionHelper } from '../application/runtime';
import { CONNECTION_HELPER_TOKEN } from '../application/runtime/base-socket';
import { BrowserRuntime } from '../application/runtime/browser';
import { ElectronRendererRuntime } from '../application/runtime/electron-renderer';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.suggestions {
display: flex;
flex-direction: column;
background-color: var(--editorGroupHeader-tabsBackground);
color: var(--ai-native-text-color-common);
max-height: 350px;
width: 500px;
overflow-y: auto;
position: absolute;
bottom: 100%;
left: 0;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}

.suggestionItem {
padding: 6px 10px 6px 16px;
cursor: pointer;
}

.suggestionItemContainer {
display: flex;
flex-direction: column;
}

.suggestionDesc {
font-size: 12px;
}

.suggestionCmd {
font-size: 12px;
opacity: 0.6;
}

.suggestionItem:hover {
filter: brightness(110%);
background-color: var(--selection-background);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import React, { useEffect, useState } from 'react';

import { Emitter } from '@opensumi/ide-core-common';

import styles from './terminal-intell-command-controller.module.less';

export interface SmartCommandDesc {
description: string;
command: string;
}

// 支持键盘选择的列表
export const KeyboardSelectableList = (props: {
items: { description: string; command: string }[];
handleSuggestionClick: (command: string) => void;
controller?: Emitter<string>;
noListen?: boolean;
}) => {
const { items, handleSuggestionClick, noListen = false, controller } = props;
// 选中项的索引,默认为最后一个
const [selectedIndex, setSelectedIndex] = useState(-1);

// 处理键盘事件
const handleKeyPress = (event: KeyboardEvent) => {
switch (event.key) {
case 'ArrowUp': // 上键
setSelectedIndex((prevIndex) => Math.max(prevIndex - 1, 0));
break;
case 'ArrowDown': // 下键
setSelectedIndex((prevIndex) =>
Math.min(prevIndex + 1, items.length - 1),
);
break;
case 'Enter': // 回车键
if (items[selectedIndex]) {
handleSuggestionClick(items[selectedIndex].command);
}
break;
default:
break;
}
};

// 添加全局键盘事件监听器
useEffect(() => {
if (noListen) {return;}
window.addEventListener('keydown', handleKeyPress);
return () => {
window.removeEventListener('keydown', handleKeyPress);
};
}, [items, selectedIndex]);

useEffect(() => {
if (!controller) {return;}
const disposable = controller.event((e: string) => {
if (e === 'ArrowUp') {
setSelectedIndex((prevIndex) => Math.max(prevIndex - 1, 0));
}
if (e === 'ArrowDown' || e === 'Tab') {
setSelectedIndex((prevIndex) =>
Math.min(prevIndex + 1, items.length - 1),
);
}
if (e === 'Enter') {
if (items[selectedIndex]) {
handleSuggestionClick(items[selectedIndex].command);
}
}
});

return () => {
disposable.dispose();
};
}, [controller, selectedIndex, items]);

useEffect(() => {
// HACK 定位到顶部
setSelectedIndex(0);
}, [items]);

return (
<div className={styles.suggestions}>
{items.map((cmd, index) => (
<div
key={index}
className={styles.suggestionItem}
style={{ backgroundColor: index === selectedIndex ? 'var(--selection-background)' : '' }}
onClick={() => handleSuggestionClick(cmd.command)}
>
<div className={styles.suggestionItemContainer}>
<div className={styles.suggestionDesc}>{cmd.description}</div>
<div className={styles.suggestionCmd}>{cmd.command}</div>
</div>
</div>
))}
</div>
);
};

export const TerminalIntellCommandController = (props: {
suggestions: SmartCommandDesc[];
controller: Emitter<string>;
onSuggestion: (suggestion: string) => void;
}) => {
const { suggestions, controller, onSuggestion } = props;

return (
<div style={{ position: 'relative' }}>
{suggestions.length > 0 && (
<KeyboardSelectableList
items={suggestions}
controller={controller}
handleSuggestionClick={onSuggestion}
noListen
/>
)}
</div>
);
};
1 change: 1 addition & 0 deletions packages/terminal-next/src/browser/contribution/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './terminal.view';
export * from './terminal.keybinding';
export * from './terminal.network';
export * from './terminal.preference';
export * from './terminal.intell';
18 changes: 18 additions & 0 deletions packages/terminal-next/src/browser/contribution/terminal.intell.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Autowired } from '@opensumi/di';
import { ClientAppContribution, Domain } from '@opensumi/ide-core-browser';
import {
MaybePromise,
} from '@opensumi/ide-core-common';

import { IntellTerminalService } from '../intell/intell-terminal.service';

@Domain(ClientAppContribution)
export class TerminalIntellContribution implements ClientAppContribution {

@Autowired(IntellTerminalService)
intellTerminalService: IntellTerminalService;

onDidStart(): MaybePromise<void> {
this.intellTerminalService.active();
}
}
2 changes: 2 additions & 0 deletions packages/terminal-next/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { ITerminalPreference } from '../common/preference';

import {
TerminalCommandContribution,
TerminalIntellContribution,
TerminalKeybindingContribution,
TerminalLifeCycleContribution,
TerminalMenuContribution,
Expand Down Expand Up @@ -60,6 +61,7 @@ export class TerminalNextModule extends BrowserModule {
TerminalKeybindingContribution,
TerminalNetworkContribution,
TerminalPreferenceContribution,
TerminalIntellContribution,
{
token: ITerminalApiService,
useClass: TerminalApiService,
Expand Down
23 changes: 23 additions & 0 deletions packages/terminal-next/src/browser/intell/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## 终端的智能补全能力

终端的智能补全能力是指终端在输入命令时,能够根据用户的输入,自动提示可能的命令或参数,交互方式类似于编程时的语言服务。

此功能可以增加用户使用终端时的易用性。

## 功能建设
此功能目前依然处于 Alpha 的早期实验版本,这里列举需要讨论或者处理的问题:

- [ ] 补全设计优化:目前的设计主要服务于功能验证,因此 UI 看起来很简陋,需要做后续的优化
- [ ] 补全交互方式优化:比如说 上下键选择,Tab 确认。或者 Tab 或者 上下键 选择,Enter 确认
- [ ] Generator 补全支持,目前还不支持调用命令的 补全,因为是基于前端做的,可能要做个前后端通信
- [ ] 渲染方式优化,目前是直接渲染在 Xterm.js Decorations 上面的,考虑做一个全局 DOM,然后通过 DOM Align + Xterm.js Decoration 来做生命周期绑定和位置绑定
- [ ] 讨论是否需要转移补全逻辑到 Node.js
- [ ] 把基于 Fig 打包 bundle 的逻辑转移到 OpenSumi 这边
- [ ] CodeStyle 处理,目前没有对从 inShellisense 项目的代码做处理,考虑到未来比较方便更新代码,不过这块要看看是不是要格式化一下代码什么的

## 开源项目依赖
感谢开源项目提供的灵感和相关能力支持:

https://github.com/withfig/autocomplete

https://github.com/microsoft/inshellisense
Loading

0 comments on commit d7eb345

Please sign in to comment.